spring-framework-core-iocContainer


  1. xml方式實例化bean

  2. 注解方式實例化bean

  3. java方式實例化bean

  4. ClassPathXmlApplication和AnnotationConfigApplicationContext

  5. Bean存放到了哪里?

  6. 選取哪一種方式合適?

  7. bean的名稱

    1. 默認的名稱

    2. 設置名稱

    3. 設置別名

  8. 延遲加載

  9. bean加載順序

  10. bean范圍

  11. bean的回調函數

  12. 組件掃描

    1. @Component @Controller @Service@Configuration

    2. @Bean@Configuration精簡模式和完全模式

    3. 開啟組件掃描

    4. 默認的掃描路徑

    5. 掃描過濾

  13. 內部類實例化

  14. 靜態工廠方法

  15. 實例工廠方法

  16. BeanPostProcessor

  17. BeanFactoryPostProcessor

  1. bean的繼承

  2. 多個配置間互相拾取

    1. @Import java配置互相拾取

    2. <import> xml文件互相拾取

    3. @ImportResource java配置拾取xml

    4. <context:component-scan/> xml注解掃描

  3. spring5.0生成候選組件的索引

  4. 國際化

  5. @Profile環境抽象

  6. 事件監聽

  7. ApplicationContext的父子關系

閱讀須知

1 本文核心內容來自 https://docs.spring.io/spring/docs/5.2.1.RELEASE/spring-framework-reference/core.html

2 在我自己閱讀文檔並寫出test后,為了提高本文的可讀性和連續性,我決定本文不按照spring文檔目錄順序排版。將IOC部分拆分為 實例化bean,為bean注入屬性,spring的其他特性三個方面。

3 文章中所包含的test都經過測試。git地址為 https://gitee.com/zumengjie/springcode-core-ioc

4 文章中對於spring的使用方式和使用技巧,僅代表我個人。如有錯誤,或不足,請留言。

5 文章環境為jdk8,springboot2.2.2.RELEASE

6 文章適用於使用過spring系列框架的朋友,或者准備學習spring的朋友。

7 最后,這篇博客從第一遍看spring文檔到手稿,到整理,三周,一個個test敲出來。對我自身是有很大提升的,spring對於ioc部分的處理十分細膩,本文也只是根據我所看到的整理出來了。在原文中,還有很多東西沒有被我挖掘出來。我覺得對於每一個學spring用spring的人來講都應該去看一遍官方文檔。在我看文檔前,我擔心自己英語不好,看不懂。在一篇博客上,有個前輩說,你看不懂的不是英文文檔,而是文檔。確實,一篇文檔,如果沒有一點根基,就算他是中文的,我們也不一定看一遍就懂,看一遍就記住。spring官方文檔,值得我們耐着性子看,然后自己寫test。只看文檔和邊看邊寫還是有很大差距的。下一章資源,會在后續更新。

實例化bean

1 xml方式實例化bean

<?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">
    <!-- class:哪個bean需要被實例化,就用哪個bean的路徑
         id:告訴spring被實例化這個bean叫什么名字-->
    <bean class="com.datang.springcode.xmlRegisterBean.Computer" id="computer"></bean>
</beans>
View Code

2 注解方式實例化bean

package com.datang.springcode.annoRegisterBean;
import org.springframework.stereotype.Component;

//@Component標識這個類,需要被實例化
@Component
public class Pear {
}
View Code

3 java方式實例化bean

package com.datang.springcode.javaRegisterBean;

//需要被實例化的bean
public class Banana {
}
View Code
package com.datang.springcode.javaRegisterBean;


import org.springframework.context.annotation.Bean;
//注意:此處RegisterBeanConfig類上我並未使用@Configuration注解,是因為我使用的AnnotationConfigApplicationContext對象時,
// 傳遞的是RegisterBeanConfig.class。否則,RegisterBeanConfig類必須也要使用xml方式或@Component配置成bean。
public class RegisterBeanConfig {

    //這個注解才是真正的java實例化bean的關鍵,可以指定多個value值,第一個往后,為別名
    @Bean(value = {"banana", "banana2", "banana3"})
    public Banana getBanana() {
        return new Banana();
    }
}
View Code

4 ClassPathXmlApplication和AnnotationConfigApplicationContext

    @Test
    public void t1() {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:xmlRegisterBean.xml");
        Computer computer = context.getBean("computer", Computer.class);
    }

    @Test
    public void t2() {
        ApplicationContext context = new AnnotationConfigApplicationContext(Pear.class);
        Pear bean = context.getBean(Pear.class);

    }

    @Test
    public void t3() {
        ApplicationContext context = new AnnotationConfigApplicationContext(RegisterBeanConfig.class);
        Object getBanana1 = context.getBean("getBanana");
    }
View Code

ClassPathXmlApplicationContext對象用來加載xml配置文件,AnnotationConfigApplicationContext用來加載一個類。spring通過抽象抽取了一個用戶使用的對象ApplicationContext,此次ioc部分我們最常用的方法就是getBean()

ClassPathXmlApplicationContext有多個構造函數重載,這里提一種常見的 ClassPathXmlApplicationContext(String... configLocations)參數接收可變長度的字符串,也就是說,ClassPathXmlApplicationContext可以同時加載多個xml配置文件。

AnnotationConfigApplicationContext也有多個構造函數重載,最常用的是AnnotationConfigApplicationContext(Class<?>... componentClasses)參數接收可變長度的Class類對象,可以同時加載多個配置對象類。

getBean()方法的可重載方式很多,接收一個字符串根據bean的id查詢,接收Class根據類型查詢,還可以同時接收id和class。

5 Bean存放到了哪里?

上邊介紹的三種方式無論哪一種最終都會將pojo實例化到spring容器中。那么我們經常說的spring容器到底是什么?通過debug查看ApplicationContext對象。在spring上下文中有一個beanFactory對象,該對象實現類DefaultListableBeanFactory。依次再找父類AbstractAutowireCapableBeanFactory-->AbstractBeanFactory-->FactoryBeanRegistrySupport-->DefaultSingletonBeanRegistry中有一個Map,registeredSingletons這個Map就是用來

存放所有單例模式的bean。關於bean的模式后邊內容會說到。

6 選取哪一種方式合適?

以上三種實例化bean的方式,無論使用哪一種,最終的bean都會存到到registeredSingletons這個Map中。我們可以三種方式混着用,也可以只用一種方式。使用過springmvc框架的朋友一般都有過這樣的配置。

 <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
 <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>
View Code

ContextLoaderListener實現了ServletContextListener接口,所以能夠監聽Tomcat啟動和停止。而在ContextLoaderListener中則讀取了。classpath:applicationContext.xml。在這個xml中我們是不是了實例化bean,例如dataSource。

而使用springboot的朋友一般極少使用xml實例化的方式,就算有需要配置的也會在application.properties中配置。如果是我們用戶自己的pojo,那使用注解方式實例化bean最方便,如果有一些系統pojo需要統一規范的配置,那就選取xml方式實例化bean,便於管理和修改,而且不容易配破壞,大家都不會隨便修改xml的內容嘛。最后如果你使用的是第三方的jar,那此時使用java的實例化方式是最合適的了,畢竟你也無法在源碼上加@Component注解。

7 bean的名稱

7.1 默認名稱

無論哪種方式實例化bean,被實例化到map中的bean都有一個唯一的名稱。我們通過這個名稱來找到bean,如getBean(),包括在注入屬性時,我們也需要使用到bean的名稱來找到bean。以上test中使用xml實例化bean時,我們使用id屬性來指定bean的名稱,不設置id也有默認的名稱。而使用java方式和注解方式時,我們則沒有指定bean的名稱。為了演示方便我們將xml中實例化bean的id屬性去掉。

package com.datang.springcode.javaRegisterBean;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportResource;
//掃描包下的注解
@ComponentScan(value = "com.datang.springcode")
//加載xml
@ImportResource(value = "classpath:xmlRegisterBean.xml")
public class RegisterBeanConfig {

    //這個注解才是真正的java實例化bean的關鍵。
    @Bean
    public Banana getBanana(){
        return new Banana();
    }
}
View Code

在上邊代碼中,使用@ComponentScan和@ImportResource注解分別掃描包中的注解和加載xml,為的就是我們能在一個Map中看到三個bean。xml方式實例化bean默認的id是類的全路徑拼接#和編號。而@Component注解方式則是使用類名,首字母換成了小寫。使用@Bean方式實例化bean時,bean的名稱是@Bean所注釋的方法名。

7.2 設置名稱

package com.datang.springcode.javaRegisterBean;
import org.springframework.context.annotation.Bean;
public class RegisterBeanConfig {

