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

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="//www.springframework.org/schema/beans"
       xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="//www.springframework.org/schema/beans //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="//www.w3.org/2001/XMLSchema-instance"
       xmlns="//www.springframework.org/schema/beans"
       xmlns:context="//www.springframework.org/schema/context"
       xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd
                           //www.springframework.org/schema/context //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>
Tags: