Spring MVC系列-(3) Bean的裝配

Spring.png

3. 高級裝配Bean

3.1 Bean的做用域

默認狀況下,Spring中的bean都是以單例的形式存在的,不管注入多少次,每次注入的都是同一個實例。java

考慮到某些bean多是可變的,Spring定義了不一樣的做用域,能夠基於這些做用域建立不一樣的bean,mysql

Screen Shot 2020-01-30 at 3.44.24 PM.png

單例是默認的做用域,若是選擇@Scope註解選擇其餘做用域,這能夠和@Component@Bean一塊兒使用。git

@Configuration
public class Cap3MainConfig {
	//給容器中註冊一個bean, 類型爲返回值的類型, 默認是單實例
	/*
	 * prototype:多實例: IOC容器啓動的時候,IOC容器啓動並不會去調用方法建立對象, 而是每次獲取的時候纔會調用方法建立對象
	 * singleton:單實例(默認):IOC容器啓動的時候會調用方法建立對象並放到IOC容器中,之後每次獲取的就是直接從容器中拿(大Map.get)的同一個bean
	 * request: 主要針對web應用, 遞交一次請求建立一個實例
	 * session:同一個session建立一個實例
	 */
	@Scope("prototype")
	@Bean
	public Person person(){
		return new Person("vincent",20);
	}
}

3.2 Lazy懶加載

顧名思義,懶加載推遲加載Bean。默認狀況下,在IOC容器初始化時,會將各個Bean註冊到容器中;若是在定義Bean時,使用@Lazy聲明,則該Bean只有在第一次使用時,纔會被註冊到IOC容器中。github

下面的例子中,person實例將會在第一次被獲取的時候纔會初始化。web

@Configuration
public class Cap4MainConfig {
	//給容器中註冊一個bean, 類型爲返回值的類型, 默認是單實例
	/*
	 * 懶加載: 主要針對單實例bean:默認在容器啓動的時候建立對象
	 * 懶加載: 容器啓動時候不建立對象, 僅當第一次使用(獲取)bean的時候才建立被初始化
	 */
	@Lazy
	@Bean
	public Person person(){
		System.out.println("給容器中添加person.......");
		return new Person("vincent",20);
	}
}

可使用以下測試程序進行測試:面試

public class Cap4Test {
	@Test
	public void test01(){
		AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(Cap4MainConfig.class);
		
		String[] names = app.getBeanDefinitionNames();
		
		// 此時能夠獲取到person的name,可是person依然未實例化
		for(String name:names){
			System.out.println(name);
		}
		
		System.out.println("IOC容器建立完成........");
		
		// 實例化person
		app.getBean("person");//執行獲取的時候才建立並初始化bean
		
	}
}

3.3 Conditional條件註冊Bean

Spring4引入了@Conditional註解,用於條件化註冊Bean。若是給定的條件,計算結果爲true,就會建立這個bean,不然的話,bean會被忽略。spring

下面的例子中,將IOC容器註冊bean時, 當操做系統爲WINDOWS時,註冊Lison實例; 當操做系統爲LINUX時, 註冊James實例,此時要用得@Conditional註解進行定製化條件選擇註冊bean;sql

@Configuration
public class Cap5MainConfig {
	@Bean("person")
	public Person person(){
		System.out.println("給容器中添加person.......");
		return new Person("person",20);
	}
	
	@Conditional(WinCondition.class)
	@Bean("lison")
	public Person lison(){
		System.out.println("給容器中添加win.......");
		return new Person("win",58);
	}
	@Conditional(LinCondition.class)
	@Bean("james")//bean在容器中的ID爲james, IOC容器MAP,  map.put("id",value)
	public Person james(){
		System.out.println("給容器中添加mac.......");
		return new Person("mac",20);
	}
}

注意到,咱們須要本身實現對應的WinCondition.classLinCondition.class類,以其中的一個爲例,以下能夠看到,須要實現本身的match函數,後端