    //這個注解才是真正的java實例化bean的關鍵。value指定bean的名稱
    @Bean(value = "banana")
    public Banana getBanana(){
        return new Banana();
    }
}
View Code
package com.datang.springcode.annoRegisterBean;
import org.springframework.stereotype.Component;

//@Component標識這個類,需要被實例化.value指定bean的名稱
@Component(value = "pear")
public class Pear {
}
View Code
<?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">
    <!-- class:哪個bean需要被實例化,就用哪個bean的路徑
         id:告訴spring被實例化這個bean叫什么名字-->
    <bean class="com.datang.springcode.xmlRegisterBean.Computer" id="computer"></bean>
</beans>
View Code

通過以上代碼我們給每一個bean設置了名稱。

7.3 設置別名

package com.datang.springcode.javaRegisterBean;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportResource;

//掃描包下的注解
@ComponentScan(value = "com.datang.springcode")
//加載xml
@ImportResource(value = "classpath:xmlRegisterBean.xml")
public class RegisterBeanConfig {

    //這個注解才是真正的java實例化bean的關鍵,可以指定多個value值,第一個往后,為別名
    @Bean(value = {"banana", "banana2", "banana3"})
    public Banana getBanana() {
        return new Banana();
    }
}
View Code
<?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">
    <!-- class:哪個bean需要被實例化,就用哪個bean的路徑
         id:告訴spring被實例化這個bean叫什么名字
         通過name設置別名,可以用 , 號分割-->
    <bean class="com.datang.springcode.xmlRegisterBean.Computer" id="computer" name="computer2,computer3"></bean>
    <!-- alias也可以給bean起別名,但是一個alias標簽僅能指定一個別名 -->
    <alias name="computer" alias="computer4"/>
</beans>
View Code

通過別名也可以獲取到bean。另外別名不可以重復,否則會被替換。在beanFactory下有aliasMap,記錄的就是當前所有bean的別名。@Component注解實例化bean無法設置別名。

8 延遲加載

bean的延遲加載也就是說,在spring上下文啟動時,不會立刻將bean實例化到registeredSingletonsMap中,而是在第一次使用getBean()時才會實例化。

@Component
@Lazy
public class Bookrack {
}
View Code
package com.datang.springcode.lazyBean;
public class Schoolbag {
}
View Code
package com.datang.springcode.lazyBean;
public class Stool {

}
View Code
<?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 class="com.datang.springcode.lazyBean.Stool" id="stool" lazy-init="true"></bean>
</beans>
View Code
package com.datang.springcode.lazyBean;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Lazy;

@ComponentScan(value = "com.datang.springcode.lazyBean")

public class LazyConfig {
    @Bean(value = "schoolbag")
    @Lazy
    public Schoolbag getSchoolbag(){
        return new Schoolbag();
    }
}
View Code

以上代碼通過三種方式實例化bean,並且通過三種方式實行懶加載。結果如下。在spring啟動成功后,並singletonObects中並沒有出現。Bookrack,Schoolbag,Stool三個類的實例對象。

但實際開發中我們又不會直接使用getBean()方法,多數會使用屬性注入。比如@Autowried。如果Abean不是lazy,Bbean是lazy。Abean需要注入Bbean,那么在實例化Abean時也會把Bbean實例化了。

9 bean加載順序

假設一個場景,我們有一個全局配置類sitting,里邊有默認的幾個靜態全局變量。同時我們在程序的各個地方調用通過static.的方式調用。但是,這里的參數有些在項目啟動后需要從數據庫重新取值,對此我們創建了一個專門重新賦值的刷新類,該類初始化過程中,從數據庫取值,然后在重新賦值到sitting類中。在此場景中,散布在程序各處的類和從數據庫取值的刷新類沒有明顯依賴注入關系,但是有弱依賴關系。也就是說,spring必須先把刷新類實例化到Map中,然后才能實例化其他各個業務類。

package com.datang.springcode.depends_on;

import org.springframework.stereotype.Component;

@Component(value = "cap")
public class Cap {
    public Cap(){
        System.out.println("cap");
    }
}
View Code
package com.datang.springcode.depends_on;

import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;

@Component
@DependsOn(value = {"cap"})
public class Clothes {
    public Clothes(){
        System.out.println("clothes:我依賴於cap");
    }
}
View Code
package com.datang.springcode.depends_on;

import org.springframework.stereotype.Component;

@Component(value = "bed")
public class Bed {
    public Bed(){
        System.out.println("bed");
    }
}
View Code
package com.datang.springcode.depends_on;
public class Quilt {
    public Quilt(){
        System.out.println("quilt:我依賴於bed");
    }
}
View Code
<?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 class="com.datang.springcode.depends_on.Quilt" id="quilt" depends-on="bed"></bean>
</beans>
View Code
package com.datang.springcode.depends_on;

public class Phone {
    public Phone() {
        System.out.println("phone:我依賴於charger");
    }
}
View Code
package com.datang.springcode.depends_on;

public class Charger {
    public Charger() {
        System.out.println("charger");
    }
}
View Code
package com.datang.springcode.depends_on;

import org.springframework.context.annotation.*;

@Configuration
@ImportResource(value = {"classpath:depends_on.properties.xml"})
@ComponentScan(value = {"com.datang.springcode.depends_on"})
public class DependsOnConfig {

    @Bean(value = {"phone"})
    @DependsOn(value = {"charger"})
    public Phone getPhone() {
        return new Phone();
    }

    @Bean(value = {"charger"})
    public Charger getCharger() {
        return new Charger();
    }

}
View Code

以上代碼,演示了三種實例化bean的方式分別使用不同的方式指定bean的加載順序。xml實例化方式使用depends-on屬性,java實例化方式和注解實例化方式使用@DependsOn注解。兩種方式都可以指定多個值。

10 bean范圍

在實例化bean時,我們可以選擇bean的活動范圍。spring默認實例化單例bean,我們可以自定義選擇注入prototype原型bean(有會講多實例bean)。在web環境還有request,session,application和websocket。

package com.datang.springcode.scope;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(value = "prototype")
public class Refrigerator {
}
View Code
package com.datang.springcode.scope;
public class Television {
}
View Code
<?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 class="com.datang.springcode.scope.Television" id="television" scope="prototype"/>
</beans>
View Code
package com.datang.springcode.scope;
public class Spoon {
}
View Code
package com.datang.springcode.scope;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.Scope;

@ComponentScan(value = {"com.datang.springcode.scope"})
@ImportResource(value = {"classpath:scope.xml"})
public class ScopeConfig {

    @Bean(value = "spoon")
    @Scope(value = "prototype")
    public Spoon getSpoon(){
        return new Spoon();
    }
}
View Code
    @Test
    public void t6(){
        ApplicationContext context = new AnnotationConfigApplicationContext(ScopeConfig.class);

        Refrigerator refrigerator = context.getBean(Refrigerator.class);
        Refrigerator refrigerator1 = context.getBean(Refrigerator.class);
        Assert.assertFalse(refrigerator==refrigerator1);

        Television television = context.getBean(Television.class);
        Television television1 = context.getBean(Television.class);
        Assert.assertFalse(television==television1);

        Spoon spoon = context.getBean(Spoon.class);
        Spoon spoon1 = context.getBean(Spoon.class);
        Assert.assertFalse(spoon==spoon1);

    }
View Code

上邊測試使用三種實例化bean的方式,實例化了三個原型bean。在singletonObjectsMap中並沒有上邊所實例化的三個單例bean。

另外我們也可以在。beanFactory下的beanDefinitionMap中查看所有bean的描述信息。在spring實例化bean的過程中,有一個步驟會將收集到的每個pojo信息存到到beanDefinitionMap中,等到以后使用。例如在實例化時,會先查看這個pojo是否是singleton,如果不是則不會實例化。

11 bean的回調函數

在spring實例化bean后,和銷毀bean之前有兩個鈎子函數。spring有三種方式實現這兩個鈎子函數。分別時xml配置,注解,和實現接口。

package com.datang.springcode.beanCallBack;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
//此處標記類需要被掃描
@Component
public class House implements InitializingBean, DisposableBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("接口形式的回調after");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("接口形式的回調destroy");
    }
    
    public House() {
        System.out.println("house的構造函數");
    }

    public void xmlInit() {
        System.out.println("xml形式初始化回調");
    }

    public void xmlDes() {
        System.out.println("xml形式銷毀回調");
    }

    @PostConstruct
    public void annInit() {
        System.out.println("注解形式的初始化回調");
    }

    @PreDestroy
    public void annDes() {
        System.out.println("注解形式的銷毀回調");
    }

 


}
View Code
<?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
       https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 掃描包,注意導入命名空間 -->
    <context:component-scan base-package="com.datang.springcode.beanCallBack"/>
    <bean class="com.datang.springcode.beanCallBack.House" id="house" init-method="xmlInit" destroy-method="xmlDes"/>
