-
xml方式實例化bean
-
注解方式實例化bean
-
java方式實例化bean
-
ClassPathXmlApplication和AnnotationConfigApplicationContext
-
Bean存放到了哪里?
-
選取哪一種方式合適?
-
bean的名稱
-
延遲加載
-
bean加載順序
-
bean范圍
-
bean的回調函數
-
組件掃描
-
內部類實例化
-
靜態工廠方法
-
實例工廠方法
-
BeanPostProcessor
-
BeanFactoryPostProcessor
閱讀須知
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>
2 注解方式實例化bean
package com.datang.springcode.annoRegisterBean;
import org.springframework.stereotype.Component;
//@Component標識這個類,需要被實例化
@Component
public class Pear {
}
3 java方式實例化bean
package com.datang.springcode.javaRegisterBean;
//需要被實例化的bean
public class Banana {
}
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();
}
}
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");
}
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>
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();
}
}
在上邊代碼中,使用@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();
}
}
package com.datang.springcode.annoRegisterBean;
import org.springframework.stereotype.Component;
//@Component標識這個類,需要被實例化.value指定bean的名稱
@Component(value = "pear")
public class Pear {
}
<?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>
通過以上代碼我們給每一個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();
}
}
<?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>

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

8 延遲加載
bean的延遲加載也就是說,在spring上下文啟動時,不會立刻將bean實例化到registeredSingletonsMap中,而是在第一次使用getBean()時才會實例化。
@Component
@Lazy
public class Bookrack {
}
package com.datang.springcode.lazyBean;
public class Schoolbag {
}
package com.datang.springcode.lazyBean;
public class Stool {
}
<?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>
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();
}
}
以上代碼通過三種方式實例化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");
}
}
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");
}
}
package com.datang.springcode.depends_on;
import org.springframework.stereotype.Component;
@Component(value = "bed")
public class Bed {
public Bed(){
System.out.println("bed");
}
}
package com.datang.springcode.depends_on;
public class Quilt {
public Quilt(){
System.out.println("quilt:我依賴於bed");
}
}
<?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>
package com.datang.springcode.depends_on;
public class Phone {
public Phone() {
System.out.println("phone:我依賴於charger");
}
}
package com.datang.springcode.depends_on;
public class Charger {
public Charger() {
System.out.println("charger");
}
}
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();
}
}
以上代碼,演示了三種實例化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 {
}
package com.datang.springcode.scope;
public class Television {
}
<?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>
package com.datang.springcode.scope;
public class Spoon {
}
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();
}
}
@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);
}
上邊測試使用三種實例化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("注解形式的銷毀回調");
}
}
<?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>

注意這三種方式同時使用時的順序。注解優先,實現接口其次,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 {
}
package com.datang.springcode.scanPack;
public class Fruit {
}
@ComponentScan(value = {"com.datang.springcode.scanPack"})
public class ScanPackConfig {
}

上邊代碼片段,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;
}
}
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();
}
}

看結果,兩個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();
}
}

現在發現兩個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 {
}
package com.datang.springcode.scanPack2;
import org.springframework.stereotype.Component;
@Component
public class Exist {
}
package com.datang.springcode.scanPack2;
public class Dream {
}
package com.datang.springcode.scanPack2;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan(value = "com.datang.springcode.scanPack2")
public class ScanPack2Config {
}
使用@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>
使用xml方式配置掃描器,結果也是一樣的。

12.4默認的掃描路徑
即使我們使用@ComponentScan注解時,不指定value或者basePackages屬性,spring會默認掃描,該配置類的平級以及自包中所有@Component。
package com.datang.springcode.filterScan1;
public class Clerk extends Employee {
}
package com.datang.springcode.filterScan1;
public class Employee {
}
package com.datang.springcode.filterScan1;
public class Manager extends Employee {
}
package com.datang.springcode.filterScan1;
import org.springframework.stereotype.Component;
@Component
public class Smoke {
}
package com.datang.springcode.filterScan1.child;
import org.springframework.stereotype.Component;
@Component
public class Mask {
}
package com.datang.springcode.filterScan1;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class FilterScan1Config {
}


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

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

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

FilterType下有五種匹配模式,順序依次是。注解匹配,類型匹配,AspectJ表達式匹配,正則表達式匹配,自定義匹配器。
package com.datang.springcode.filterScan1;
public class Employee {
}
package com.datang.springcode.filterScan1;
public class Manager extends Employee {
}
package com.datang.springcode.filterScan1;
public class Clerk extends Employee {
}
package com.datang.springcode.filterScan1;
import org.springframework.stereotype.Component;
@Component
public class Smoke {
}
package com.datang.springcode.filterScan1.child;
import org.springframework.stereotype.Component;
@Component
public class Mask {
}
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 {
}
以上代碼片段,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 {
}

