Spring 原始碼詳解(一)

帶著假髮的程式設計師發表於2020-12-18

一、所需依賴
1、Spring核心依賴
2、Spring DAO依賴
3、Spring Web依賴
4、Spring Test依賴
二、XML名稱空間
三、IOC
1、什麼是IOC
2、什麼是DI
3、DI的實現方式
3.1、構造器注入
3.2、Setter注入
4、IOC容器
4.1、IOC容器的設計
4.2、ApplicationContext
4.3、註解注入方式
4.3.1、@Autowired 自動裝配的歧義性
4.3.2、@Autowired 為什麼作用在介面上

一、所需依賴

<!-- Spring依賴 -->
    <!-- 1.Spring核心依賴 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>
<!-- 2.Spring dao依賴 -->
<!-- spring-jdbc包括了一些如jdbcTemplate的工具類 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>
    <!-- 3.Spring web依賴 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>
    <!-- 4.Spring test依賴:方便做單元測試和整合測試 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.2.9.RELEASE</version>
  </dependency>

1、Spring核心依賴
spring-core、spring-beans、spring-context
2、Spring DAO依賴
spring-jdbc (JDBCTemplate模板)、spring-tx
3、Spring Web依賴
spring-web、spring-webmvc
4、Spring Test依賴
spring-test

二、XML名稱空間

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
        
</beans>

三、IOC
1、什麼是IOC
“控制反轉”, 將建立物件的過程交給Spring, 比如我們不需要了解Redis或Mybatis的構建過程, 只需簡單的配置即可使用, 又比如多個團隊開發不同元件, 我們不需要其他團隊開發的元件構建過程, 我們這需要的時候直接拿來用即可

2、什麼是DI
“依賴注入” IOC的實現方式

3、DI的實現方式
構造器注入
Setter注入
介面注入

目前最常用的是構造器注入與Setter注入, 介面注入一般是使用第三方API時使用, 底層Spring都是通過反射來實現的, 這個我們後面再討論

3.1、構造器注入
建立 Entity實體類

@Data
public class Users {
    private Long id;
    private String username;
    private String password;
    private String email;

    public Users(Long id, String username, String password, String email) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.email = email;
    }
}

配置XML

<bean id="users" class="com.chenjiaxin.spring.entity.Users">
    <constructor-arg index="0" value="1"/>
    <constructor-arg index="1" value="zhangsan"/>
    <constructor-arg index="2" value="zhangsan123"/>
    <constructor-arg index="3" value="18297828@sinl.com"/>
</bean>

3.2、Setter注入
該方式是Spring比較推薦的方式, 優點是靈活性高, 由於當我們構造引數足夠多時, 構造器注入就顯得十分冗餘
首先,預設情況下 要注入的物件必須要有無參構造器

@Data
public class Users {
    private Long id;
    private String username;
    private String password;
    private String email;
    // 可以省略, 這裡為了說明
    public Users() {
    }
}

XML配置

<bean id="users" class="com.chenjiaxin.spring.entity.Users">
    <property name="email" value="18297828@sinl.com"/>
</bean>

4、IOC容器
從上面的例子中我們知道IOC的作用, 它可以容納我們開發的各種Bean

4.1、IOC容器的設計
IOC容器的設計主要依賴於 BeanFactory與ApplicationContext兩個介面, 其中 ApplicationContext是BeanFactory的子介面, 換就話說
BeanFactory是IOC最底層的介面, 但是工作中我們一般使用 ApplicationContext, 因為它對BeanFactory的功能又做了許多擴充套件

  1. BeanFactory原始碼
public interface BeanFactory {
    String FACTORY_BEAN_PREFIX = "&";

    Object getBean(String var1) throws BeansException;

    <T> T getBean(String var1, Class<T> var2) throws BeansException;

    Object getBean(String var1, Object... var2) throws BeansException;

    <T> T getBean(Class<T> var1) throws BeansException;

    <T> T getBean(Class<T> var1, Object... var2) throws BeansException;

    <T> ObjectProvider<T> getBeanProvider(Class<T> var1);

    <T> ObjectProvider<T> getBeanProvider(ResolvableType var1);

    boolean containsBean(String var1);

    boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;

    boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;

    @Nullable
    Class<?> getType(String var1) throws NoSuchBeanDefinitionException;

    @Nullable
    Class<?> getType(String var1, boolean var2) throws NoSuchBeanDefinitionException;

    String[] getAliases(String var1);
}

由於這個介面的重要性, 筆者有必要進行一些基本的闡述

  • getBean 的多個方法用於獲取配置給Spring IOC容器的Bean, 從引數型別可以看出可以是字串也可以是Class型別
    isSingleton 判斷是否為單例 true 為單例 isPrototype 判斷是否為非單例 true 為非單例
    getAliases 獲取別名

4.2、ApplicationContext

  1. ApplicationContext原始碼
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
    @Nullable
    String getId(); //獲取ID

    String getApplicationName(); //獲取應用名稱

    String getDisplayName(); //獲取應用顯示名稱

    long getStartupDate(); //獲取應用啟動時間

    @Nullable
    ApplicationContext getParent(); //獲取父級應用上下文

    AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException; //獲取bean工廠(DefaultListableBeanFactory)
}