</beans>
View Code

注意這三種方式同時使用時的順序。注解優先,實現接口其次,xml配置最后。

12 組件掃描

12.1 @Component @Controller @Service @Repository @Configuration

@Component只能被標記在類上,被@Component標記表示這個類需要被spring實例化。

package com.datang.springcode.scanPack;

import org.springframework.stereotype.Component;

@Component
public class Milk {

}
View Code
package com.datang.springcode.scanPack;
public class Fruit {
}
View Code
@ComponentScan(value = {"com.datang.springcode.scanPack"})
public class ScanPackConfig {

}
View Code

上邊代碼片段,Fruit類沒有被@Component標記,所以沒有被實例化到singletonObjects這個Map中。@Controller @Service @Repository @Configuration 注解都是和@Component組合的注解。也就是說,這4個注解和@Component有同等的效果。其中 @Controller @Service @Repository 在語義化上代表服務層,業務層,持久層。而 @Configuration則一般和@Bean使用。也只有@Configuration是有特殊的功能。

12.2 @Bean@Configuration的精簡模式和完全模式

略微修改Milk類。

package com.datang.springcode.scanPack;

public class Milk {
    
    public Fruit fruit;
    //使其構造函數需要持有一個Fruit實例
    public Milk(Fruit fruit) {
        this.fruit = fruit;
    }
}
View Code
package com.datang.springcode.scanPack;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@ComponentScan(value = {"com.datang.springcode.scanPack"})
@Configuration
public class ScanPackConfig {

    @Bean(value = "milk")
    public Milk getMilk() {
        //這種寫法完全沒有問題。構造函數需要一個Fruit而getFruit()方法返回的就是一個fruit
        return new Milk(getFruit());
    }

    @Bean(value = "fruit")
    public Fruit getFruit() {
        return new Fruit();
    }
}
View Code

看結果,兩個fruit是同一個對象。這種結果就是完全模式。再看@Configuration

默認是true。我們修改成false試試。

package com.datang.springcode.scanPack;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@ComponentScan(value = {"com.datang.springcode.scanPack"})
@Configuration(proxyBeanMethods = false)
public class ScanPackConfig {

    @Bean(value = "milk")
    public Milk getMilk() {
        //這種寫法完全沒有問題。構造函數需要一個Fruit而getFruit()方法返回的就是一個fruit
        return new Milk(getFruit());
    }

    @Bean(value = "fruit")
    public Fruit getFruit() {
        return new Fruit();
    }
}
View Code

現在發現兩個fruit就不是同一個對象了。但是在singletonObjects中只有一個fruit

這就是@Bean的精簡模式。也是@Configuration和@Component的區別。

12.3 開啟組件掃描

spring提供兩種方式的組件掃描,注意組件掃描只會掃描類上標記有@Component注解或和@Component的組合注解如@Service

package com.datang.springcode.scanPack2;

import org.springframework.stereotype.Component;

@Component
public class Reality {
}
View Code
package com.datang.springcode.scanPack2;

import org.springframework.stereotype.Component;

@Component
public class Exist {
}
View Code
package com.datang.springcode.scanPack2;

public class Dream {
}
View Code
package com.datang.springcode.scanPack2;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan(value = "com.datang.springcode.scanPack2")
public class ScanPack2Config {
}
View Code

使用@ComponentScan注解掃描Dream類沒有標記@Component所以沒有被實例化出來。

<?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
       https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 掃描包,注意導入命名空間 -->
    <context:component-scan base-package="com.datang.springcode.scanPack2"/>
</beans>
View Code

使用xml方式配置掃描器,結果也是一樣的。

12.4默認的掃描路徑

即使我們使用@ComponentScan注解時,不指定value或者basePackages屬性,spring會默認掃描,該配置類的平級以及自包中所有@Component。

package com.datang.springcode.filterScan1;
public class Clerk extends Employee {
}
View Code
package com.datang.springcode.filterScan1;
public class Employee {
}
View Code
package com.datang.springcode.filterScan1;
public class Manager extends Employee {
}
View Code
package com.datang.springcode.filterScan1;

import org.springframework.stereotype.Component;

@Component
public class Smoke {
}
View Code
package com.datang.springcode.filterScan1.child;

import org.springframework.stereotype.Component;

@Component
public class Mask {
}
View Code
package com.datang.springcode.filterScan1;

import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class FilterScan1Config {
}
View Code

同級中的@Component被掃描到,子包中的@Component也可以被掃描到。這也就是springboot啟動類以下的包可以被掃描到的原因。

12.5 掃描過濾

在組件掃描過程中,除了掃描@Component標記的類,我們也可以額外的設置掃描規則。

在@ComponentScan注解的屬性中,有includeFilters()和excludeFilters()兩個屬性,分別是包含和排除,而這兩個屬性可接收多個Filter。我們來看看Filter中的type()屬性,有哪些可選值。

FilterType下有五種匹配模式,順序依次是。注解匹配,類型匹配,AspectJ表達式匹配,正則表達式匹配,自定義匹配器。

package com.datang.springcode.filterScan1;
public class Employee {
}
View Code
package com.datang.springcode.filterScan1;
public class Manager extends Employee {
}
View Code
package com.datang.springcode.filterScan1;
public class Clerk extends Employee {
}
View Code
package com.datang.springcode.filterScan1;

import org.springframework.stereotype.Component;

@Component
public class Smoke {
}
View Code
package com.datang.springcode.filterScan1.child;

import org.springframework.stereotype.Component;

@Component
public class Mask {
}
View Code
package com.datang.springcode.filterScan1;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;

@ComponentScan(includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {Employee.class})})
public class FilterScan1Config {
}
View Code

以上代碼片段,Employee Clerk Manager 三個類沒有被@Component注解。但我們在掃描組件時使用 includeFilters 並且按照 FilterType.ASSIGNABLE_TYPE Class類型 值是 Employee.class 如此Employee類及其子類可以被spring實例化。

下邊我們在試試excludeFilters,此時@Component標記的類沒有被注入。

package com.datang.springcode.filterScan1;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Component;

@ComponentScan(includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {Employee.class})},
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Component.class)})
public class FilterScan1Config {
}
View Code

使用xml配置方式也可以做到過濾掃描,需要注意的是,在xml中assignable代表class類型,和Filter中的ASSIGNABLE_TYPE作用一樣。<context:include-filter/>必須在<context:exclude-filter/>之前

<?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
       https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 掃描包,注意導入命名空間 -->
    <context:component-scan base-package="com.datang.springcode.filterScan1">
        <!-- 加載Employee類 -->
        <context:include-filter type="assignable" expression="com.datang.springcode.filterScan1.Employee"/>
        <!-- 排除配置類 -->
        <context:exclude-filter type="assignable" expression="com.datang.springcode.filterScan1.FilterScan1Config"/>
        <!-- 排除@Component注解-->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/>
    </context:component-scan>
</beans>
View Code

13 內部類實例化

在使用xml實例化bean時,內部類必須是static的,否則報錯

package com.datang.springcode.innerBean;
public class Teacher {
     static class  Student {

    }
}
View Code
<?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 class="com.datang.springcode.innerBean.Teacher$Student" id="student"/>
</beans>
View Code

@Component方式,外部類必須要是一個springbean,內部類必須是靜態的。

package com.datang.springcode.innerBean;

import org.springframework.stereotype.Component;
@Component
public class Family {
    @Component
    static class Person{

    }
}
View Code
package com.datang.springcode.innerBean;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan(basePackages = {"com.datang.springcode.innerBean"})
public class InnerBeanConfig {

}
View Code

java方式實例化bean則比較靈活

package com.datang.springcode.innerBean;

public class Food {
    class Bread {

    }
}
View Code
package com.datang.springcode.innerBean;

import org.springframework.context.annotation.Bean;

public class InnerBeanConfig2 {
    @Bean
    public Food.Bread getBread() {
        return new Food().new Bread();
    }
}
View Code

14 靜態工廠方法

package com.datang.springcode.staticFactoryBean;

public class Conditioner {

    private Conditioner() {
    }

    private static Conditioner conditioner = new Conditioner();

    public static Conditioner getConditioner() {
        return conditioner;
    }
}
View Code
<?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 class="com.datang.springcode.staticFactoryBean.Conditioner" id="conditioner" destroy-method="getConditioner"/>
</beans>
View Code