public class LinCondition implements Condition{
	/*
	*ConditionContext: 判斷條件可使用的上下文(環境)
	*AnnotatedTypeMetadata: 註解的信息
	*/
	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		// TODO 是否爲WINDOW系統
		//能獲取到IOC容器正在使用的beanFactory
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		//獲取當前環境變量(包括咱們操做系統是WIN仍是LINUX??)
		Environment environment = context.getEnvironment();
		String os_name = environment.getProperty("os.name");
		if(os_name.contains("Mac")){
			return true;
		}
		return false;
	}
}

3.4 import註冊Bean

這節中,首先總結一下Spring中常見的注入Bean的方法。設計模式

  1. @Bean: [導入第三方的類或包的組件],好比Person爲第三方的類, 須要在咱們的IOC容器中使用
  2. 包掃描+組件的標註註解(@ComponentScan: @Controller, @Service @Repository,@Component),通常是針對咱們本身寫的類。
  3. @Import:快速給容器導入一個組件
  • a, @Import(要導入到容器中的組件):容器會自動註冊這個組件,bean的id爲全類名
  • b, ImportSelector:是一個接口,返回須要導入到容器的組件的全類名數組。
  • c, ImportBeanDefinitionRegistrar:能夠手動添加組件到IOC容器, 全部Bean的註冊可使用BeanDifinitionRegistry,只須要實現。ImportBeanDefinitionRegistrar接口便可
  1. 使用Spring提供的FactoryBean(工廠bean)進行註冊

前兩種方法在上一章已經介紹了,如今主要介紹剩下兩類。

下面的配置類中,直接將Dog和Cat import到配置中,自己配置類中也定義了person的實例bean以及自定義的factoryBean。

@Configuration
@Import(value = { Dog.class,Cat.class, JamesImportSelector.class,
JamesImportBeanDefinitionRegistrar.class })
public class Cap6MainConfig {
	//容器啓動時初始化person的bean實例
	@Bean("person")
	public Person persond(){
		System.out.println("aaaaaaaaaaaa");
		return new Person("james",20);
	}

	@Bean
	public JamesFactoryBean jamesFactoryBean(){
		return new JamesFactoryBean();
	}
}

JamesImportSelector.class實現中,只須要返回全部須要import的class類名便可。

public class JamesImportSelector implements ImportSelector{
	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata){
		//返回全類名的bean
		return new String[]{"com.enjoy.cap6.bean.Fish","com.enjoy.cap6.bean.Tiger"};
	}
}

JamesImportBeanDefinitionRegistrar.class中,根據須要能夠手動注入須要的bean實例,

public class JamesImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

	/*
	*AnnotationMetadata:當前類的註解信息
	*BeanDefinitionRegistry:BeanDefinition註冊類
	*    把全部須要添加到容器中的bean加入;
	*    @Scope
	*/
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		boolean bean1 = registry.containsBeanDefinition("com.enjoy.cap6.bean.Dog");
		boolean bean2 = registry.containsBeanDefinition("com.enjoy.cap6.bean.Cat");
		//若是Dog和Cat同時存在於咱們IOC容器中,那麼建立Pig類, 加入到容器
		//對於咱們要註冊的bean, 給bean進行封裝,
		if(bean1 && bean2){
			RootBeanDefinition beanDefinition = new RootBeanDefinition(Pig.class);
			registry.registerBeanDefinition("pig", beanDefinition);
		}
	}
}

注意到上面,也能夠經過FactoryBean的方法來將所須要的bean注入到IOC容器中,在這其中,須要手動實現其中的getObject等方法。

public class JamesFactoryBean implements FactoryBean<Monkey>{

	@Override
	public Monkey getObject() throws Exception {
		// TODO Auto-generated method stub
		return new Monkey();
	}

	@Override
	public Class<?> getObjectType() {
		// TODO Auto-generated method stub
		return Monkey.class;
	}
	
	@Override
	public boolean isSingleton() {
		return true;
	}
}

下面是實際的測試程序,須要注意的是,直接使用getBean(bean name)是取出FactoryBean裏面封裝的Monkey實例,若是須要拿到FactoryBean自己,須要加上&符號。