4.2.1、ClassPathXmlApplicationContext
會在啟動時載入指定XML配置, 初始化容器, 比如通過上面的 DI進行Setter或構造器注入之後 我們想得到這個Bean可以這樣做

public static void main(String[] args) {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
    Users users = context.getBean("users", Users.class);
    System.out.println(users);
}

4.2.2、AnnotationConfigApplicationContext
使用AnnotationConfigApplicationContext可以實現基於Java的配置類載入Spring的應用上下文。避免使用application.xml進行配置。相比XML
配置,更加便捷。

  1. 建立 configuration配置類
@Configuration註解就相當於XML中的<beans/> 標籤
@Configuration
public class UserConfig {
    @Bean
    public Users getUsers() {
        Users users = new Users();
        users.setId(1L);
        users.setUsername("lisi");
        return users;
    }
}
  1. 讀取該bean
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(UserConfig.class); //註冊配置類
context.refresh(); //重新整理容器
Users bean = context1.getBean(Users.class);

4.3、註解注入方式
元件掃描 @Component && @ComponentScan
自動裝配 @Autowired

註解注入 等同於

元件掃描

  1. 在POJO類上面新增 @Component 註解
  2. 新增配置類,使用 @ComponentScan 使用包掃描, 掃描指定包下的 @Component 註解
  3. 使用 AnnotationConfigApplicationContext類獲取物件
@Component
public class Users{
    @Value("1")
    private Long id;
    @Value("zhangsan")
    private String username;
}

@ComponentScan(basePackages = "com.chenjiaxin")
public class UserConfig {
}

public class SpringMain {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(UserConfig.class);
        Users bean = context.getBean(Users.class);
        System.out.println(bean);
    }
}

元件掃描 只能作用在簡單資料型別之上, 如果要作用在複雜物件上是不可取的, 比如下面的例子
//角色類

@Data
public class Role {
    private Long id;
    private String roleName;
}

//使用者類
@Data
public class Users{
    private String email;
    private Role role;
}

//XML
<bean id="role" class="com.chenjiaxin.spring.entity.Role">
    <property name="id" value="1"/>
    <property name="roleName" value="管理員"/>
</bean>

<bean id="users" class="com.chenjiaxin.spring.entity.Users">
    <property name="email" value="18297828@sinl.com"/>
    <property name="role" ref="role"/>
</bean>

//獲取物件
public class SpringMain {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
        Users bean = context.getBean(Users.class);
        System.out.println(bean);
    }
}

@Autowired 自動裝配 原理是Set注入, 對上方測試程式碼進行改造

//角色POJO
@Data
@Component
public class Role {
    @Value("1")
    private Long id;
    @Value("管理員")
    private String roleName;
}

//使用者POJO
@Data
@Component
public class Users{
    @Autowired  
    private Role role;
    @Value("981928@sign.com")
    private String email;
}

//配置註解掃描
@ComponentScan(basePackages = "com.chenjiaxin")
public class UserConfig {
}

//測試
public class SpringMain {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(UserConfig.class);
        Users bean = context.getBean(Users.class);
        System.out.println(bean);
    }
}

@Autowired 是按照型別 獲取Bean的

public interface BeanFactory {
    ...
    <T> T getBean(Class<T> var1) throws BeansException;
    ...
}

4.3.1、@Autowired 自動裝配的歧義性
先看下面一個例子, Uservice介面有兩個實現類, 在Test中 使用Autowired 會出現歧義性

public interface UserService {
    // 讀取資料庫虛擬碼業務介面
    Users selectOne();
}

//實現類一
@Service
public class UserServiceImpl implements UserService {
   ...
}

//實現類二
@Service
public class UserServiceImplTwo implements UserService {
   ...
}

//測試類
@RunWith(SpringRunner.class)
@ContextConfiguration(classes=UserConfig.class)
public class SpringMain {
    @Autowired
    private UserService userService;
    
    @Test
    public void  test1() {
        System.out.println(userService.selectOne());
    }
}

解決方案:

  • @Primary
  • @Qualifier

方式一: 被 @Primary標註的實現類 ,當spring 進行裝配時 會被優先處理。

@Service
@Primary
public class UserServiceImpl implements UserService {
  ...
}

方式二: 也可以在裝配的時候 強制讓spring 按照名稱進行裝配 也就是XML中bean標籤的ID

@Autowired
@Qualifier("userServiceImpl")
private UserService userService;

4.3.2、@Autowired 為什麼作用在介面上
如果Spring配置了<context:component-scan base-package=“com.*.service”></context:component-scan>,並且要注入的介面只有一個實現類的話,那麼spring框架可
以自動將interface與其實現類組裝起來。如果沒有配置component scan,那麼我們必須在application-config.xml(或等同的配置檔案)定義這個bean。
一般情況下一個介面我們只寫一個實現類,這個時候我們只需要在實現類上註解@service

@Service
public class UserServiceImpl implements UserService {
	……
}

在這種情況下,我們要使用這個實現類的時候也只需要用@Autowired即可 Spring會自動組裝userservice與其實現類UserServiceImpl

@Autowired
private Userservice userservice;

相關文章