注意,靜態工廠,本身無法實例化,需要提供一個靜態的方法,用來返回bean。其實靜態工廠方法,如果您需要的是單例對象。不使用springbean完全可以。

延用剛才的pojo。我們用@Bean注入,其實這種做法就有點脫褲子放屁的感覺了,感受下。

package com.datang.springcode.staticFactoryBean;

import org.springframework.context.annotation.Bean;

public class StaticFactoryBeanConfig {

    @Bean
    public Conditioner getConditioner() {
        return Conditioner.getConditioner();
    }
}
View Code

@Component注解方式無法創建靜態工廠bean。因為構造方法私有化。

15 實例工廠方法 

只有xml配置支持實例工廠方法,要配置由實例工廠返回的pojo,需要先配置工廠類。

<?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 class="com.datang.springcode.newBeanFacotry.Mask" id="mask"/>

    <bean id="glove" factory-bean="mask" factory-method="getGlove"/>
</beans>
View Code
package com.datang.springcode.newBeanFacotry;
public class Glove {
}
View Code
package com.datang.springcode.newBeanFacotry;

public class Mask {

    private static Glove glove = new Glove();

    public Glove getGlove() {
        return glove;
    }
}
View Code

16 BeanPostProcessor

beanPostProcessor接口是spring提供給用戶的一個擴展接口,其作用是在每一個bean創建后,都可以對bean進行個性化設置。

package com.datang.springcode.beanPostProcessorTest;

import org.springframework.stereotype.Component;

@Component
public class Bright {
    public String name = "張三";

    public Bright() {
        System.out.println("構造函數 Bright");
    }
}
View Code
package com.datang.springcode.beanPostProcessorTest;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

@Component
public class CustomBeanPostProcessor implements BeanPostProcessor, Ordered {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("BeanPostProcessor1,before");
        if (beanName.equals("bright")) {
            Bright bright = (Bright) bean;
            bright.name = "李四";
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("BeanPostProcessor1,after");
        if (beanName.equals("bright")) {
            Bright bright = (Bright) bean;
            bright.name = "王五";
        }
        return bean;
    }

    @Override
    public int getOrder() {
        return 1;
    }
}
View Code
package com.datang.springcode.beanPostProcessorTest;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

@Component
public class CustomBeanPostProcessor2 implements BeanPostProcessor, Ordered {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("BeanPostProcessor2,before");
        if (beanName.equals("bright")) {
            Bright bright = (Bright) bean;
            bright.name = "趙六";
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("BeanPostProcessor2,after");
        if (beanName.equals("bright")) {
            Bright bright = (Bright) bean;
            bright.name = "馮七";
        }
        return bean;
    }

    @Override
    public int getOrder() {
        return 2;
    }
}
View Code
package com.datang.springcode.beanPostProcessorTest;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan(value = "com.datang.springcode.beanPostProcessorTest")
public class BeanPostProcessorTestConfig {
}
View Code

在以上代碼片段,我們創建了兩個BeanPostProcessor的實現類,並對Bright進行改造。這里需要注意,實現BeanPostProcessor接口的實現類,必須也是一個springbean,多個實現類之間可以實現Ordered接口,指定順序。

17 BeanFactoryPostProcessor

BeanFactoryPostProcessor擴展接口,用於在bean實例化之前改造bean,確切說是改造BeanDefinition,上邊提到過,spring首先收集所有pojo,給每個pojo創建一個BeanDefinition。BeanFactoryPostProcessor實現類,只執行一次,並不是每一個bean都執行。多個BeanFactoryPostProcessor還是使用Ordered接口指定順序。

package com.datang.springcode.beanFactoryPostProcessorTest;

import org.springframework.stereotype.Component;

@Component
public class Money {
}
View Code
package com.datang.springcode.beanFactoryPostProcessorTest;
public class Pen {
}
View Code
package com.datang.springcode.beanFactoryPostProcessorTest;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

@Component
public class BeanFactoryPostProcessor1 implements BeanFactoryPostProcessor, Ordered {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        GenericBeanDefinition genericBeanDefinition = (GenericBeanDefinition) configurableListableBeanFactory.getBeanDefinition("money");
        genericBeanDefinition.setBeanClass(Pen.class);
        System.out.println("BeanFactoryPostProcessor1");
    }

    @Override
    public int getOrder() {
        return 1;
    }
}
View Code
package com.datang.springcode.beanFactoryPostProcessorTest;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

@Component
public class BeanFactoryPostProcessor2 implements BeanFactoryPostProcessor, Ordered {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        BeanDefinition pen = configurableListableBeanFactory.getBeanDefinition("money");
        pen.setScope("prototype");
        System.out.println("BeanFactoryPostProcessor2");
    }

    @Override
    public int getOrder() {
        return 2;
    }
}
View Code
package com.datang.springcode.beanFactoryPostProcessorTest;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class BeanFactoryPostProcessorTestConfig {
}
View Code

這個測試有點復雜,首先注解@Component在Money類上,第一個BeanPostProcessor找到Money的BeanDefinition,然后向下轉型成GenericBeanDefinition使用setBeanClass重新設置BeanDefinition中的class,此時money就不是money了,而是Pen,在第二個BeanPostProcessor中我們設置bean的范圍是原型bean。

注入依賴屬性 

1 xml方式注入

1.1 基於構造器注入依賴屬性

package com.datang.springcode.xmlConstructorInject1;

public class Basketball {
    private String hobby;
    private String nikeName;
    private Football football;

    public Basketball(String nikeName, String hobby, Football football) {
        this.nikeName = nikeName;
        this.hobby = hobby;
        this.football = football;
    }
}
View Code
<?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:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="com.datang.springcode.xmlConstructorInject1.Football" id="football"/>

    <bean class="com.datang.springcode.xmlConstructorInject1.Basketball" id="basketball">
        <constructor-arg name="nikeName" value="張三"/>
        <constructor-arg name="hobby" value="我喜歡打籃球"/>
        <constructor-arg name="football" ref="football"/>
    </bean>

    <bean class="com.datang.springcode.xmlConstructorInject1.Basketball" id="basketball2"
          c:_0="李四"
          c:_1="我喜歡乒乓球"
          c:_2-ref="football"/>

    <bean class="com.datang.springcode.xmlConstructorInject1.Basketball" id="basketball3"
          c:nikeName="王五"
          c:hobby="我喜歡打游戲"
          c:football-ref="football"/>
</beans>
View Code

構造器注入注入,value屬性用於普通類型注入,ref屬性用於pojo屬性注入。另外引入c標簽我們也可以使用c:寫法注入屬性。

1.2 注入復雜元素數組,集合

構造器注入這次我們使用下標順序來注入屬性<constructor-arg index="0">,在內部使用spring提供的集合標簽,分別復制基本數據類型,和引用數據類型。

package com.datang.springcode.xmlConstructorInject1;

import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class Love {
    private List hobby;
    private Map friend;
    private Set gender;
    private Properties sitting;
    private String[] nikeName;

    public Love(List hobby, Map friend, Set gender, Properties sitting, String[] nikeName) {
        this.hobby = hobby;
        this.friend = friend;
        this.nikeName = nikeName;
        this.gender = gender;
        this.sitting = sitting;
    }
}
View Code
<?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 class="com.datang.springcode.xmlConstructorInject1.Football" id="football"/>


    <bean class="com.datang.springcode.xmlConstructorInject1.Love" id="love">
        <constructor-arg index="0">
            <list>
                <value>張三</value>
                <value>李四</value>
                <ref bean="football"/>
            </list>
        </constructor-arg>
        <constructor-arg index="1">
            <map>
                <entry key="a" value="A"/>
                <entry key="B" value="b"/>
                <entry key="c" value-ref="football"/>
            </map>
        </constructor-arg>
        <constructor-arg index="2">
            <set>
                <value>哈哈</value>
                <value>呵呵</value>
                <ref bean="football"/>
            </set>
        </constructor-arg>
        <constructor-arg index="3">
            <props>
                <prop key="p1">呸呸呸</prop>
                <prop key="p2">颯颯颯</prop>
                <prop key="p3">啊啊啊</prop>
            </props>
        </constructor-arg>
        <constructor-arg index="4">
            <array>
                <value>1</value>
                <value>2</value>
                <value>3</value>
            </array>
        </constructor-arg>
    </bean>

</beans>
View Code

1.3 <util>創建集合類型的bean