public class Cap6Test {
	@Test
	public void test01(){
		AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(Cap6MainConfig.class);
		
		System.out.println("IOC容器建立完成........");
		
		
		Object bean1 = app.getBean("jamesFactoryBean");
		Object bean2 = app.getBean("jamesFactoryBean");//取Monkey bean
		System.out.println("bean的類型="+bean1.getClass());
		System.out.println(bean1 == bean2);

		Object bean3 = app.getBean("&jamesFactoryBean");//取factoryBean
		System.out.println("bean的類型="+bean3.getClass());
		
		// 打印輸出全部bean
		String[] beanDefinitionNames = app.getBeanDefinitionNames();
		for(String name:beanDefinitionNames){
			System.out.println(name);
		}
	}
}

Spring中出現了BeanFactory和FactoryBean,下面對二者的區別進行解釋:

BeanFactory是個Factory,也就是IOC容器或對象工廠,FactoryBean是個Bean。在Spring中,全部的Bean都是由BeanFactory(也就是IOC容器)來進行管理的。
FactoryBean是一個Bean,這個Bean不是簡單的Bean,而是一個能生產或者修飾對象生成的工廠Bean,它的實現與設計模式中的工廠模式和修飾器模式相似。

1. BeanFactory

BeanFactory,以Factory結尾,表示它是一個工廠類(接口),它負責生產和管理bean的一個工廠。在Spring中,BeanFactory是IOC容器的核心接口,它的職責包括:實例化、定位、配置應用程序中的對象及創建這些對象間的依賴。

BeanFactory只是個接口,並非IOC容器的具體實現,可是Spring容器給出了不少種實現,如 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等,其中XmlBeanFactory就是經常使用的一個,該實現將以XML方式描述組成應用的對象及對象間的依賴關係。XmlBeanFactory類將持有此XML配置元數據,並用它來構建一個徹底可配置的系統或應用。

ApplicationContext包含BeanFactory的全部功能,一般建議比BeanFactory優先 。ApplicationContext以一種更向面向框架的方式工做以及對上下文進行分層和實現繼承,ApplicationContext包還提供瞭如下的功能:

  • MessageSource, 提供國際化的消息訪問
  • 資源訪問,如URL和文件
  • 事件傳播
  • 載入多個(有繼承關係)上下文 ,使得每個上下文都專一於一個特定的層次,好比應用的web層;

BeanFactory提供的方法及其簡單,僅提供了六種方法供客戶調用:

  • boolean containsBean(String beanName) 判斷工廠中是否包含給定名稱的bean定義,如有則返回true
  • Object getBean(String) 返回給定名稱註冊的bean實例。根據bean的配置狀況,若是是singleton模式將返回一個共享實例,不然將返回一個新建的實例,若是沒有找到指定bean,該方法可能會拋出異常
  • Object getBean(String, Class) 返回以給定名稱註冊的bean實例,並轉換爲給定class類型
  • Class getType(String name) 返回給定名稱的bean的Class,若是沒有找到指定的bean實例,則排除NoSuchBeanDefinitionException異常
  • boolean isSingleton(String) 判斷給定名稱的bean定義是否爲單例模式
  • String[] getAliases(String name) 返回給定bean名稱的全部別名

2. FactoryBean

通常狀況下,Spring經過反射機制利用 的class屬性指定實現類實例化Bean,在某些狀況下,實例化Bean過程比較複雜,若是按照傳統的方式,則須要在 中提供大量的配置信息。配置方式的靈活性是受限的,這時採用編碼的方式可能會獲得一個簡單的方案。

Spring爲此提供了一個org.springframework.bean.factory.FactoryBean的工廠類接口,用戶能夠經過實現該接口定製實例化Bean的邏輯。FactoryBean接口對於Spring框架來講佔用重要的地位,Spring自身就提供了70多個FactoryBean的實現。它們隱藏了實例化一些複雜Bean的細節,給上層應用帶來了便利。從Spring3.0開始,FactoryBean開始支持泛型,即接口聲明改成FactoryBean 的形式

以Bean結尾,表示它是一個Bean,不一樣於普通Bean的是:它是實現了FactoryBean 接口的Bean,根據該Bean的ID從BeanFactory中獲取的其實是FactoryBean的getObject()返回的對象,而不是FactoryBean自己,若是要獲取FactoryBean對象,請在id前面加一個&符號來獲取。

3.5 運行時注入

本節介紹Spring在運行時的兩種常見注入方式,@Value和@Autowired。

