1. 程式人生 > >Spring MVC系列-(3) Bean的裝配

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

3. 高階裝配Bean

3.1 Bean的作用域

預設情況下,Spring中的bean都是以單例的形式存在的,無論注入多少次,每次注入的都是同一個例項。

考慮到某些bean可能是可變的,Spring定義了不同的作用域,可以基於這些作用域建立不同的bean,

單例是預設的作用域,如果選擇@Scope註解選擇其他作用域,這可以和@Component@Bean一起使用。

@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容器中。

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

@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會被忽略。

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

@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通過反射機制利用

Spring為此提供了一個org.springframework.bean.factory.FactoryBean的工廠類介面,使用者可以通過實現該介面定製例項化Bean的邏輯。FactoryBean介面對於Spring框架來說佔用重要的地位,Spring自身就提供了70多個FactoryBean的實現。它們隱藏了例項化一些複雜Bean的細節,給上層應用帶來了便利。從Spring3.0開始,FactoryBean開始支援泛型,即介面宣告改為FactoryBean

以Bean結尾,表示它是一個Bean,不同於普通Bean的是:它是實現了FactoryBean

3.5 執行時注入

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

@Value

該註解的作用是將我們配置檔案的屬性讀出來,有@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

@Autowired

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。

擴充套件:

  1. @Resource

@Resource和Autowired一樣可以裝配bean
@Resource缺點: 不能支援@Primary功能,不能支援@Autowired(required = false)的功能

  1. @Inject

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

@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精品面試課程。

相關推薦

Spring MVC系列-(3) Bean裝配

3. 高階裝配Bean 3.1 Bean的作用域 預設情況下,Spring中的bean都是以單例的形式存在的,無論注入多少次,每次注入的都是同一個例項。 考慮到某些bean可能是可變的,Spring定義了不同的作用域,可以基於這些作用域建立不同的bean, 單例是預設的作用域,如果選擇@Scope註解

Spring學習系列(二) 自動化裝配Bean

can one bean 工作 顯式 實例 cnblogs con frame 一、Spring裝配-自動化裝配 @[email protected]/* */ 通過spring註解(@Component)來表明該類會作為組件類,並告知Spring要為這類創建b

Spring系列(二) Bean裝配

fig 攔截 首字母 文種 屬性 應用 管理 兩個 兩種 創建應用對象之間協作關系的行為稱為裝配(wiring), 這也是DI的本質. Spring中裝配Bean的方式 Spring提供了三種裝配Bean的方式. 隱式的Bean發現機制和自動裝配 Java Config

【轉】Spring MVC系列(五)之自定義數據綁定---HandlerMethodArgumentResolver

開閉 src pat 獲取參數 mvc .net 定義 開閉原則 淺析 介紹 前面幾節我們介紹了Spring MVC的幾種常見的數據綁定的方法,可以靈活地獲取用戶請求中的參數,例如@PathVariable,@ModelAttribute,@RequestPar

Spring MVC3Spring MVC 高級應用

ann 默認 chap coo cto 合作 視圖解析器 eric 不同   一、Spring MVC 的數據轉換和格式化   前面的應用,都只是用HandlerAdapter去執行處理器。     處理器和控制器不是一個概念,處理器是在控制器功能的基礎上加上了一層包裝,有

第1章:spring注入/1.3 Bean重寫

易學筆記 十年IT經驗個人學習筆記分享: 開發語言:C/C++/JAVA/PYTHON/GO/JSP WEB架構:Servlets/springMVC/springBoot/springClound 容器架構:Docker容器/Docker叢集/Docker與微服務整合/

spring mvc系列文章

Spring 不但讓 del() 方法處理這個請求,而且還將 id 請求引數在型別轉換後繫結到 del() 方法的 id 入參上。而 del() 方法的返回型別是 String,它將被解析為邏輯檢視的名稱。也就是說 Spring 在如何給處理方法入參自動賦值以及如何將處理方法返回值轉化為 ModelAndVi