在1.2的代碼片段中我們在<constructor-arg index="0">內部定義集合,但是這種方式定義的集合只能使用一次,下面我們來創建集合bean,達到復用的效果。util標簽需要引入命名空間。

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

    <bean class="com.datang.springcode.xmlConstructorInject1.Football" id="football"/>

    <util:list id="list">
        <value>張三</value>
        <value>李四</value>
        <ref bean="football"></ref>
    </util:list>

    <util:map id="map">
        <entry key="1" value="張三"/>
        <entry key="2" value-ref="football"/>
    </util:map>

    <util:set id="set">
        <value>張三</value>
        <value>李四</value>
        <ref bean="football"></ref>
    </util:set>

    <util:properties id="properties">
        <prop key="a">A</prop>
        <prop key="b">B</prop>
        <prop key="c">C</prop>
    </util:properties>

    <bean class="com.datang.springcode.xmlConstructorInject1.Love" id="love">
        <constructor-arg index="0" ref="list"/>
        <constructor-arg index="1" ref="map"/>
        <constructor-arg index="2" ref="set"/>
        <constructor-arg index="3" ref="properties"/>
        <constructor-arg index="4">
            <array>
                <value>1</value>
                <value>2</value>
                <value>3</value>
            </array>
        </constructor-arg>
    </bean>

</beans>
View Code

1.4 類型選取

如果pojo的多個構造器參數個數相同,類型不同。此時我們就需要在使用構造器注入時,定義參數的類型,讓spring知道我們要使用哪一個構造器。

package com.datang.springcode.xmlConstructorInject1;

public class Chinese {
    private String name;
    private Integer age;

    public Chinese(Integer value) {
        this.age = value;
    }

    public Chinese(String value) {
        this.name = value;
    }


}
View Code
<?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 class="com.datang.springcode.xmlConstructorInject1.Chinese" id="chinese">
        <constructor-arg name="value" type="java.lang.Integer" value="100"/>
    </bean>
</beans>
View Code

1.5 基於set注入依賴屬性

set注入要求需要被注入依賴的屬性必須有set()方法。注入復雜元素和pojo的方式和構造器類似。

package com.datang.springcode.setInject1;
public class Wallet {
}
View Code
package com.datang.springcode.setInject1;