使用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>

13 內部類實例化
在使用xml實例化bean時,內部類必須是static的,否則報錯
package com.datang.springcode.innerBean;
public class Teacher {
static class Student {
}
}
<?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>

@Component方式,外部類必須要是一個springbean,內部類必須是靜態的。
package com.datang.springcode.innerBean;
import org.springframework.stereotype.Component;
@Component
public class Family {
@Component
static class Person{
}
}
package com.datang.springcode.innerBean;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan(basePackages = {"com.datang.springcode.innerBean"})
public class InnerBeanConfig {
}

java方式實例化bean則比較靈活
package com.datang.springcode.innerBean;
public class Food {
class Bread {
}
}
package com.datang.springcode.innerBean;
import org.springframework.context.annotation.Bean;
public class InnerBeanConfig2 {
@Bean
public Food.Bread getBread() {
return new Food().new Bread();
}
}

14 靜態工廠方法
package com.datang.springcode.staticFactoryBean;
public class Conditioner {
private Conditioner() {
}
private static Conditioner conditioner = new Conditioner();
public static Conditioner getConditioner() {
return conditioner;
}
}
<?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>
注意,靜態工廠,本身無法實例化,需要提供一個靜態的方法,用來返回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();
}
}

@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>
package com.datang.springcode.newBeanFacotry;
public class Glove {
}
package com.datang.springcode.newBeanFacotry;
public class Mask {
private static Glove glove = new Glove();
public Glove getGlove() {
return glove;
}
}

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");
}
}
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;
}
}
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;
}
}
package com.datang.springcode.beanPostProcessorTest;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan(value = "com.datang.springcode.beanPostProcessorTest")
public class BeanPostProcessorTestConfig {
}

在以上代碼片段,我們創建了兩個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 {
}
package com.datang.springcode.beanFactoryPostProcessorTest;
public class Pen {
}
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;
}
}
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;
}
}
package com.datang.springcode.beanFactoryPostProcessorTest;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class BeanFactoryPostProcessorTestConfig {
}

這個測試有點復雜,首先注解@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;
}
}
<?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>

構造器注入注入,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;
}
}
<?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>

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>

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; } }
<?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>

1.5 基於set注入依賴屬性
set注入要求需要被注入依賴的屬性必須有set()方法。注入復雜元素和pojo的方式和構造器類似。
package com.datang.springcode.setInject1; public class Wallet { }
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; } }
<?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>

同樣我們也可以使用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>

1.6 自動注入
xml方式可以自動注入引用類型,自動注入分為byName和byType,僅限於set方法構造器注入。要確保被依賴的bean確實存在。
package com.datang.springcode.xmlautoInject1; public class School { }
package com.datang.springcode.xmlautoInject1; public class Family { private School school; public void setSchool(School school) { this.school = school; } }
<?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>

以上測試代碼片段為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>

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>

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>

1.9 構造器注入外部屬性
首先注入要注入外部屬性,必須注入spring的內置類,PropertySourcesPlaceholderConfigurer類,指定locations屬性。讀取外部文件。
db.driver=com.mysql.jdbc.Driver
db.url=localhost:3306/pet
db.username=root
db.password=123
<?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>
此方式同樣適用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; } }
<?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>

1.11 注入匿名內部類
內部bean沒有id,因為他不能單獨出現,不能給其他的bean使用.
package com.datang.springcode.xmlInnerBean; public class Water { public String str; public void setStr(String str) { this.str = str; } }
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; } }
<?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>

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; } }
<?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>

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; } }
package com.datang.springcode.factoryInject; public class StaticFactoryBean { private StaticFactoryBean() { } public static Curtain getPerson(String name, Integer age) { return new Curtain(name, age); } }
<?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>
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; } }
<?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>
定義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 { }
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; }
package com.datang.springcode.autowiredInject1; import org.springframework.context.annotation.ComponentScan; @ComponentScan public class AutowiredInject1Config { }

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) { } }

如果bean只有一個構造器,忽略@Autowired也是可以的。這主要依賴於@Component注解的作用,但是如果有兩個構造器,其中一個必須使用@Autowired標注我們需要使用哪個構造器。
2.3 不是必須的注入
package com.datang.springcode.autowiredInject1; import org.springframework.stereotype.Component; @Component public class Law { }
package com.datang.springcode.autowiredInject1; public class Train { }
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; } }
在以上代碼片段中,成員變量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 { }
package com.datang.springcode.byNameorbyType; import org.springframework.stereotype.Component; @Component public class Sugarcane implements Fruits{ }
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; }
package com.datang.springcode.byNameorbyType; import org.springframework.context.annotation.ComponentScan; @ComponentScan public class ByNameByTypeConfig { }

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

