默認狀況下,Spring中的bean都是以單例的形式存在的,不管注入多少次,每次注入的都是同一個實例。java
考慮到某些bean多是可變的,Spring定義了不一樣的做用域,能夠基於這些做用域建立不一樣的bean,mysql
單例是默認的做用域,若是選擇@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); } }
顧名思義,懶加載推遲加載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 } }
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.class
和LinCondition.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; } }
這節中,首先總結一下Spring中常見的注入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提供的方法及其簡單,僅提供了六種方法供客戶調用:
2. FactoryBean
通常狀況下,Spring經過反射機制利用
Spring爲此提供了一個org.springframework.bean.factory.FactoryBean的工廠類接口,用戶能夠經過實現該接口定製實例化Bean的邏輯。FactoryBean接口對於Spring框架來講佔用重要的地位,Spring自身就提供了70多個FactoryBean的實現。它們隱藏了實例化一些複雜Bean的細節,給上層應用帶來了便利。從Spring3.0開始,FactoryBean開始支持泛型,即接口聲明改成FactoryBean
以Bean結尾,表示它是一個Bean,不一樣於普通Bean的是:它是實現了FactoryBean
本節介紹Spring在運行時的兩種常見注入方式,@Value和@Autowired。
該註解的做用是將咱們配置文件的屬性讀出來,有@Value(「${}」)
和@Value(「#{}」)
兩種方式。
1. @Value(「${}」):注入的是外部配置文件對應的property
在application.propertites配置屬性以下:
在程序中動態讀取server.port屬性,
@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。
@w=400
接着在另外一個Controller類中注入UserBean的屬性。
@w=300
Spring中常利用@Autowired
完成依賴注入(DI), 對IOC容器中的各個組件的依賴關係賦值。
下面的例子中,是常見的DAO、Service、Controller模型,採用Autowired能夠方便的在Service層和Controller層中注入對應的Bean實例。
@Autowired實現原理就是:默認優先按類型去容器中找對應的組件,至關於anno.getBean(TestDao.class)去容器獲取id爲testDao的bean, 並注入到TestService的bean中;
可是當容器中有多個testDao時,使用默認的@Autowired就會發生異常,IOC容器此時沒法肯定哪一個bean做爲依賴注入的對象,Spring引入了Qualifier和Primary來解決這個問題。
假定有兩個testDao,其bean id分別爲testDao1和testDao2,此時可使用@Autowired和@Qualifier
結合來指定注入哪個bean,下面的例子中,指定bean id爲testDao,注意還能夠加上required=false
當容器中找不到這個bean時,也不會報錯,此時該對象注入失敗爲null。
若是不使用@Qualifier
,可使用@Primary
來指定默認的首選bean。此時經過getBean和autowired獲取到的都是@Primary
指定的bean。
當@Qualifier
和@Primary
共存時,@Qualifier
會按照bean id來獲取指定的bean,不會受到@Primary
的影響。此時使用getBean獲取到的就是@Primary
標識的bean。
擴展:
@Resource和Autowired同樣能夠裝配bean
@Resource缺點: 不能支持@Primary功能,不能支持@Autowired(required = false)的功能
@Inject和Autowired同樣能夠裝配bean, 支持@Primary功能, 可用於非spring框架.
@Inject缺點: 但不能支持@Autowired(required = false)
的功能,須要引入第三方包javax.inject
@w=350
Autowired屬於spring的, 不能脫離spring, @Resource和@Inject都是JAVA規範
推薦使用@Autowired。
@Component
主要和ComponentScan結合,用於自動檢測和配置Bean,Bean和被註解的類是一一對應的關係。
@Bean
用於顯式聲明一個單獨的Bean,而不是讓Spring自動完成該過程,經過該註解能夠將類的定義和Bean的聲明解耦。特別是使用第三方的庫時,只能經過@Bean來將某些類注入到容器中。
本文由『後端精進之路』原創,首發於博客 http://teckee.github.io/ , 轉載請註明出處
搜索『後端精進之路』關注公衆號,馬上獲取最新文章和價值2000元的BATJ精品面試課程。