import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class Word {
    private String nikeName;
    private Wallet wallet;
    private List list;
    private Map map;
    private Set set;
    private Properties properties;
    private String[] str;

    public void setNikeName(String nikeName) {
        this.nikeName = nikeName;
    }

    public void setWallet(Wallet wallet) {
        this.wallet = wallet;
    }

    public void setList(List list) {
        this.list = list;
    }

    public void setMap(Map map) {
        this.map = map;
    }

    public void setSet(Set set) {
        this.set = set;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    public void setStr(String[] str) {
        this.str = str;
    }
}
View Code
<?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 class="com.datang.springcode.setInject1.Wallet" id="wallet"></bean>
    <bean class="com.datang.springcode.setInject1.Word" id="word">
        <property name="nikeName" value="張三"/>
        <property name="wallet" ref="wallet"/>
        <property name="list">
            <list>
                <value>aaa</value>
                <ref bean="wallet"/>
                <value>bbb</value>
            </list>
        </property>
        <property name="map">
            <map>
                <entry key="k1" value="v1"></entry>
                <entry key="k2" value-ref="wallet"></entry>
                <entry key="k3" value="v3"></entry>
            </map>
        </property>
        <property name="set">
            <set>
                <value>s1</value>
                <value>s2</value>
                <value>s3</value>
                <ref bean="wallet"/>
            </set>
        </property>
        <property name="properties">
            <props>
                <prop key="p1">pp1</prop>
                <prop key="p2">pp2</prop>
                <prop key="p3">pp3</prop>
            </props>
        </property>
        <property name="str">
            <array>
                <value>張三</value>
                <value>李四</value>
            </array>
        </property>
    </bean>
</beans>
View Code

同樣我們也可以使用p命名空間來的方式注入屬性。需要引入p命名空間url

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

    <bean class="com.datang.springcode.setInject1.Wallet" id="wallet"></bean>

    <util:list id="list">
        <value>張三</value>
        <value>李四</value>
        <ref bean="wallet"></ref>
    </util:list>

    <util:map id="map">
        <entry key="1" value="張三"/>
        <entry key="2" value-ref="wallet"/>
    </util:map>

    <util:set id="set">
        <value>張三</value>
        <value>李四</value>
        <ref bean="wallet"></ref>
    </util:set>


    <util:properties id="properties">
        <prop key="a">A</prop>
        <prop key="b">B</prop>
        <prop key="c">C</prop>
    </util:properties>
    <bean class="com.datang.springcode.setInject1.Word" id="word"
          p:nikeName="張三"
          p:wallet-ref="wallet"
          p:list-ref="list"
          p:map-ref="map"
          p:set-ref="set"
          p:properties-ref="properties"/>
</beans>
View Code

1.6 自動注入

xml方式可以自動注入引用類型,自動注入分為byName和byType,僅限於set方法構造器注入。要確保被依賴的bean確實存在。

package com.datang.springcode.xmlautoInject1;
public class School {
}
View Code
package com.datang.springcode.xmlautoInject1;

public class Family {
    private School school;

    public void setSchool(School school) {
        this.school = school;
    }
}
View Code
<?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 class="com.datang.springcode.xmlautoInject1.School" id="school"></bean>
    <bean class="com.datang.springcode.xmlautoInject1.Family" id="family" autowire="byType"></bean>
</beans>
View Code

以上測試代碼片段為byType,也就是根據屬性的類型,來注入依賴,那如果有兩個類型相同的呢?此時如果我們在使用byType就會報錯,因為spring無法從兩個類型一樣的bean中做出選擇,此時用byName是合適的。

<?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 class="com.datang.springcode.xmlautoInject1.School" id="school"></bean>
    <bean class="com.datang.springcode.xmlautoInject1.School" id="school2"></bean>
    <bean class="com.datang.springcode.xmlautoInject1.Family" id="family" autowire="byName"></bean>
</beans>
View Code

1.7 不參與自動注入

對於多個類型相同的bean,我們可以設置bean的autowire-candidate="false"不參與自動注入。此時有多個類型相同的bean,spring依然可以成功注入,因為參數自動注入的只有一個。

<?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 class="com.datang.springcode.xmlautoInject1.School" id="school" autowire-candidate="false"></bean>
    <bean class="com.datang.springcode.xmlautoInject1.School" id="school2" autowire-candidate="false"></bean>
    <bean class="com.datang.springcode.xmlautoInject1.School" id="school3" autowire-candidate="false"></bean>
    <bean class="com.datang.springcode.xmlautoInject1.School" id="school4" autowire-candidate="false"></bean>
    <bean class="com.datang.springcode.xmlautoInject1.Family" id="family" autowire="byType"></bean>
</beans>
View Code

1.8 優先注入

使用primary屬性,指定bean優先注入。

<?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 class="com.datang.springcode.xmlautoInject1.School" id="school" primary="true"></bean>
    <bean class="com.datang.springcode.xmlautoInject1.School" id="school2"></bean>
    <bean class="com.datang.springcode.xmlautoInject1.School" id="school3"></bean>
    <bean class="com.datang.springcode.xmlautoInject1.School" id="school4"></bean>
    <bean class="com.datang.springcode.xmlautoInject1.Family" id="family" autowire="byType"></bean>
</beans>
View Code

1.9 構造器注入外部屬性

首先注入要注入外部屬性,必須注入spring的內置類,PropertySourcesPlaceholderConfigurer類,指定locations屬性。讀取外部文件。

db.driver=com.mysql.jdbc.Driver
db.url=localhost:3306/pet
db.username=root
db.password=123
View Code
<?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:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
        <property name="locations">
            <value>classpath:db.properties</value>
        </property>
    </bean>

    <bean class="com.datang.springcode.outsource.Sitting" id="sitting">
        <constructor-arg name="driver" value="${db.driver}"/>
        <constructor-arg name="url" value="${db.url}"/>
        <constructor-arg name="username" value="${db.username}"/>
        <constructor-arg name="password" value="${db.password}"/>
    </bean>

    <bean class="com.datang.springcode.outsource.Sitting" id="sitting2"
          c:_0="${db.driver}"
          c:_1="${db.url}"
          c:_2="${db.username}"
          c:_3="${db.password}"/>

    <bean class="com.datang.springcode.outsource.Sitting" id="sitting3"
          c:driver="${db.driver}"
          c:url="${db.url}"
          c:username="${db.username}"
          c:password="${db.password}"/>

</beans>
View Code

此方式同樣適用c標簽的格式

1.10 set方式注入外部屬性

package com.datang.springcode.outsource;
public class Sitting2 {
    private String driver;
    private String url;
    private String username;
    private String password;

    public void setDriver(String driver) {
        this.driver = driver;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
View Code
<?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">

    <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
        <property name="locations">
            <value>classpath:db.properties</value>
        </property>
    </bean>

    <bean class="com.datang.springcode.outsource.Sitting2" id="sitting">
        <property name="driver" value="${db.driver}"/>
        <property name="url" value="${db.url}"/>
        <property name="username" value="${db.username}"/>
        <property name="password" value="${db.password}"/>
    </bean>

    <bean class="com.datang.springcode.outsource.Sitting2" id="sitting2"
          p:driver="${db.driver}"
          p:url="${db.url}"
          p:username="${db.username}"
          p:password="${db.password}"/>

</beans>
View Code

1.11 注入匿名內部類

內部bean沒有id,因為他不能單獨出現,不能給其他的bean使用.

package com.datang.springcode.xmlInnerBean;
public class Water {
    public String str;

    public void setStr(String str) {
        this.str = str;
    }
}
View Code
package com.datang.springcode.xmlInnerBean;

public class Milk {
    public Water water;
    public Water water2;

    public Milk(Water water) {
        this.water = water;
    }

    public void setWater2(Water water2) {
        this.water2 = water2;
    }
}
View Code
<?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 class="com.datang.springcode.xmlInnerBean.Milk" id="milk">
        <constructor-arg name="water">
            <bean class="com.datang.springcode.xmlInnerBean.Water">
                <property name="str" value="AAAAAA"/>
            </bean>
        </constructor-arg>
        <property name="water2">
            <bean class="com.datang.springcode.xmlInnerBean.Water">
                <property name="str" value="BBBBBB"/>
            </bean>
        </property>
    </bean>
</beans>
View Code

1.12 注入空字符串和null

package com.datang.springcode.emptyType;

public class Forget {

    private String name;
    private Object object;
    private String nikeName;
    private Object object2;

    public Forget(String nikeName, Object object2) {
        this.nikeName = nikeName;
        this.object2 = object2;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setObject(Object object) {
        this.object = object;
    }
}
View Code
<?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 class="com.datang.springcode.emptyType.Forget" id="forget">
        <constructor-arg name="nikeName" value=""/>
        <constructor-arg name="object2">
            <null/>
        </constructor-arg>
        <property name="name" value=""/>
        <property name="object">
            <null/>
        </property>
    </bean>
</beans>
View Code

value值默認是空字符串,<null/>

1.13 靜態工廠和構造器依賴注入

靜態工廠注入需要保證工廠方法是靜態的。參數可以是返回值的構造參數。

package com.datang.springcode.factoryInject;

public class Curtain {

    private String name;
    private Integer age;

    public Curtain(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}
View Code
package com.datang.springcode.factoryInject;

public class StaticFactoryBean {

    private StaticFactoryBean() {
    }

    public static Curtain getPerson(String name, Integer age) {
        return new Curtain(name, age);
    }
}
View Code
<?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 class="com.datang.springcode.factoryInject.StaticFactoryBean" id="curtain" factory-method="getPerson">
        <constructor-arg name="name" value="張三"/>
        <constructor-arg name="age" value="1"/>
    </bean>
</beans>
View Code

1.14  value值為bean的id

package com.datang.springcode.citeRestBean;

public class Owe {
    private String name;

    private String nikeName;

    public Owe(String name) {
        this.name = name;
    }

    public void setNikeName(String nikeName) {
        this.nikeName = nikeName;
    }
}
View Code
<?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 class="java.lang.Object" id="object"/>
    <bean class="com.datang.springcode.citeRestBean.Owe" id="owe">
        <constructor-arg name="name">
            <idref bean="object"/>
        </constructor-arg>
        <property name="nikeName">
            <idref bean="object"/>
        </property>
    </bean>
</beans>
View Code

定義value的值,一個bean的id。官方文檔上說,這樣可以保證value的值一定存在...確實我get不到這個點的意義。

2 注解注入

2.1 @Autowired注釋成員變量

在默認的情況下,@Autowired注解的屬性bean必須存在。

package com.datang.springcode.autowiredInject1;

import org.springframework.stereotype.Component;

@Component
public class Law {
}
View Code
package com.datang.springcode.autowiredInject1;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Attorney {

    @Autowired
    public Law law;

}
View Code
package com.datang.springcode.autowiredInject1;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class AutowiredInject1Config {
}
View Code

 

2.2 @Autowired注釋構造函數

package com.datang.springcode.autowiredInject1;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Order {
    private Law law;

    @Autowired
    public Order(Law law) {
        this.law = law;
    }

    public Order(String name) {

    }
}
View Code

如果bean只有一個構造器,忽略@Autowired也是可以的。這主要依賴於@Component注解的作用,但是如果有兩個構造器,其中一個必須使用@Autowired標注我們需要使用哪個構造器。

2.3 不是必須的注入

package com.datang.springcode.autowiredInject1;

import org.springframework.stereotype.Component;

@Component
public class Law {
}
View Code
package com.datang.springcode.autowiredInject1;



public class Train {
}
View Code
package com.datang.springcode.autowiredInject1;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Airplane {
    @Autowired
    public Law law;
    @Autowired(required = false)
    public Train train;

    public Train train2;

    public Airplane() {
    }

    @Autowired(required = false)
    public Airplane(Train train2) {
        this.train2 = train2;
    }
}
View Code

在以上代碼片段中,成員變量train使用@Autowired(required=false)表示屬性不是非必須的,沒有找到注入bean不報錯。而構造函數使用@Autowired(required=false),則表示構造函數參數也不是必須的,但是這意味着spring創建bean會失敗,所以必須要有一個空的構造函數才能保證創建bean成功。可以看到,當構造器注入屬性失敗時,spring使用的是空的構造函數創建bean。

2.4 byName還是byType

使用@Autowired注入屬性時,通常我們要保證有可用bean被注入。當需要注入的pojo是接口時,會尋找該接口的實現類。

package com.datang.springcode.byNameorbyType;

public interface Fruits {
}
View Code
package com.datang.springcode.byNameorbyType;

import org.springframework.stereotype.Component;

@Component
public class Sugarcane implements Fruits{
}
View Code
package com.datang.springcode.byNameorbyType;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class User {
    @Autowired
    public Fruits fruits;
}
View Code
package com.datang.springcode.byNameorbyType;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class ByNameByTypeConfig {
}
View Code

但是一個接口同時有兩個實現類就會報錯。

 

這時我們修改其中一個實現類的名稱修改為@Autowired標注的屬性名,則可以成功注入。由此可見,@Autowired先byType找到所有類型可以注入的,然后在byName找到其中名稱一樣的。

package com.datang.springcode.byNameorbyType;

import org.springframework.stereotype.Component;

@Component(value = "fruits")
public class Sugarcane implements Fruits {
}
View Code

2.5 @Nullable 可以為空的

當我們使用構造器注入時,屬性bean不存在,則會報異常。使用@Nullable允許參數為null

package com.datang.springcode.cannull;
public class Cloud {
}
View Code
package com.datang.springcode.cannull;
public class Snow {
}
View Code
package com.datang.springcode.cannull;

import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

@Component
public class Sky {
    public Cloud cloud;
    public Snow snow;

    public Sky(@Nullable Cloud cloud, @Nullable Snow snow) {
        this.cloud = cloud;
        this.snow = snow;
    }
}
View Code
package com.datang.springcode.cannull;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class CanNullConfig {
}
View Code

 可以看出spring並沒有使用帶參構造函數實例化bean,並且我們也沒有指定無參數構造函數。如果一個參數可以找到bean,一個找不到也是可以的。在Cloud類上加入@Component注解

package com.datang.springcode.cannull;

import org.springframework.stereotype.Component;

@Component
public class Cloud {
}
View Code

我們並沒有定義Cloud單參數的構造函數,這是spring幫我們做的事情。

2.6 @Primary 優先注入

上邊我們說過,如果一個接口有兩個或以上實現類,我們需要保證byName能夠找到bean。或者我們可以提高bean的優先級

package com.datang.springcode.primaryBean;
public interface Profession {
}
View Code
package com.datang.springcode.primaryBean;

import org.springframework.stereotype.Component;

@Component
public class Doctor implements Profession{
}
View Code
package com.datang.springcode.primaryBean;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Component
@Primary
public class Police implements Profession{
}
View Code
package com.datang.springcode.primaryBean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Human {
    @Autowired
    private Profession profession;
}
View Code
package com.datang.springcode.primaryBean;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class PrimaryBeanConfig {
}
View Code

2.7 @Resource和@Autowired的區別

@Resource注解同樣可以注入屬性。

package com.datang.springcode.resourceInject;

import org.springframework.stereotype.Component;

@Component
public class Base {
}
View Code
package com.datang.springcode.resourceInject;

import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component
public class Laugh {
    @Resource
    private Base base;
}
View Code
package com.datang.springcode.resourceInject;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class ResourceInjectConfig {
}
View Code

下面我們修改Base類的name。並且給@Resource增加name屬性

package com.datang.springcode.resourceInject;

import org.springframework.stereotype.Component;

@Component(value = "base1")
public class Base {
}
View Code
package com.datang.springcode.resourceInject;

import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component
public class Laugh {
    @Resource(name = "base")
    private Base base;
}
View Code

@Autowired章節我們說到@Autowired是先byType后byName。而@Resource注解則可以自己指定是byName還是byType。通過name和type屬性。一下對@Resource注解的裝配順序

1. 如果同時指定了name和type,則從Spring上下文中找到唯一匹配的bean進行裝配,找不到則拋出異常
2. 如果指定了name,則從上下文中查找名稱(id)匹配的bean進行裝配,找不到則拋出異常
3. 如果指定了type,則從上下文中找到類型匹配的唯一bean進行裝配,找不到或者找到多個,都會拋出異常
4. 如果既沒有指定name,又沒有指定type,則自動按照byName方式進行裝配;如果沒有匹配,則回退為一個原始類型進行匹配,如果匹配則自動裝配;

2.8 @PropertySource和@Value注入外部屬性

db.driver=com.mysql.jdbc.Driver
db.url=localhost:3306/pet
db.username=root
db.password=123
View Code
package com.datang.springcode.propertySourceAndValue;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class Lamp {

    public String driver;

    @Value("${db.url}")
    public String url;

    public Lamp(@Value("${db.driver}") String driver) {
        this.driver = driver;
    }
}
View Code
package com.datang.springcode.propertySourceAndValue;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.PropertySource;

@ComponentScan
@PropertySource("classpath:db.properties")
public class propertySourceAndValueConfig {
}
View Code

通過@PropertySource注解讀取配置文件,然后通過@Value注入屬性到bean。另外我們還可以配置本地路徑@PropertySource(value = {"classpath:db.properties","file:C:\\workspace\\springcode-core-ioc\\src\\main\\resources\\db2.properties"})。classpath從類路徑下尋找文件,file從服務器文件系統尋找文件。此外關於@PropertySource讀取文件,spring將讀取到的文件也封裝成了對象。

context->environment->propertySource->propertySourceList

並且封裝成了bean context->beanFactory->singletonObjects->environment->value->propertySource-propertySourceList

2.9 注入集合,收集bean

@Autowired注入集合類型,並不是像xml那樣尋找集合的引用,而是收集context中所有相同類型的bean。

package com.datang.springcode.collectionGather;
public interface Honey {
}
View Code
package com.datang.springcode.collectionGather;

import org.springframework.stereotype.Component;

@Component
public class Tissue implements Honey{
}
View Code
package com.datang.springcode.collectionGather;

import org.springframework.stereotype.Component;

@Component
public class Server implements Honey{
}
View Code
package com.datang.springcode.collectionGather;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class Gather {

    @Autowired
    public List<Honey> honeys;
}
View Code
package com.datang.springcode.collectionGather;

import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class CollectionGatherConfig {
}
View Code

特性和功能

1 bean的繼承

在java中,子類會繼承父類的屬性。那在spring中子bean是否也存在這種特性呢?先來看xml配置bean的方式。

package com.datang.springcode.beanextends;

public class Creation {
    public Object object;

    public void setObject(Object object) {
        this.object = object;
    }
}
View Code
package com.datang.springcode.beanextends;


public class Plagiarize extends Creation{
}
View Code
<?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 class="com.datang.springcode.beanextends.Creation" id="creation">
        <property name="object">
            <bean class="java.lang.Object"></bean>
        </property>
    </bean>
    <bean class="com.datang.springcode.beanextends.Plagiarize" id="plagiarize"></bean>
</beans>
View Code

在父pojo中設置了屬性object。子pojo並沒有繼承到。如果需要繼承則需要給子bean加上parent屬性。

<?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 class="com.datang.springcode.beanextends.Creation" id="creation">
        <property name="object">
            <bean class="java.lang.Object"></bean>
        </property>
    </bean>
    <bean class="com.datang.springcode.beanextends.Plagiarize" id="plagiarize" parent="creation"></bean>
</beans>
View Code

使用注解方式創建bean則比較有意思。

package com.datang.springcode.beanextends;

import org.springframework.stereotype.Component;

@Component
public class Cities {
}
View Code
package com.datang.springcode.beanextends;

import org.springframework.beans.factory.annotation.Autowired;
public class City {
    @Autowired
    public Cities cities;
}
View Code
package com.datang.springcode.beanextends;

import org.springframework.stereotype.Component;

@Component
public class Capital extends City{
}
View Code
package com.datang.springcode.beanextends;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class BeanExtendsConfig {
}
View Code

上邊代碼片段,子bean的父類並沒有增加@Component注解,看結果。

我們發現子bean的屬性中有cities。在singletonObjects中也並沒有city這個bean,由此可見,子類實際是繼承了父類的@Autowried注解。自己把屬性注入了。

最后java方式創建bean,則沒有繼承的概念。

package com.datang.springcode.beanextends2;
public class Woman {
}
View Code
package com.datang.springcode.beanextends2;


public class Marriage {

    public Woman woman;

    public void setWoman(Woman woman) {
        this.woman = woman;
    }
}
View Code
package com.datang.springcode.beanextends2;
public class Lovers extends Marriage{
}
View Code
package com.datang.springcode.beanextends2;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Beanextends2Config {

    @Bean
    public Lovers lovers(){
        return  new Lovers();
    }


    @Bean
    public Woman woman(){
        return new Woman();
    }

    @Bean
    public Marriage marriage(){
        Marriage marriage = new Marriage();
        marriage.setWoman(woman());
        return marriage;
    }
}
View Code

但是在父bean中確存在Woman屬性。

2 多個配置間互相拾取

2.1 @Import java配置互相拾取

首先@Configuration注解除了能檢測@Bean之外,還會把標記注解的class創建成bean,而這個class可能本身沒有意義。如果我們又有多個@Bean類,這時使用@Import可以有效的創建多個類中的@Bean。

package com.datang.springcode.configPickup;


import org.springframework.context.annotation.Bean;

public class PickupConfig2 {
    @Bean
    public Object object1() {
        return new Object();
    }
}
View Code
package com.datang.springcode.configPickup;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(value = {PickupConfig2.class})
public class PickupConfig1 {
}
View Code

2.2 <import> xml文件互相拾取

多個xml之間,可以使用import標簽導入

<?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 class="java.lang.Object" id="o2"></bean>
</beans>
View Code
<?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">

    <import resource="classpath:configPickup2.xml"/>
    <bean class="java.lang.Object" id="o1"></bean>
</beans>
View Code

2.3 @ImportResource java配置拾取xml

@ImportResource注解,用來導入其他的xml配置。

package com.datang.springcode.configPickup;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;

@Configuration
@Import(value = {PickupConfig2.class})
@ImportResource(locations = {"classpath:configPickup1.xml"})
public class PickupConfig1 {
}
View Code

2.4 <context:component-scan/> 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: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 https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.datang.springcode.configPickup"/>
    <import resource="classpath:configPickup2.xml"/>
    <bean class="java.lang.Object" id="o1"></bean>
</beans>
View Code

3 spring5.0生成候選組件的索引

在正式開發中我們會把編譯好的war包放到Tomcat中,然后啟動Tomcat就運行項目,此時@ComponentScan就開始工作。但是如果我們這個項目有幾千個Class那么掃描操作是很耗時的。Srping5引入了生成索引的方法。在編譯期間對掃描到的類創建索引,在Tomcat啟動時,就會直接從索引中創建類。加快啟動速度。

在pom中增加依賴,成功編譯項目后再classes\META-INF會產生一個spring.components。當項目啟動時,則會根據這個索引去創建bean.

   <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-indexer</artifactId>
            <version>5.2.1.RELEASE</version>
            <optional>true</optional>
        </dependency>
View Code

4 國際化

content=歓迎いたします{0}來到{1},現在的語言環境是:{2}
View Code
content=歡迎{0}來到{1},現在的語言環境是:{2}
View Code
content=歡迎{0}來到{1},現在的語言環境是:{2}
View Code
package com.datang.springcode.internationalization;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.stereotype.Component;

import java.util.Locale;

@Component
public class Operation {
    @Autowired
    private ResourceBundleMessageSource messageSource;

    public void getInternationalization() {
        String message1 = messageSource.getMessage("content", new Object[]{"趙六", "博客園", "簡體中文"}, "歡迎用戶來到網站", Locale.SIMPLIFIED_CHINESE);
        String message2 = messageSource.getMessage("content", new Object[]{"李四", "博客園", "繁體中文"}, "歡迎用戶來到網站", Locale.TRADITIONAL_CHINESE);
        String message3 = messageSource.getMessage("content", new Object[]{"張三", "博客園", "日語"}, "歡迎用戶來到網站", Locale.JAPAN);

        System.out.println(message1);
        System.out.println(message2);
        System.out.println(message3);
    }
}
View Code
package com.datang.springcode.internationalization;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.support.ResourceBundleMessageSource;

@ComponentScan
public class InternationalizationConfig {
    @Bean
    public ResourceBundleMessageSource setMessageSource() {
        ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource();
        resourceBundleMessageSource.setBasenames("title");
        return resourceBundleMessageSource;
    }


}
View Code

首先我們需要創建spring ResourceBundleMessageSource內置對象,然后設置要加載的文件前綴。這里需要注意的是,除了前綴之外,后邊的都是固定格式。語言類型必須是java.util.Locale類中的靜態變量。其次,{0}這樣的標記是替換字符串。在getMessage()方法參數對應的含義是 尋找的key,替換字符串,沒有找到key設置的默認值,語言環境(這個語言環境只有和文件名設置的語言類型一樣,才會找到)

 

 

5 @Profile環境抽象

package com.datang.springcode.environmentAbstract;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile;

public class EnvironmentAbstractConfig {

    @Bean
    @Profile(value = {"prod"})
    public Object obj1(){
        return new Object();
    }

    @Bean
    @Profile(value = {"test"})
    public Object obj2(){
        return new Object();
    }
}
View Code

spring 提供的環境抽象,十分強大,供我們在不同的環境中注入不同的類,引用不同的配置信息。最典型的設置配置環境的方法是springboot中在properties文件中使用 spring.profiles.active= xxx

6 事件監聽與訂閱

在開發時,我們會有這樣的需求,當一個對象改變,隨即我們要改變其他的對象的狀態。最常用的設計模式是觀察者模式,詳情請看 https://www.cnblogs.com/zumengjie/p/12054663.html spring對於這一套監聽,訂閱也封裝了一套接口。來看官方文檔的demo

package com.datang.springcode.incidentMonitor;

import org.springframework.context.ApplicationEvent;
//繼承ApplicationEvent使其類成為一個事件類
public class BlackListEvent extends ApplicationEvent {
    private final String address;
    private final String content;

    public BlackListEvent(Object source, String address, String content) {
        super(source);
        this.address = address;
        this.content = content;
    }

    public String getAddress() {
        return address;
    }

    public String getContent() {
        return content;
    }
}
View Code
package com.datang.springcode.incidentMonitor;
import org.springframework.context.ApplicationListener;

//實現ApplicationListener使其成為監聽類,監聽的對象就是 BlackListEvent
public class BlackListNotifier  implements ApplicationListener<BlackListEvent> {
    @Override
    public void onApplicationEvent(BlackListEvent event) {
        System.out.println(event.getAddress() + "已被列入黑名單,不能發送郵件訂閱者1");
    }
}
View Code
package com.datang.springcode.incidentMonitor;

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class BlackListNotifier2 {

    //使用注解也可以監聽事件
    @EventListener(value = {BlackListEvent.class})
    public void t(BlackListEvent event) {
        System.out.println("我是訂閱者3,我啥都不干,就是隨便監聽");
    }
}
View Code
package com.datang.springcode.incidentMonitor;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;

import java.util.List;

//ApplicationEventPublisherAware實現 使其成為服務類
public class EmailService implements ApplicationEventPublisherAware {
    private List<String> blackList;
    private ApplicationEventPublisher publisher;

    public void setBlackList(List<String> blackList) {
        this.blackList = blackList;
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    // 主要邏輯就在這里,發送郵件之前先做一次判斷,判斷是否在黑名單內
    public void sendEmail(String address, String content) {
        if (blackList.contains(address)) {
            publisher.publishEvent(new BlackListEvent(this, address, content));
            return;
        }
        // send email...
        System.out.println(address + "郵件已被發送...");
    }
}
View Code
package com.datang.springcode.incidentMonitor;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;

@Configuration
@ComponentScan(value = "com.datang.springcode.incidentMonitor")
public class ListenerConfig {
    @Bean
    public EmailService emailService() {

        EmailService emailService = new EmailService();
        // 在這里添加黑名單集合
        emailService.setBlackList(Arrays.asList("known.spammer@example.org", "known.hacker@example.org", "john.doe@example.org", "blackAddredd@123.com"));

        return emailService;
    }

    @Bean
    public BlackListNotifier blackListNotifier() {
        return new BlackListNotifier();
    }
}
View Code

這里講一下業務邏輯,當我們調用EmailService的sendEmail()方法時會先判斷傳入的郵件地址是否在黑名單中,黑名單在創建EmailService這個bean時初始化了。如果郵件地址在黑名單中,就new一個BlackListEvent對象,並且傳入相關的參數。而BlackListNotifier BlackListNotifier2 兩個類都監聽的有BlackListEvent所以這時就會執行相應的方法。這里我們需要說下,使用@EventListener注解的方法,可以監聽多個事件類。另外這些監聽類都是同步執行的,一個執行完畢另一個才會后續執行。多個被@EventListener注釋的方法可以用 @Order()指定順序

7 ApplicationContext的父子關系

在springmvc中我們常常在web.xml中這么配置。

<!-- 加載spring容器 --> 
<!-- 初始化加載application.xml的各種配置文件 --> 
<context-param>    
    <param-name>contextConfigLocation</param-name> 
    <param-value>classpath:spring/application-*.xml</param-value>  
</context-param>  
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
</listener>  
<!-- 配置springmvc前端控制器 -->  
<servlet>
    <servlet-name>taotao-manager</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- contextConfigLocation不是必須的, 如果不配置contextConfigLocation
,     springmvc的配置文件默認在:WEB-INF/servlet的name+"-servlet.xml" -->
    <init-param>        
    <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/springmvc.xml</param-value>
    </init-param>    
<load-on-startup>1</load-on-startup>  
</servlet>
View Code

首先配置的是Spring容器的初始化加載的application文件,然后是SpringMVC的前端控制器(DispatchServlet),當配置完DispatchServlet后會在Spring容器中創建一個新的容器。其實這是兩個容器,Spring作為父容器,SpringMVC作為子容器。對於傳統的spring mvc來說,ServletDispatcher對應的容器為子容器,而web.xml中通過ContextLoaderListner的contextConfigLocation屬性配置的為父容器。有了這個概念我們來嘗試下做一個子父容器

package com.datang.springcode.parentContext;
public class ParentBean {
    private String name = "我是父bean";

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
View Code
package com.datang.springcode.parentContext;
public class ChildBean {
    private String name = "我是子bean";

    private ParentBean pBean;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public ParentBean getpBean() {
        return pBean;
    }

    public void setpBean(ParentBean pBean) {
        this.pBean = pBean;
    }
}
View Code
<?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="parentBean" class="com.datang.springcode.parentContext.ParentBean"></bean>


</beans>
View Code
<?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="childBean" class="com.datang.springcode.parentContext.ChildBean">
        <property name="pBean" ref="parentBean"/>
    </bean>

</beans>
View Code
@Test
    public void t54() {
        //創建一個 spring父容器
        ApplicationContext parentContext = new ClassPathXmlApplicationContext("classpath:parentContext.xml");

        //創建一個 spring子容器 此處傳遞父bean作為參數
        ClassPathXmlApplicationContext childContext = new ClassPathXmlApplicationContext(parentContext);
        //然后才能設置資源文件,切記一定要先設置 父bean
        childContext.setConfigLocation("classpath:parentContext2.xml");
        //這個不要忘記了
        childContext.refresh();

        //父容器獲取自己的bean
        ParentBean parentBean = parentContext.getBean("parentBean", ParentBean.class);

        //子容器獲取自己的bean
        ChildBean childBean = childContext.getBean("childBean", ChildBean.class);

        //子容器獲取父容器的bean,可以獲取到
        ParentBean parentBean2 = childContext.getBean("parentBean", ParentBean.class);

        //父容器獲取子容器的bean 此時是獲取不
        // ChildBean childBean3 = parentContext.getBean("childBean", ChildBean.class);
    }
View Code


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM