Spring(二)-生命週期 + 自動裝配(xml) +自動裝配(註解)

語言: CN / TW / HK

1、生命週期

**Spring容器的 bean **的生命週期;

1.1 默認生命週期

1.1.1 生命週期

  1. 調用構造方法,創建實例對象;
  2. set方法,給實例對象賦值;
  3. init 初始化方法 初始化對象;(手寫並配置到bean上init-method="")
  4. 使用容器中的bean對象;
  5. destroy 銷燬方法 銷燬對象 (手寫並配置到bean上destroy-method="")

1.1.2 bean 實體類

Truck

@Data
@ToString
public class Truck {

    //品牌
    private String brand;
	//廠商
    private String factory;
	//價格
    private Double price;

    public Truck() {
        //空參構造方法,觀察bean什麼時候實例化
        System.out.println("------ 1.調用構造方法,創建實例對象 ------\n");
    }

    public void setBrand(String brand) {
        //任意一個set方法,觀察bean什麼時候注入參數
        System.out.println("------ 2.set方法,給實例對象賦值 ------");
        this.brand = brand;
    }


    public  void  initTruck(){
        //init初始化方法,觀察bean什麼時候初始化
        //需要再配置bean的時候,配置init初始化方法
        System.out.println("------ 3.Truck init 初始化方法 初始化對象 ------\n");
        this.brand = "大運";
    }

    public  void  destroyTruck(){
        //destory方法,觀察bean什麼時候銷燬
        //需要再配置bean的時候,配置destory銷燬方法
        System.out.println("------ 5.Truck destroy 銷燬方法 銷燬對象 ------\n");
    }
	//這裏方法上標註的序號是測試後得來的;
}

1.1.3 bean 配置

spring-lifecycle.xml

<!-- spring容器中bean的生命週期  默認生命週期 -->
<bean id="truck" class="com.kgc.spring.lifecycle.Truck" init-method="initTruck" destroy-method="destroyTruck">
    <property name="brand" value="江淮"></property>
    <property name="factory" value="安徽"></property>
    <property name="price" value="200000"></property>
</bean>

1.1.4 測試

public class TestSpringLifeCycle {

    //定義全局容器對象,如果需要關閉容器對象,
    //必須使用ApplicationContext的子接口 ConfigurableApplicationContext
    //ApplicationContext接口主要各種屬性的get方法;
    //ConfigurableApplicationContext重在對各種屬性的配置;
    
//    private ApplicationContext context;
    private ConfigurableApplicationContext context;

    @Before
    public void initApplicationContext(){
        context = new ClassPathXmlApplicationContext("spring-lifecycle.xml");
    }

    //測試spring 容器的bean的生命週期,默認和加了處理器兩種場景
    @Test
    public void  testSpringBeanLifeCycle(){

        //從容器中,獲取Truck的是實例對象
        Truck truck = context.getBean("truck", Truck.class);

        //使用對象
        System.out.println("------ 4.使用容器中的bean對象"+truck +" ------");

        //關閉容器
        context.close();

    }

}

輸出結果:

//可以得出 spring中bean的 默認生命週期
------ 1.調用構造方法,創建實例對象 ------

------ 2.set方法,給實例對象賦值 ------
    
------ 3.Truck init 初始化方法 初始化對象 ------
    
------ 4.使用容器中的bean對象Truck(brand=大運, factory=安徽, price=200000.0) ------
    
------ 5.Truck destroy 銷燬方法 銷燬對象 ------

1.1.5 ApplicationContext 和 ConfigurableApplicationContext

參考博客: ApplicationContext和ConfigurableApplicationContext解析

  • ApplicationContext接口主要 各種 屬性的get方法 ;

  • ConfigurableApplicationContext重在對 各種 屬性的配置 ;

1.2 增加後置處理器

1.2.1 生命週期

​1.調用構造方法,創建實例對象;

​2.set方法,給實例對象賦值;

​3-1.後置處理的 before 方法;

​3.初始化方法 初始化對象;

​3+1.後置處理器的的 after 方法;

​4.使用容器中的bean對象;