@Value

該註解的做用是將咱們配置文件的屬性讀出來,有@Value(「${}」)@Value(「#{}」)兩種方式。

1. @Value(「${}」):注入的是外部配置文件對應的property

在application.propertites配置屬性以下:

Screen Shot 2020-02-04 at 10.29.30 PM.png

在程序中動態讀取server.port屬性,

Screen Shot 2020-02-04 at 10.31.09 PM.png@w=300

這樣server.port=8000就注入到了對應的參數中。

2. @Value(「#{}」):經常使用的方式是#{obj.property ? :default_value},注意與上一種方式不一樣的是,這種方式中的obj須要是一個對象。也能夠在其中填寫SpEL表達式

Spring表達式語言全稱爲「Spring Expression Language」,縮寫爲「SpEL」,相似於Struts2x中使用的OGNL表達式語言,能在運行時構建複雜表達式、存取對象圖屬性、對象方法調用等等,而且能與Spring功能完美整合,如能用來配置Bean定義。

下面的例子中,首先定義UserBean並從property文件中讀取屬性,屬性值爲mysql。

Screen Shot 2020-02-04 at 10.42.30 PM.png@w=400

接着在另外一個Controller類中注入UserBean的屬性。

Screen Shot 2020-02-04 at 10.44.04 PM.png@w=300

@Autowired

Spring中常利用@Autowired完成依賴注入(DI), 對IOC容器中的各個組件的依賴關係賦值。

下面的例子中,是常見的DAO、Service、Controller模型,採用Autowired能夠方便的在Service層和Controller層中注入對應的Bean實例。

Screen Shot 2020-02-05 at 10.15.23 AM.png

@Autowired實現原理就是:默認優先按類型去容器中找對應的組件,至關於anno.getBean(TestDao.class)去容器獲取id爲testDao的bean, 並注入到TestService的bean中;

可是當容器中有多個testDao時,使用默認的@Autowired就會發生異常,IOC容器此時沒法肯定哪一個bean做爲依賴注入的對象,Spring引入了QualifierPrimary來解決這個問題。

假定有兩個testDao,其bean id分別爲testDao1和testDao2,此時可使用@Autowired和@Qualifier結合來指定注入哪個bean,下面的例子中,指定bean id爲testDao,注意還能夠加上required=false當容器中找不到這個bean時,也不會報錯,此時該對象注入失敗爲null。

Screen Shot 2020-02-05 at 11.20.09 AM.png

若是不使用@Qualifier,可使用@Primary來指定默認的首選bean。此時經過getBean和autowired獲取到的都是@Primary指定的bean。

@Qualifier@Primary共存時,@Qualifier會按照bean id來獲取指定的bean,不會受到@Primary的影響。此時使用getBean獲取到的就是@Primary標識的bean。

擴展:

  1. @Resource

@Resource和Autowired同樣能夠裝配bean
@Resource缺點: 不能支持@Primary功能,不能支持@Autowired(required = false)的功能

Screen Shot 2020-02-05 at 11.38.40 AM.png

  1. @Inject

@Inject和Autowired同樣能夠裝配bean, 支持@Primary功能, 可用於非spring框架.
@Inject缺點: 但不能支持@Autowired(required = false)的功能,須要引入第三方包javax.inject

Screen Shot 2020-02-05 at 11.41.57 AM.png@w=350

Autowired屬於spring的, 不能脫離spring, @Resource和@Inject都是JAVA規範
推薦使用@Autowired。

3.6 @Bean Vs @Component

@Component主要和ComponentScan結合,用於自動檢測和配置Bean,Bean和被註解的類是一一對應的關係。

@Bean用於顯式聲明一個單獨的Bean,而不是讓Spring自動完成該過程,經過該註解能夠將類的定義和Bean的聲明解耦。特別是使用第三方的庫時,只能經過@Bean來將某些類注入到容器中。


本文由『後端精進之路』原創,首發於博客 http://teckee.github.io/ , 轉載請註明出處

搜索『後端精進之路』關注公衆號,馬上獲取最新文章和價值2000元的BATJ精品面試課程

後端精進之路.png

相關文章
相關標籤/搜索