Spring MVC系列(四)之session處理[email

介紹        在web開發中,session的重要性不言而喻,與cookie相比,session更加安全,處於伺服器端,開發者經常把一些重要的資訊放在session,方便在多次請求中方便的獲取資訊,Spring MVC 對session的支援也依舊很強大很靈活 Sp

Spring MVC 系列(四)——Spring MVC 與Ajax互動及重定向操作

 一、Spring MVC 與Ajax互動 一般情況下,Controller中方法返回值型別有兩種 1、String 直接跳轉到某View介面 2、Void 不需要進行頁面跳轉,直接訪問下一個方法

spring學習筆記(3)Bean命名、定義與配置

基於xml的配置 基礎配置 <bean id="id" name="name" class="full_name"> <property name="pname" value="pvalue" lazy-init="defalu

Spring MVC入門3——返回JSON或XML等多種檢視

上一篇的例子返回的都是HTML的頁面,是為人進行服務的頁面,供人進行閱讀的。如果我們希望將Web作為一種服務,返回資料為機器服務,例如返回JSON,XML等,我們應該如何實現呢? 很顯然,根據MVC的思想,以及Spirng MVC的架構設計,我們很容易就能想到,只需要修改

spring mvc 多個bean,或一個bean多個物件的資料繫結

一、前臺傳遞不同類不同物件 1、屬性名不同,可直接封裝進controller方法的物件引數(經驗證) 2、屬性名有重複,可在重複的類中設定一個值型別,後臺再去將值型別值賦值給例項變數(經驗證) 二、同一類多個物件集合 方法1、Json方式 方法2、新建一個類,該

Spring MVC系列之模型繫結(SpringBoot)(七)

前言 上一節我們在SpringBoot中啟用了Spring MVC最終輸出了HelloWorld,本節我們來講講Spring MVC中的模型繫結,這個名稱來源於.NET或.NET Core,不知是否恰當,我們暫且這樣理解吧。 @RequestParam VS  @PathVariable &n

Spring MVC系列之JDBC Demo(SpringBoot)(七)

前言 前面我們瞭解了Spring MVC的基本使用,其實和.NET或.NET Core MVC無異,只是語法不同而已罷了,本節我們將和和資料庫打交道,從最基礎的JDBC講解起,文中若有錯誤之處,還望指正。 JDBC Demo 我們需要下載三個包:JDBC驅動包(mysql-connector-java)、sp

Spring原始碼系列(二)--bean元件的原始碼分析

# 簡介 spring-bean 元件是 Spring IoC 的核心,我們可以使用它的 beanFactory 來獲取所需的物件,物件的例項化、屬性裝配和初始化等都可以交給 spring 來管理。 本文將從`DefaultListableBeanFactory.getBean(Class)`方法開始分析獲

Spring MVC系列-(5) AOP

![Spring.png](http://ww1.sinaimg.cn/large/a18449c6gy1gco2xb0bj9j20nn0cet8m.jpg) [toc] ## 5 AOP ### 5.1 什麼是AOP AOP(Aspect-Oriented Programming,面向切面程式設計)

6.Spring系列Bean的配置3

enc 1.7 ole lex mysq get style 後置 分配 一、配置Bean的兩種方式之使用XML配置Bean 1.在IOC容器中引入外部屬性文件 在IOC容器中,當配置 Bean 時, 有時需要在 Bean 的配置裏引入系統部署的相關信息(例如:文件路徑、

Spring入門篇——第3Spring Bean裝配(上)

第3章 Spring Bean裝配(上) 介紹Bean的作用域、生命週期、Aware介面、自動裝配和Resource等內容。 3-1 Spring Bean裝配之Bean的配置項及作用域       3-2 Spring B

Spring Bean3裝配方式

  Bean常用的裝配方式有3種: 基於xml的裝配 基於Annotation(註解)的裝配 基於Java類的裝配     基於xml的裝配 在xml檔案中配置Bean。 如果依賴很多,xml配置檔案會很臃腫,後期維護、升級不方便。自動裝配可解決這一問題

Spring系列bean的使用

ack 工廠 使用實例 自動裝配 繼承 放心 sys xmla 客戶端 一、Bean的定義 <bean id="userDao" class="com.dev.spring.simple.MemoryUserDao"/> 這是一個最簡單的 Bean 定義。它