Spring 學習筆記①:IoC容器、Bean與注入


收藏這三篇筆記,完整回顧Spring常見問題及使用方式速查:

  1. Spring 學習筆記①:IoC容器、Bean與注入(即本篇)
  2. Spring 學習筆記②:動態代理及面向切面編程
  3. Spring 學習筆記③:JDBC與事務管理

0. 基本概念

  • 控制反轉(IoC):被調用者的實例不再由調用者創建,而是由 Spring 容器創建,這稱爲控制反轉。
  • 依賴注入(DI):Spring 容器在創建被調用者的實例時,會自動將調用者需要的對象實例注入給調用者,這樣,調用者通過 Spring 容器獲得被調用者實例,這稱爲依賴注入。

文檔、JAR包:官方倉庫地址


1. 示例pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <build>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <configuration>
            <source>8</source>
            <target>8</target>
          </configuration>
        </plugin>
      </plugins>
    </build>

    <groupId>test</groupId>
    <artifactId>test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!-- spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>

2. IOC容器

二者相比,前者功能簡單且沒有初始化自檢。

2.1 BeanFactory(不常用)

Resource resource = new ClassPathResource("ApplicationContext.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
beanFactory.getBean("$BeanName");

2.2 ApplicationContext(示例)

ApplicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="service0" class="Service">
        <constructor-arg name="name" type="java.lang.String" value="service0:name"/>
    </bean>
        <!-- 構造標籤詳見下 -->
    <bean id="dao0" class="DAO">
        <constructor-arg name="id" type="java.lang.Integer" value="7"/>
        <constructor-arg name="name" type="java.lang.String" value="dao0"/>
        <constructor-arg name="service" type="Service" ref="service0"/> <!-- 實例依賴-->
    </bean>
</beans>

Service.java

@Data  // 使用了 lombok
@AllArgsConstructor
public class Service {
    private String name;
}

DAO.java

@Data
@AllArgsConstructor
public class DAO {
    private Integer id;
    private String name;
    private Service service; // 依賴Service,採用`ref`
}

測試代碼:

public class Main {
    public static void main(String[] args){
        ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        DAO a = (DAO)context.getBean("dao0");
        System.out.println(a);
    }
}

目錄結構爲:

2.3 通過註解進行裝配

通過註解進行裝配,編寫方便、無需寫xml標籤文件,但若有更改需要重編譯。

2.3.1 常用註解

  1. @Component :可以使用此註解描述 Spring 中的 Bean,但它是一個泛化的概念,僅僅表示一個組件(Bean),並且可以作用在任何層次。使用時只需將該註解標註在相應類上即可;將會將其實例化和注入依賴並加入IoC容器。
  2. @Repository :用於將數據訪問層(DAO層)的類標識爲 Spring 中的 Bean,其功能與 @Component 相同。
  3. @Service :通常作用在業務層(Service 層),用於將業務層的類標識爲 Spring 中的 Bean,其功能與 @Component 相同。
  4. @Controller :通常作用在控制層(如 Struts2 的 Action),用於將控制層的類標識爲 Spring 中的 Bean,其功能與 @Component 相同。
  5. @Autowired :用於對 Bean 的屬性變量、屬性的 Set 方法及構造函數進行標註,配合對應的註解處理器完成 Bean 的自動配置工作。默認按照 Bean 的類型進行裝配。
  6. @Resource :其作用與 Autowired 一樣。其區別在於 @Autowired 默認按照 Bean 類型裝配,而 @Resource 默認按照 Bean 實例名稱進行裝配。
  7. @Qualifier :與 @Autowired 註解配合使用,會將默認的按 Bean 類型裝配修改爲按 Bean 的實例名稱裝配,Bean 的實例名稱由 @Qualifier 註解的參數指定。

附 · 使用JacaConfig類配置Bean所用到的註解:

  • @Configuation 等價於 <beans></beans> ,不可用於標註 final 、匿名類,必須爲靜態類。
  • @Bean 等價於 <bean></bean>
  • @ComponentScan 等價於 <context:component-scan base-package="com.dxz.demo"/>

2.3.2 示例:使用註解裝配的MVC

目錄結構:

TIM截圖20200609151507.png

此處可以選擇構建一個配置類以取代 ApplicationContext.xml

@Configuration // 該註解同樣也需要被掃描到,因此同樣要在xml文件中進行配置
// @ComponentScan("MVC") // 等價於<context:component-scan base-package="MVC"/> //由於目錄結構中該配置類與MVC同包,會被xml配置直接掃描到,因此此處不需要。
public class SpringConfiguration {
    // 等價於
    // <bean id="user0" class="xxx.User">
    //  <constructor-arg name="id" type="java.lang.Integer" value="0"></constructor-arg>
    //     <constructor-arg name="name" type="java.lang.String" value="Jack"></constructor-arg>
    // </bean>
    @Bean(name = "user0")
    public User UserBeanJack(){
        return new User(0, "Jack");
    }
}

構建一個控制器:

@Controller
public class UserController {
    @Resource // 此處使用Resource,默認採用首字母小寫的形式去找到類, 等價於 getBean("userService")
    private UserService userService;

    public void acceptRequest(ApplicationContext context, Object request){
        System.out.println("Accept request.");
        User user = (User)context.getBean("user0"); // 假設根據Request構建Model
        System.out.println("Controller find user: " + user);
        this.userService.service(user); // 找到Model對應的業務
        System.out.println("MVC Controller Over.");
    }
}

構建Model層的各個組件:,首先是業務邏輯:

@Service("userService") // 默認是類名首字母小寫, 等價於 <bean id="userService" class="xxx.UserService"/>
public class UserService {
    private UserDao userDao;

    @Autowired // 此處使用自動裝配的形式
    public UserService(UserDao userDao){
        this.userDao = userDao;
    }

    public void service(User user){
        System.out.println("MVC Service sth. with " + user);
        this.userDao.save(user);
        System.out.println("MVC Service Over.");
    }
}

數據持久層:

@Repository("userDao") // 等價於 <bean id="userDao" class="xxx.UserDao"/>
public class UserDao {
    public void save(User user){
        System.out.println("數據庫已保存" + user);
    }
}

一個POJO類:

@Data // 使用了lombok簡化代碼編寫
@AllArgsConstructor
public class User {
    private Integer id;
    private String name;
}

最後,還需要讓Spring掃描到這些註解:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
        <!-- 增加第3, 8, 9行-->
    <context:component-scan base-package="MVC"/>
</beans>

測試:

public class Main {

    public static void main(String[] args){
        final ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        UserController userController = (UserController)context.getBean("userController");
        // 注意不能用 UserController userController = new UserController();
        userController.acceptRequest(context, null);
    }
}
/*打印結果如下:
Accept request.
Controller find user: User(id=0, name=Jack)
MVC Service sth. with User(id=0, name=Jack)
數據庫已保存User(id=0, name=Jack)
MVC Service Over.
MVC Controller Over.*/

  • 需要注意的是,所有的實例都要從IoC容器裏獲取,自行new出來的實例不會被Spring注入,會導致Null異常。

2.3.3 示例所使用的流程圖

image.png

2.4 往容器內配置Bean的三種方式

  1. 使用xml進行配置,bean的名字爲 id 字段。
  2. 使用JavaConfig進行配置( @Configuration ),默認bean的名字爲 @Bean 所標註的方法名。
  3. 使用註解進行配置,默認Bean的名字爲類名(首字母小寫)。

2.5 @Autowired 和 @Resource 的區別

  • 前者默認按類型裝配,當容器內存在多種同一類型的Bean時,需要使用 @Qualifier(value = "$BeanName") 纔可以找到被注入的對象。
  • 後者在不指定 name 字段時,也是以同一類型的Bean進行注入,若存在多個同類型對象,會報錯;當指定 name 字段時等同於 getBean("$BeanName") (某些框架中,找不到會再根據類型尋找)。
  • 二者都可以標註在 setter() 或構造器方法上; @Autowired 不被推薦在字段上直接標註(缺點見下文)。

2.5.1 注入方式及字段注入的缺點

對於二者,注入方式都有三種:構造器注入、setter注入、field注入,區別如下:

public class UserController {
    @Autowired // field注入,也稱爲反射注入或字段注入,對訪問級別無要求
    private UserService userService;
    
    @Autowired // 構造器注入(需要爲public) 適用於強制依賴項
    public UserController(UserService userService){
        this.userService = userService;
    }
    
    @Autowired // setter注入(需要爲public) 適用於可選依賴項
    public void setUserService(UserService userService){
        this.userService = userService;
    }
}

使用字段注入,其最大缺點爲:

  1. 類會和Spring強耦合,由於無構造器或setter方法,無法在容器外使用。
  2. 無法使用屬性注入的方式構建不可變對象。
  3. 同第一小點,在測試時無法脫離容器進行測試,即 new 出來的 Mock 對象無法被引入測試單元。
  4. 強依賴和可選依賴都被隱藏,無法區分,違背單一職責原則。

3. Bean的常見概念

3.1 Bean的常用屬性

屬性名稱 描述
id 是一個 Bean 的唯一標識符,Spring 容器對 Bean 的配置和管理都通過該屬性完成
name Spring 容器同樣可以通過此屬性對容器中的 Bean 進行配置和管理,name 屬性中可以爲 Bean 指定多個名稱,每個名稱之間用逗號或分號隔開
class 該屬性指定了 Bean 的具體實現類,它必須是一個完整的類名,使用類的全限定名
scope 用於設定 Bean 實例的作用域,其屬性值有 singleton(單例)、prototype(原型)、request、session 和 global Session。其【默認值】是 singleton。
property 元素的子元素,用於調用 Bean 實例中的 Set 方法完成屬性賦值,從而完成依賴注入。該元素的 name 屬性指定 Bean 實例中的相應屬性名
constructor-arg 元素的子元素,可以使用此元素傳入構造參數進行實例化。該元素的 index 屬性指定構造參數的序號(從 0 開始),type 屬性指定構造參數的類型
constructor-arg:name/index 標定一個bean的屬性名稱/構造函數的第index個參數,用於初始化實例
ref/value 和 等元素的子元素,用於直接指定一個引用/常量值;亦可作爲初始化時注入集合類元素的標籤
list 用於封裝 List 或數組類型的依賴注入
set 用於封裝 Set 或數組類型的依賴注入
map 用於封裝 Map 或數組類型的依賴注入
entry 元素的子元素,用於設置一個鍵值對。其 key 屬性指定字符串類型的鍵值,ref 或 value 子元素指定其值
factory-bean 當class是一個工廠類時,指定其作爲工廠
factory-method 當class是一個工廠類時,使用該標籤標註的靜態方法(或若已指定一個factory-bean標註的實例)得到實例
autowire 是否自動裝配,詳見下文表格

3.1.1 自動裝配

屬性取值 描述
byName 根據 Property 的 name 自動裝配,如果一個 Bean 的 name 和另一個 Bean 中的 Property 的 name 相同,則自動裝配這個 Bean 到 Property 中。
byType 根據 Property 的數據類型(Type)自動裝配,如果一個 Bean 的數據類型兼容另一個 Bean 中 Property 的數據類型,則自動裝配。
constructor 根據構造方法的參數的數據類型,進行 byType 模式的自動裝配。
autodetect 如果發現默認的構造方法,則用 constructor 模式,否則用 byType 模式。
no 默認】情況下,不使用自動裝配,Bean 依賴必須通過 ref 元素定義。

3.2 Bean的作用域

Spring 容器在初始化一個 Bean 的實例時,同時會指定該實例的作用域。Spring3 爲 Bean 定義了五種作用域,具體如下。

  1. singleton :單例模式,使用 singleton 定義的 Bean 在 Spring 容器中只有一個實例,這也是 Bean 【默認】的作用域。
  2. prototype :原型模式,每次通過 Spring 容器獲取 prototype 定義的 Bean 時,容器都將創建一個新的 Bean 實例。
  3. request :在一次 HTTP 請求中,容器會返回該 Bean 的同一個實例。而對不同的 HTTP 請求,會返回不同的實例,該作用域僅在當前 HTTP Request 內有效。
  4. session :在一次 HTTP Session 中,容器會返回該 Bean 的同一個實例。而對不同的 HTTP 請求,會返回不同的實例,該作用域僅在當前 HTTP Session 內有效。
  5. global Session :在一個全局的 HTTP Session 中,容器會返回該 Bean 的同一個實例。該作用域僅在使用 portlet context 時有效。

3.3 Bean的生命週期

5-1ZF1100325116.png

  1. 根據配置情況調用 Bean 構造方法或工廠方法實例化 Bean,包含以下四個預處理項目:
  • ResouceLoader 加載配置信息;
  • BeanDefintionReader 解析配置信息,生成若干個 BeanDefintion
  • BeanDefintionBeanDefintionRegistry管理起來;
  • BeanFactoryPostProcessor 對配置信息進行加工(也就是處理配置的信息,一般通過PropertyPlaceholderConfigurer來實現);
  1. 利用依賴注入完成 Bean 中所有屬性值的配置注入。
  2. 如果 Bean 實現了 BeanNameAware 接口,則 Spring 調用 Bean 的 setBeanName() 方法傳入當前 Bean 的 id 值。
  3. 如果 Bean 實現了 BeanFactoryAware 接口,則 Spring 調用 setBeanFactory() 方法傳入當前工廠實例的引用。
  4. 如果 Bean 實現了 ApplicationContextAware 接口,則 Spring 調用 setApplicationContext() 方法傳入當前 ApplicationContext 實例的引用。
  5. 如果 BeanPostProcessor (BBP) 和 Bean 關聯,則 Spring 將調用該接口的預初始化方法 postProcessBeforeInitialzation() 對 Bean 進行加工操作,此處非常重要,Spring 的 AOP 就是利用它實現的。
  6. 如果 Bean 實現了 InitializingBean 接口,則 Spring 將調用 afterPropertiesSet() 方法。
  7. 如果在配置文件中通過 init-method 屬性指定了初始化方法,則調用該初始化方法。
  8. 如果 BeanPostProcessor 和 Bean 關聯,則 Spring 將調用該接口的初始化方法 postProcessAfterInitialization() 。此時,Bean 已經可以被應用系統使用了。
  9. 如果在 <bean> 中指定了該 Bean 的作用範圍爲 scope="singleton" ,則將該 Bean 放入 Spring IoC 的緩存池中,將觸發 Spring 對該 Bean 的生命週期管理;如果在 <bean> 中指定了該 Bean 的作用範圍爲 scope="prototype" ,則將該 Bean 交給調用者,調用者管理該 Bean 的生命週期,Spring 不再管理該 Bean。
  10. 如果 Bean 實現了 DisposableBean 接口,則 Spring 會調用 destory() 方法將 Spring 中的 Bean 銷燬;如果在配置文件中通過 destory-method 屬性指定了 Bean 的銷燬方法,則 Spring 將調用該方法對 Bean 進行銷燬。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章