​5.destroy 銷燬方法 銷燬對象;

1.2.2 後置處理器

  • 要求:必須實現 BeanPostProcessor 接口

  • 自定義 bean 的 後置處理器,對容器中 所有的bean統一處理(生效)

  • 要生效的話,必須將此處理器放到容器中( 配置到spring的核心配置文件中 ,增加處理器的實例配置);

注意: 當前案例, 只對 容器中的 一個實例處理

MyBeanPostProcessor

public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //在容器中bean的實例對象調用 初始化方法 前 自動調用(init方法可以沒有,不影響)
        //模擬處理容器中的bean,接下來的寫法,僅限於當前的用法案例(容器中就 只有一個 卡車實例)
        Truck truck = (Truck)bean;

        System.out.println("++++++ 容器中的卡車對象 "+truck+"++++++");

        System.out.println("++++++ 3-1,後置處理的 before 方法 ++++++");
        truck.setBrand("後置處理的before方法");
        System.out.println("++++++ 處理後的 卡車對象 "+truck+" ++++++\n");

        return truck;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //bean 初始化方法 執行 後,調用此方法處理bean
        Truck truck = (Truck)bean;

        System.out.println("++++++ 初始化容器中的卡車對象 "+truck+"++++++");

        System.out.println("++++++ 3+1,後置處理器的的 after 方法 ++++++");
        truck.setBrand("after");
        System.out.println("++++++ 初始化後 處理後的 卡車對象 "+truck+" ++++++\n");
        return truck;

    }
}

1.2.3 bean 配置

在配置文件中配置 MyBeanPostProcessor;

<!-- 配置後置處理器的實例,自動放入容器中,可以自動生效 (容器中所有的實例生效) -->
<bean class="com.kgc.spring.lifecycle.MyBeanPostProcessor"></bean>

1.2.4 測試

跟默認生命週期的測試代碼一致;

輸出結果:

------ 1.調用構造方法,創建實例對象 ------

------ 2.set方法,給實例對象賦值 ------
++++++ 容器中的卡車對象 Truck(brand=江淮, factory=安徽, price=200000.0)++++++
    
++++++ 3-1,後置處理的 before 方法 ++++++ ------ 2.set方法,給實例對象賦值 ------
++++++ 處理後的 卡車對象 Truck(brand=後置處理的before方法, factory=安徽, price=200000.0) ++++++

------ 3.Truck init 初始化方法 初始化對象 ------
++++++ 初始化容器中的卡車對象 Truck(brand=大運, factory=安徽, price=200000.0)++++++
    
++++++ 3+1,後置處理器的的 after 方法 ++++++ ------ 2.set方法,給實例對象賦值 ------
++++++ 初始化後 處理後的 卡車對象 Truck(brand=after, factory=安徽, price=200000.0) ++++++