這時我們修改其中一個實現類的名稱修改為@Autowired標注的屬性名,則可以成功注入。由此可見,@Autowired先byType找到所有類型可以注入的,然后在byName找到其中名稱一樣的。
package com.datang.springcode.byNameorbyType; import org.springframework.stereotype.Component; @Component(value = "fruits") public class Sugarcane implements Fruits { }

2.5 @Nullable 可以為空的
當我們使用構造器注入時,屬性bean不存在,則會報異常。使用@Nullable允許參數為null
package com.datang.springcode.cannull; public class Cloud { }
package com.datang.springcode.cannull; public class Snow { }
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; } }
package com.datang.springcode.cannull; import org.springframework.context.annotation.ComponentScan; @ComponentScan public class CanNullConfig { }

可以看出spring並沒有使用帶參構造函數實例化bean,並且我們也沒有指定無參數構造函數。如果一個參數可以找到bean,一個找不到也是可以的。在Cloud類上加入@Component注解
package com.datang.springcode.cannull; import org.springframework.stereotype.Component; @Component public class Cloud { }

我們並沒有定義Cloud單參數的構造函數,這是spring幫我們做的事情。
2.6 @Primary 優先注入
上邊我們說過,如果一個接口有兩個或以上實現類,我們需要保證byName能夠找到bean。或者我們可以提高bean的優先級
package com.datang.springcode.primaryBean; public interface Profession { }
package com.datang.springcode.primaryBean; import org.springframework.stereotype.Component; @Component public class Doctor implements Profession{ }
package com.datang.springcode.primaryBean; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @Component @Primary public class Police implements Profession{ }
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; }
package com.datang.springcode.primaryBean; import org.springframework.context.annotation.ComponentScan; @ComponentScan public class PrimaryBeanConfig { }

2.7 @Resource和@Autowired的區別
@Resource注解同樣可以注入屬性。
package com.datang.springcode.resourceInject; import org.springframework.stereotype.Component; @Component public class Base { }
package com.datang.springcode.resourceInject; import org.springframework.stereotype.Component; import javax.annotation.Resource; @Component public class Laugh { @Resource private Base base; }
package com.datang.springcode.resourceInject; import org.springframework.context.annotation.ComponentScan; @ComponentScan public class ResourceInjectConfig { }

下面我們修改Base類的name。並且給@Resource增加name屬性
package com.datang.springcode.resourceInject; import org.springframework.stereotype.Component; @Component(value = "base1") public class Base { }
package com.datang.springcode.resourceInject; import org.springframework.stereotype.Component; import javax.annotation.Resource; @Component public class Laugh { @Resource(name = "base") private Base base; }

@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
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; } }
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 { }

通過@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 { }
package com.datang.springcode.collectionGather; import org.springframework.stereotype.Component; @Component public class Tissue implements Honey{ }
package com.datang.springcode.collectionGather; import org.springframework.stereotype.Component; @Component public class Server implements Honey{ }
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; }
package com.datang.springcode.collectionGather; import org.springframework.context.annotation.ComponentScan; @ComponentScan public class CollectionGatherConfig { }

特性和功能
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; } }
package com.datang.springcode.beanextends; public class Plagiarize extends Creation{ }
<?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>

在父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>

使用注解方式創建bean則比較有意思。
package com.datang.springcode.beanextends; import org.springframework.stereotype.Component; @Component public class Cities { }
package com.datang.springcode.beanextends; import org.springframework.beans.factory.annotation.Autowired; public class City { @Autowired public Cities cities; }
package com.datang.springcode.beanextends; import org.springframework.stereotype.Component; @Component public class Capital extends City{ }
package com.datang.springcode.beanextends; import org.springframework.context.annotation.ComponentScan; @ComponentScan public class BeanExtendsConfig { }
上邊代碼片段,子bean的父類並沒有增加@Component注解,看結果。

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

最后java方式創建bean,則沒有繼承的概念。
package com.datang.springcode.beanextends2; public class Woman { }
package com.datang.springcode.beanextends2; public class Marriage { public Woman woman; public void setWoman(Woman woman) { this.woman = woman; } }
package com.datang.springcode.beanextends2; public class Lovers extends Marriage{ }
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; } }

但是在父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(); } }
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 { }

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>
<?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>

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 { }

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>

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>

4 國際化
content=歓迎いたします{0}來到{1},現在的語言環境是:{2}
content=歡迎{0}來到{1},現在的語言環境是:{2}
content=歡迎{0}來到{1},現在的語言環境是:{2}
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); } }
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; } }
首先我們需要創建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(); } }

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; } }
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"); } }
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,我啥都不干,就是隨便監聽"); } }
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 + "郵件已被發送..."); } }
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(); } }

這里講一下業務邏輯,當我們調用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>
首先配置的是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; } }
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; } }
<?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>
<?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>
@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); }