------ 4.使用容器中的bean對象Truck(brand=after, factory=安徽, 
                           
------ 5.Truck destroy 銷燬方法 銷燬對象 ------

1.2.3 BeanPostProcesso

參考博客: BeanPostProcessor簡介

BeanPostProcessor官方定義為工廠鈎子,我們也俗稱 後置處理器 。它 允許自定義修改新的bean實例 ,例如檢查標記接口或用代理包裝它們。應用程序上下文可以在其bean定義中自動檢測BeanPostProcessor bean,並將它們應用於隨後創建的任何bean。

BeanPostProcessor 的 前置處理後置處理

![image-20220826150821502](Spring-02 生命週期 + 自動裝配(xml) +自動裝配(註解).assets/image-20220826150821502.png)

2、自動裝配(xml)

2.1 bean 實體類

Person

@Data
public class Person {
    //暱稱
    private String nickName;
    //車子
    private Car car;
    //房子
    private House house;
}

Car

@Data
public class Car {
    //品牌
    private String brand;
	//廠商
    private String factory;
	//價格
    private Double price;
}

House

@Data
public class House {
    //户型
    private  String type;
    //面積
    private double area;
    //價格
    private  Integer price;
}

2.2 bean 配置 (byType)

autowire="byType":根據 屬性 的 類型 自動裝配;

spring-auto.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">

    <!-- spring的自動裝配方式,基於xml配置文件方式,掌握 -->
    <!-- 容器中實例化一個容器的Car對象 -->
    <bean id="car" class="com.kgc.spring.auto.Car">
        <property name="brand" value="Bnw520"></property>
        <property name="factory" value="華晨"></property>
        <property name="price" value="450000"></property>
    </bean>

    <!-- 容器中實例化一個容器的House對象 -->
    <bean id="house" class="com.kgc.spring.auto.House">
        <property name="type" value="三室一廳"></property>
        <property name="area" value="96"></property>
        <property name="price" value="2800000"></property>
    </bean>

    <!-- 根據類型自動裝配 -->
    <bean id="person" class="com.kgc.spring.auto.Person" autowire="byType">
        <property name="nickName" value="huayu"></property>
    </bean>

</beans>

2.3 測試

public class TestSpringAutoUserXml {
    private ApplicationContext context;

    @Before
    public void initApplicationContext(){
        context = new ClassPathXmlApplicationContext("spring-auto.xml");
    }

    @Test
    public  void testSpringAuto(){
        Person person = context.getBean("person", Person.class);
        
        //使用對象
        System.out.println("容器中的person對象:"+person);

    }

}

輸出結果:

容器中的person對象:Person(
    					nickName=huayu, 
                        car=Car(brand=Bnw520, factory=華晨, price=450000.0), 
                        house=House(type=三室一廳, area=96.0, price=2800000)
						)

2.4 bean 配置 (多個同類型bean)

bean 配置:

其他不變,多增加一個Car類型的實例bean;

<bean id="car" class="com.kgc.spring.auto.Car">
    <property name="brand" value="Bnw520"></property>
    <property name="factory" value="華晨"></property>
    <property name="price" value="450000"></property>
</bean>

<bean id="carNew" class="com.kgc.spring.auto.Car">
    <property name="brand" value="AudiA6"></property>
    <property name="factory" value="一汽"></property>
    <property name="price" value="450000"></property>
</bean>

測試,報錯信息:

No qualifying bean of type 'com.kgc.spring.auto.Car' available: expected single matching bean but found 2: car,carNew

總結 :autowire="byType" 當有 多個相同類型的bean時無法確定 要裝配的 bean;

2.5 bean 配置(byName)

其他配置信息不變,設置 autowire="byName" ,根據 屬性 的 名字 自動裝配;

<bean id="person" class="com.kgc.spring.auto.Person" autowire="byName">
    <property name="nickName" value="hauyu"></property>
</bean>

測試輸出結果:

容器中的person對象:Person(
    					nickName=huayu, 
                        car=Car(brand=Bnw520, factory=華晨, price=450000.0), 
                        house=House(type=三室一廳, area=96.0, price=2800000)
						)

總結

  • byType:根據類型自動裝配:
    • 根據實體屬性的  類型 ,到容器中,根據  bean類型  進行唯一匹配,如果可以匹配到對應類型的bean的實例,就會執行自動裝配, 如果不能唯一匹配(同類型的bean有多個),會報錯;
  • byName: 根據名稱自動裝配:
    • 根據 屬性屬性名 ,到容器中,根據 bean的id 屬性值,進行唯一匹配,如果能夠成功匹配,執行自動裝配, 如果匹配不到,不執行自動裝配,實體屬性為null;

3、自動裝配 (註解)

3.1 註解

  • @Component 普通組件註解;
  • @Repository 持久層註解
  • @Service 業務層註解
  • @Controller 控制層註解

3.3.1 註解的原理

默認情況下:spring自動將分層組件(@Controller,@Service,@Repository,@component) 標識的類 (不是接口), 自動創建實例對象放入容器 中,使用bean的標識 id值為 對應 類名首字母小寫 就相當於,幫我們手動添加了配置 :

<bean id="分層註解標識類的類名首字母小寫" class="分層註解標識類的全類名"> ... <bean>

3.1.2 自定義id 屬性

如果 不想使用默認的類名首字母小寫 ,我們可以使用註解的value屬性執行一個自定義的id值 ;

比如:@Service(value="自定義的id值"),註解只有value屬性,可以省略value執行,簡化為@Service("自定義的id值")

3.1.3 分層組件的目的

分層組件的目的,就僅僅是為了 方便開發人員 明確當前註解 所在的類所對應的角色 ,在使用上, 建議使用 ,按照官方定義的使用,防止模糊不清; 在springMVC框架中@Controller有特殊含義;

3.2 配置文件

spring創建容器對象時,如果解析到 component-scan 組件掃描配置,會將base-package指定的基包(父包)及其子包所有增加了分層組件的類,自動創建實例,放進容器中;

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
">
 <!-- 組件掃描:註解標識的組件,必須通過組件掃描配置,才可以添加到spring的容器中--> 
 <context:component-scan base-package="com.kgc.spring.acnocation" >    </context:component-scan>

</beans>

3.3 默認id屬性 測試

3.3.1 實體

@Component
public class User {
    //用户名
    @Value("huayu") //@Value()  自動裝配參數
    private  String userName;
    //用户密碼
    @Value("123")
    private String userPwd;
}

3.3.2 測試

@Test
public  void testSpringAutoUserAnnotation(){
	
    User user = context.getBean("user", User.class);
   
    System.out.println(user);
    //User(userName=huayu, userPwd=123)

}

3.4 自定義id屬性 測試

3.4.1 實體(自定義id屬性)

@Component(value = "myUser")
public class User {
    //用户名
    private  String userName;
    //用户密碼
    private String userPwd;
}

3.4.2 測試

@Test
public  void testSpringAutoUserAnnotation(){

    //User user = context.getBean("user", User.class);
    //自定義id後,默認id不能使用: No bean named 'user' available
    
    //必須使用自定義id
    User user = context.getBean("myUser", User.class);
    
    System.out.println(user);
    //User(userName=huayu, userPwd=123)

}

3.5 自動裝配

3.5.1 @Autowired

  • 組件自動裝配,可以實現 實體屬性類型的自動裝配 ,自動到spring的容器中,根據當前 屬性的類型或者名稱進行注入 ,如果容器中 能匹配到 ,就 直接 將實例對象 注入到當前實體屬性上 ,無序手動指定;
  • @Autowired自動裝配原理:首先會根據byType方式,進行自動裝配,
    • 如果 不能唯一匹配 (存在同類型多個實例對象),會再次 嘗試使用byName方式 ,根據當前實體屬性名,到容器中進行匹配(容器中bean的id值),如果能唯一匹配,直接執行自動裝配,
  • 默認情況下,@Autowired註解標識的實體屬性,必須被裝配
    • 如果 裝配失敗 ,就直接 拋出異常;
    • 如果 不需要校驗必須被裝配 (項目啟動,如果裝配失敗,項目是起不來);
    • 通過指定 required = false ,去除必須執行自動裝配的校驗(即便容器中找不到裝配的實例,也不會拋出異常);
  • 如果自動裝配,容器中 存在多個同類型的bean對象 ,可以使用註解@Qualifier("容器中同類型多個bean的某個id值"), 實現指定到容器中,找對應的bean實例對象 ,進行自動裝配;
  • 底層是如何做的:在指定要掃描的包時,<context:component-scan> 元素會 自動註冊一個bean的後置處理器 :AutowiredAnnotationBeanPostProcessor的實例。該後置處理器可以 自動裝配標記了@Autowired、@Resource或@Inject註解的屬性

3.5.2 實體

People

@Data
@Component("myPeople")
public class People {
    //暱稱
    @Value("huayu")
    private String name;
    //玩具
    @Autowired
    private Toy toy;

}

Toy接口

public interface Toy {
    //得到玩具
    public void getToy();

}

ToyImpl1

@Component("toy1")
public class ToyImpl1 implements Toy {

    @Value("玩具車")
    private String toyName;

    @Override
    public void getToy() {
        System.out.println(this.toyName);
    }
}

3.5.3 測試

注意 :可以 通過接口類型獲取實現類 (推薦使用);

@Test
public  void testAutowired  (){
    People people = context.getBean("myPeople", People.class);

    people.getToy().getToy(); //玩具車

}

3.5.4 存在多個相同類型的bean

當存在 多個相同類型的bean不能唯一匹配 ,會 自動裝配錯誤

在寫一個Toy實現類,ToyImpl2

@Component("toy2")
public class ToyImpl2 implements Toy {

    @Value("尤克里裏")
    private String toyName;

    @Override
    public void getToy() {
        System.out.println(this.toyName);
    }

}

3.5.4.1 測試

報錯信息(項目無法啟動):

No qualifying bean of type 'com.kgc.spring.acnocation.bean.Toy' available: expected single matching bean but found 2: toy1,toy2

主要信息:類型無法唯一匹配;

3.5.4.2 required = false 允許不裝配

People

@Data
@Component("myPeople")
public class People {

    //暱稱
    @Value("huayu")
    private String name;

    //玩具
    @Autowired (required = false)
    private Toy toy;

}

項目可以啟動但是還是報錯(一般項目中不會有兩個相同類型的實現類;)

3.5.4.3 @Quailfy

People

@Data
@Component("myPeople")
public class People {

    //暱稱
    @Value("huayu")
    private String name;

    //玩具
    @Autowired 
    @Qualifier("toy2") //指定bean的id值
    private Toy toy;

}

3.5.4.4 測試

@Test
public  void testAutowired  (){
    People people = context.getBean("myPeople", People.class);

    System.out.println(people.getToy());
    //com.kgc.spring.acnocation.bean.ToyImpl2@15d9bc04
    
    people.getToy().getToy();
    //尤克里裏

}

3.6 指定掃描 排除掃描

3.6.1 指定掃描

include-filter

指定掃描(包含掃描):

  • 只會掃描指定的 或者 某類組件 (使用分組掃描), 加入 到容器中;
  • 但是 必須配合 父標籤的 user-default-filter 使用,默認值是true,就是全部掃描;
  • 指定掃描,如果要生效 必須改為false
  • 指定掃描 某類組件 ,type="annotation" expression="某類組件註解的全類名";
  • 指定掃描 某個類 ,type="assignable" expression="某個類的全類名";

3.6.1.1 指定掃描某類組件

type="annotation"

  • org.springframework.stereotype.Component
  • org.springframework.stereotype.Repository
  • org.springframework.stereotype.Service
  • org.springframework.stereotype.Controller
<!-- 指定掃描 @Component 組件 -->
<context:component-scan base-package="com.kgc.spring.acnocation" use-default-filters="false" >
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
</context:component-scan>

3.6.1.2 指定掃描某個類

type="assignable"

<!-- 指定掃描 ToyImpl1 -->
<context:component-scan base-package="com.kgc.spring.acnocation" use-default-filters="false" >
    <context:include-filter type="assignable" expression="com.kgc.spring.acnocation.bean.ToyImpl1"/>
</context:component-scan>

3.6.2 排除掃描

exclude-filter

  • 排除掃描(剔除掃描):排除指定的 某類組件不加入 到容器中,處理排除外的其他組件,仍然會被添加到容器中;
  • 不需要配合父標籤,use-default-filters="true" 因為,默認就是在全部掃描的基礎上剔除;
  • 排除掃描 某類組件 ,type="annotation" expression="某類組件註解的全類名";
  • 排除掃描 某個類 ,type="assignable" expression="某個類的全類名";

3.6.2.1 排除掃描某類組件

type="annotation"

<!-- use-default-filters="true" 可寫可不寫 -->
<!-- 排除掃描 @Component組件 -->
<context:component-scan base-package="com.kgc.spring.acnocation" use-default-filters="true" >
	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/>
</context:component-scan>

3.6.2.2 排除掃描個類

type="assignable"

<!-- 排除掃描 ToyImpl1 -->
<context:component-scan base-package="com.kgc.spring.acnocation" >
	<context:exclude-filter type="assignable" expression="com.kgc.spring.acnocation.bean.ToyImpl1"/>
</context:component-scan>