歡迎光臨Spring時代(一) 上柱國IOC列傳

本篇咱們就開始學習Java領域大名鼎鼎的Spring Framework,本篇介紹的Spring Framework中的IOC(控制反轉,也稱依賴注入),原本想用初遇篇,這個題目的,可是一想這個題目不太見名知義,就改爲上柱國列傳了。爲何叫上柱國IOC列傳呢,由於以爲上柱國這個名字挺好聽。
上柱國: 原義爲自春秋起爲軍事武裝的高級統帥,引伸義爲功勳的榮譽稱號,戰國時楚、趙置,位令尹、相國下,甚尊。原爲保衛國都之官

緣起

2002年 Rod Johnson在2002年編著的《Expert One-On-One J2EE Design And Development》一書中對,對Java EE正統框架(EJB)臃腫、低效、脫離現實的學院派提出了質疑,而後以該書爲指導思想,編寫了interface21框架。而後在interface21框架的基礎上,通過從新設計,於2004年發佈。到如今開始已經十八年左右了。java

開發者: Spring Framework 請回答

《歡迎光臨Spring時代-緒論》中咱們提出了幾個關於如何取對象的問題,這裏咱們再回憶一下:正則表達式

  • 對象之間有複雜的依賴關係的時候,在不但願是硬編碼的狀況下,如何取對象才能作到優雅和可配置化?
  • 知道如何建立對象,可是沒法把握建立對象的時機。我如今就但願把如何建立對象代碼交給"負責何時建立的代碼"。後者在對應的時機,就調用對應的建立對象函數。
  • 雖然Java不用操心內存回收問題,可是我仍是但願能節省資源一下,在控制器這一層,不但願處理的每一個請求都new一下對應的service實現類,我但願能將該Service作成單例模式,可否在作成單例的同時又作到讓取對象優雅呢?

Spring Framework: IOC容器就是答案

在我剛學Spring框架的時候,很多視頻都會說,咱們並不在new對象,而是將對象放進IOC容器中,你要取的時候,向IOC容器中取便可。這裏咱們先從實例入手再來解釋IOC。結合着例子來解釋IOC會更加易懂。

準備工做

本篇咱們還基於maven來作示例,基本上開發Spring Framework的程序基本上就只須要五個jar包就夠了,分別是下面這五個:spring

  • spring-context
  • spring-core
  • spring-aop
  • spring-beans
  • spring-expression

此次咱們選的版本都是5.2.8.RELEASE。
若是你不會用maven,而後請參考個人這篇博客:express

若是暫時還不想學maven,還想作jar包下載。那麼有兩種形式能夠下載到對應的jar包:segmentfault

  • Spring Framework 官方maven庫

瀏覽器訪問這個網址: https://maven.springframework...

下載完粘貼到對應的lib目錄下便可。數組

  • 去maven的中央倉庫下載
輸入上面的jar包名字,粘貼對應的依賴便可。
開發工具我這裏用的是IDEA,提示比較強大,用着比較順手。
若是你不習慣用IDEA,是Eclipse黨,這邊推薦你下載Spring官方出品的開發工具STS,也是在Eclipse的基礎開發的,開發Spring框架的程序更加快速。
瀏覽器輸入: https://spring.io/tools/

本文的示例都是基於IDEA來作的,若是不會用IDEA,想用STS,能夠參考文末後面的參考資料,是視頻。

第一個Spring Framework程序


通常Spring框架的這個配置文件,咱們都命名爲applicationContext.xml,這是一種大體的約定。
上面的問題是如何優雅的取對象,在取以前你首先就得存,向applicationContext.xml放對象。怎麼放?
像下面這樣放:

我建了一個Student類,而後裏面有name和age屬性,無參和有參構造函數,get和set函數,重寫了toString方法。
bean裏的id是惟一的,class是Student類的全類名(包名+類名)。
如何取:瀏覽器

public class SpringDemo {
    public static void main(String[] args) {
        // 加載配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = (Student) applicationContext.getBean("student");
        System.out.println(student);
    }
}

打印結果:
springboot

這就叫DI和IOC

咱們直接new,在Spring的配置文件配置參數,而後間接取對象的方式, 咱們稱之爲控制反轉(IOC Inversion of Control)或依賴注入(DI Dependency Injection)。首先解釋一下爲何有兩個稱呼,剛開始是隻有控制反轉,後來發現控制反轉這個概念有點難以理解,就在一次大會上講控制反轉改成依賴注入。
那什麼是控制反轉,咱們能夠認爲new 對象是一種控制權,而後控制反轉就是咱們將new對象的控制權交給Spring Framework。依賴注入呢? 其實也是同樣,咱們能夠理解爲在Spring的配置文件配置完對象以後,Spring將該對象給須要該對象的對象,此時就回答了上面咱們提出的第二個問題:session

對象之間有複雜的依賴關係的時候,在不但願是硬編碼的狀況下,如何取對象才能作到優雅和可配置化?
若是對象之間有複雜的依賴關係,那麼就請將這種複雜的依賴關係當作配置參數同樣,放在Spring的配置文件中,而後再從Spring IOC容器中取值,咱們此時姑且就能夠將配置文件當作IOC容器,放置了對象。

對象之間有依賴關係該如何配置呢?

用上面的配置只能解決簡單的值,那若是某個對象的某個屬性也是對象類型的呢? Spring也想到了,當對象的屬性是屬性用ref注入,像下面這樣:app

<bean id = "studentCard" class = "org.example.StudentCard">
        <property name = "id" value="1"></property>
        <property name = "cardNo" value="code01"></property>
     </bean>

    <bean id="student" class="org.example.Student">
        <property name = "name" value = "zs"></property>
        <property name = "age" value = "23"></property>
        <property name = "studentCard" ref = "studentCard"></property>
    </bean>

普通屬性用value,那麼引用類型就用ref,ref的值是配置文件中bean標籤的id屬性,因此在applicationContext.xml中id禁止重複。

經過註解將對象放入IOC容器中

咱們上面的第三個問題,咱們Dao層的對象在對應的Service只須要一份就能夠了,Spring容器中的對象默認都是單例的。
那Dao層有的時候都沒有屬性,咱們還要寫在配置文件中嗎?Spring也想到了,提供了下面幾個註解:

@Controller 對應控制層
@Service 對應服務層
@Component 通用註解,若是你不肯定這個對象屬於那一層的話,就用這個。
@Repository 對應dao層

而後在配置文件中加入:

<!--配置掃描器,base-package放須要掃描的包,指定了包以後,Spring會掃描。
    該包下面的類,若是有以上四個註解,那麼就會將對應的類加入到容器中,id默認爲類名首字母轉小寫。
    多個包也能夠寫,用逗號隔開便可。若是寫是一個包下面有多個包,
    好比org.example.service,org.example.dao。寫到二級包:org.example。Spring會自動掃描全部的子包。
 -->
    <context:component-scan base-package="org.example"/>



因此咱們上面的第三個問題就獲得了回答,咱們寫Service層須要對應的dao層對應的時候就能夠這麼寫:

@Service
public class StudentService {
    // 僞裝Student是dao層,被打上@Autowired的屬性,Spring在掃描的時候會自動去容器去尋找對應的類型
    // 而後給該屬性注入值,因此若是你有兩個IOC容器中有兩個Student對象,那麼可能就會報錯
    // Spring官方並不推薦如此注入
    @Autowired
    private Student student;
    
    public void print(){
        System.out.println(student);
    }
}

官方推薦的注入方式是 @Autowired出如今set方法或構造方法上:

@Service
public class StudentService {
    private Student student;
    @Autowired
    public StudentService(Student student) {
        this.student = student;
    }
    public void print(){
        System.out.println(student);
    }
}

至此咱們上面提出的第一個問題和第三個問題獲得瞭解決:

  • 對象有複雜的依賴關係,咱們在配置文件中佩,在調用的類中,用Autowired自動注入,這很優雅。
  • 對應的Serveice層經過註解就能將dao層注入,就不用再每一個業務方法中,重複new了。

而後有不懂SpringMVC框架的同窗這裏可能就會問了,那我在Servlet中該如何取IOC容器中的對象啊,Servlet的初始化又不像main函數,有個明確的入口,用戶是能夠從任意一個網頁進入的。對於這個問題能夠參看:

Spring視頻教程的P十一、P十二、P1三、P14。

接着咱們回答第二個問題,難以把握對象的建立時機的這個問題,對於這個問題,Spring框架的答案是條件註解。
IOC容器有兩種形式,一種是基於配置文件(咱們上面用的就是),一種是基於註解。條件註解是基於註解形式的,查了一些資料仍是沒找到如何用配置文件實現條件註解的。可是基於配置文件的IOC還須要再補充一些,因此下面是先將配置文件形式的講解完畢後,纔會講基於註解的,條件註解也是基於註解。

基於XML的依賴注入

不一樣的注入方式

咱們知道建立一個對象是有幾種不一樣的方式的:

  1. 經過無參構造函數,而後經過set函數設置值
  2. 經過有參構造函數
  3. 經過反射
  4. 經過序列化

一樣的在配置文件中配置對象參數的也有幾種形式,上面的property的配置的形式就是經過第一種方式來建立對象的。有興致的同窗能夠測試下。
接下來咱們介紹的就是經過第二種方式將對象放入IOC容器中:

<bean id = "studentCard" class = "org.example.StudentCard">
    <constructor-arg value="1" index = "0"/>
    <constructor-arg value="11" index = "1"/>
 </bean>

constructor-arg有四個屬性:

  • value (具體的值,不加index的話,具體的值和構造函數要求的參數類型要保持一致,默認狀況下標籤的前後順序和構造函數保持一致)
  • index 用於指定給構造函數的第幾個參數
  • type 指定參數類型
  • name 用於指定參數名
  • ref 這個講過是指引用類型

經過反射產生對象:

<bean id = "studentCard" class = "org.example.StudentCard" p:id="1" p:cardNo="23">
 </bean>


引用類型經過p:屬性名-ref來設定IOC容器中bean的ID

將集合放入對應的IOC容器中

首先咱們準備一個集合的類,構造函數省略,get和set方法省略:

public class CollectionDemo {
    private List<String> list;
    private String[] arrayString;
    private Set<String> set;
    private Map<String, Object> map;
    private Properties properties;
}

Spring配置文件:

<bean id = "collectionDemo"  class = "org.example.CollectionDemo">
            <property name="list">
                <value>
                    14
                </value>
            </property>
            <property name="arrayString">
                <array>
                    <value>ddd</value>
                </array>
            </property>
            <property name = "set">
                <set>
                    <value>aaa</value>
                </set>
            </property>
            <property name="map">
                <map>
                    <entry>
                        <key>
                            <value>zs</value>
                        </key>
                         <value>zs</value>
                    </entry>
                </map>
            </property>
            <property name = "properties">
                <props>
                    <prop key="aa">bb</prop>
                    <prop key="cc">dd</prop>
                </props>
            </property>
    </bean>

示例:

public class SpringDemo {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        CollectionDemo collectionDemo = (CollectionDemo)applicationContext.getBean("collectionDemo");
        System.out.println(collectionDemo);
    }
}


剛學Spring框架的時候,視頻上說控制反轉,咱們本身再也不new對象,我覺得就是真的再也不new了,就在配置文件裏面配就好了,後來隨着編碼量的上升,才發現這是對Spring框架的一種誤解,是有複雜依賴關係的咱們在配置文件裏面配,像你要是想用個HashMap,就沒必要了。

特殊值的注入問題

咱們在配置文件配置對象的時候,用的值都和XML預約義的符號值不衝突,什麼意思呢? 假設我給對象的值是<,就會報錯。

咱們上面配置Student對象的參數的時候,咱們用的是這種:

<bean id="student" class="org.example.Student">
    <property name = "name" value = "zs"></property>
    <property name = "age" value = "23"></property>
    <property name = "studentCard" ref = "studentCard"></property>
</bean>

咱們稱之爲value屬性注入,其實還能夠這麼寫:

<bean id="student" class="org.example.Student">
        <property name = "name" >
            <value type="java.lang.String">zs</value>
        </property>
        <property name = "age" value = "23"></property>
        <property name = "studentCard" ref = "studentCard"></property>
    </bean>

咱們稱之爲value子標籤注入。 二者的區別以下:

因此當咱們配置的屬性值是< 這個符號的時候咱們就能夠這麼寫:

<bean id="student" class="org.example.Student">
    <property name = "name" >
        <value type="java.lang.String">z&lt;s</value>
    </property>
</bean>

也能夠這麼寫:

<bean id="student" class="org.example.Student">
        <property name = "name" >
            <value type="java.lang.String"><![CDATA[z<3]]></value>
        </property>
    </bean>

那我要給屬性的值注入null怎麼辦? 方法有兩個

  • 不給值(property 標籤都不寫,寫標籤value裏面啥都不寫,若是是String的話,默認給的是空字符串)
  • 用null標籤
<bean id="student" class="org.example.Student" autowire = "byName">
        <property name = "name" >
                <null/>
        </property>
  </bean>

各類類型的自動注入

上面咱們提到在Spring的配置文件中配置對象的屬性值的時候,若是屬性值是對象類型的,那麼用ref就能夠了,其實這個也能夠不寫,用自動注入就能夠了,用這種自動注入也有兩種方式:

  1. byName Spring會自動的去尋找容器中id爲對象屬性類名首字母轉小寫的對象。
<bean id = "studentCard" class = "org.example.StudentCard">
        <property name="id" value="1"></property>
        <property name="cardNo" value="zs"></property>
     </bean>

    <bean id="student" class="org.example.Student" autowire = "byName">
        <property name = "name" >
            <value type="java.lang.String"><![CDATA[z<3]]></value>
        </property>
        <property name="age">
            <value>34</value>
        </property>
    </bean>

運行結果再也不展現,假設你把第一個bean標籤的id改成studentCard1,那麼就注入不了。

  1. byType 按類型,自動去尋找匹配的類型,若是你有兩個StudentCard類型,也是沒法注入。
<bean id="student" class="org.example.Student" autowire = "byType">
        <property name = "name" >
            <value type="java.lang.String"><![CDATA[z<3]]></value>
        </property>
        <property name="age">
            <value>34</value>
        </property>
    </bean

基於註解的依賴注入

@Bean+方法

在xml裏面限制仍是挺多的,若是你不當心寫錯了屬性名,那麼也是到運行時才能發現錯誤,若是你不當心給錯了類型值,也是到運行時才能發現錯誤,好比屬性是數字類型,你給了一個字符串。不少時候咱們都但願儘量早的發現錯誤,那麼咱們的配置文件能不能變換一種形式呢? 用代碼作配置文件怎麼樣呢? 好啊,很好的想法啊,那咱們就用代碼作配置文件吧。

@Configuration
public class SpringConfig {
     @Bean(name = "studentCard")
    public StudentCard studentCard(){
        return  new StudentCard(11,"22");
    }
    @Bean
    public Student student(@Qualifier("studentCard") StudentCard studentCard){
        return new Student(20,"zs",studentCard);
    }
    @Bean
    public StudentCard studentCard2(){
        return  new StudentCard(11,"22");
    }
}
private static void annotationTest() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        Student student = applicationContext.getBean("student", Student.class);
        System.out.println(student);
    }

運行結果:

  1. 問如何讓一個類成爲一個配置文件?
給一個類打上註解@Configuration
  1. 如何將bean標籤移植到配置類中?
將原先的bean標籤變成方法,方法返回對象類型便可。同時方法要加上@Bean註解。方法名默認爲id。
  1. 那我想自定義id名,能夠嗎?
能夠,在@bean標籤中,用name屬性自定義就好
  1. 那引用類型的屬性注入怎麼辦?
像上面同樣,在方法參數中寫對應的參數便可。
也能夠調用對應的方法來完成注入。
你能夠本身new,可是爲何不直接用IOC容器的呢。
  1. 那我有兩個對象,都屬於一個類。 我想指定一個對象注入怎麼辦?
@Qualifier中指定對象名便可。

@import 、FactoryBean、ImportBeanDefinitionRegistrar、ImportSelector

@import(註解) 、FactoryBean(接口)、ImportBeanDefinitionRegistrar(接口)、ImportSelector(接口)是Spring提供的將對象加入IOC容器的另外方式。

  • @import
@Configuration
@ComponentScan(basePackages = "org.example")
// ImportTest是我建的一個空類,用來測試@import,value是一個數組
@Import(value = {ImportTest.class})
public class SpringConfig {
}

測試一下:

private static void annotationPrintAllBean() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        String[] beanNames = applicationContext.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            System.out.println(beanName);
        }
    }

測試結果:

這種方式加進來的bean名是全類名。

  • ImportSelector簡述

首先實現ImportSelector接口:

public class MyImportSelector implements ImportSelector {
    // 最後返回的即爲須要加入到容器的類名
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{ImportTest.class.getName()};
    }
}

而後在配置類上引入:

@Configuration
@ComponentScan(basePackages = "org.example")
@Import(value = MyImportSelector.class)
public class SpringConfig {
}

測試代碼:

private static void annotationPrintAllBean() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        String[] beanNames = applicationContext.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            System.out.println(beanName);
        }
    }

測試結果:

AnnotationMetadata 中攜帶打上@import註解的配置類上的元信息。

  • ImportBeanDefinitionRegistrar概述

先實現接口:

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // BeanDefinitionRegistry能夠將bean註冊進IOC工廠
        // 咱們須要準備一個BeanDefinition。
        BeanDefinition beanDefinition = new RootBeanDefinition(ImportTest.class);
        registry.registerBeanDefinition("importTest",beanDefinition);
    }
}

配置類引入:

@Configuration
@ComponentScan(basePackages = "org.example")
@Import(value = MyImportBeanDefinitionRegistrar.class)
public class SpringConfig {
}

測試代碼:

private static void annotationPrintAllBean() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        String[] beanNames = applicationContext.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            System.out.println(beanName);
        }
    }

測試結果:

  • FactoryBean

FactoryBean是一個接口,還有一個接口叫BeanFactory。
反射是框架的靈魂,有的時候,某個bean重複性的屬性太多,在配置文件裏面配置也是一件讓人煩心的事情,可是程序不是擅長作重複工做嗎? 咱們可否少寫點,從配置文件中讀,而後用程序來作這種重複性工做呢?這也就是FactoryBean接口作的事情。

@Component
public class MyFactory implements FactoryBean<Car> {

    private String cardInfo;

    public MyFactory() {
        // 僞裝從Spring的配置文件中讀到了值。
        this.cardInfo = "brand,100,200.12";;
    }

    /**
     * 向IOC容器中放入對象
     * @return
     * @throws Exception
     */
    @Override
    public Car getObject() throws Exception {
        Car car = new Car();
        String[] cardInfoArray = cardInfo.split(",");
        car.setBrand(cardInfoArray[0]);
        car.setMaxSpeed(Integer.parseInt(cardInfoArray[1]));
        car.setPrice(Double.parseDouble(cardInfoArray[2]));
        return car;
    }

    /**
     * 向IOC容器返回指定的類型
     * @return
     */
    @Override
    public Class<?> getObjectType() {
        return Car.class;
    }

    /**
     * 設置是不是單例模式
     * @return
     */
    @Override
    public boolean isSingleton() {
        return false;
    }
}

測試代碼:

private static void testFactoryBean() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        // 表明取MyFactory
        MyFactory myFactory = applicationContext.getBean("&myFactory", MyFactory.class);
        // 不加&表明取工廠中放入的bean
        Car car = applicationContext.getBean("myFactory", Car.class);
        System.out.println(myFactory);
        System.out.println(car);
    }

測試結果:

掃描包移植-@ComponentScan

排除某些類

在配置類上加上:

@ComponentScan(basePackages = "org.example", excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Service.class})})
  • basePackages: 默認掃描本包及其子包。
  • excludeFilters : 排除的類,要求兩個屬性,第一個是type,能夠理解爲策略。

是一個枚舉類型,值都在FilterType中,一共有六種:

  • ANNOTATION 過濾打上指定註解(@Controller,@Component,@Repository,@Service)上的類。
  • ASSIGNABLE_TYPE: 指定的類型,已經寫在配置類中的,好比說Student類,沒法排除。默認應當是先加載配置文件中的類,而後在根據掃描的包,掃描類,去將要加入到IOC容器的對象,加入到IOC容器中。
  • ASPECTJ 按照Aspectj的表達式
  • REGEX 按照正則表達式
  • CUSTOM 自定義規則

我建了一個類,打上了Service註解,如今咱們來測試下:

@Configuration
@ComponentScan(basePackages = "org.example", excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Service.class})})
// @ComponentScan(basePackages = "org.example", excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {StudentDao.class})})
// @ComponentScan(basePackages = "org.example", includeFilters= {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {StudentDao.class})},useDefaultFilters = false)
// @ComponentScan(basePackages = "org.example", includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM,value = {MyFilter.class})},useDefaultFilters = false)
public class SpringConfig {
    @Bean(name = "studentCard")
    public StudentCard studentCard(){
        return  new StudentCard(11,"22");
    }
    @Bean
    public Student student(@Qualifier("studentCard") StudentCard studentCard){
        return new Student(20,"zs",studentCard);
    }
    @Bean
    public StudentCard studentCard2(){
        return  new StudentCard(11,"22");
    }
}
private static void annotationTest() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        // 獲取IOC容器中全部的bean
        String[] beanNameArray = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanNameArray) {
            System.out.println(beanDefinitionName);
        }
    }

測試結果:

沒有service了。
下面解除第二個@ComponentScan的註釋,將第一個@ComponentScan的註釋解除掉。再度測試:

會發現StudentDao沒了。

只包含某些類

與排除指定的類是相似的,只不過Spring默認會加載子包上須要加入到IOC容器中的類,也就是說你想只包含的類在basePackages下面,那麼這個包含就是無效的。因此咱們須要經過useDefaultFilters來禁止Spring的默認行爲。
咱們註釋掉其餘的@ComponentScan,只讓第三個@ComponentScan解除註釋。測試一下:

會發現打上@Service類的對象沒了。

自定義規則排除或包含某些類

自定義規則要實現TypeFilter,像下面這樣:

public class MyFilter implements TypeFilter {
    // 返回true加入到IOC容器中
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        // 獲取掃描的元數據類型
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        // 每掃到子包下的一個類,這個方法就會被調用。
        String className = classMetadata.getClassName();
        // 只要類名中包含Student的,我就加入到容器中
        if (className.contains("Student")){
            return true;
        }else{
            return false;
        }
    }
}

仍是上面的配置類, 咱們解除第四個@ComponentScan的註釋,其餘的所有註釋。測試一下。

HomeStudentCondition是我本身建的類,沒加咱們介紹的Spring提供的註解,也加進來了。

條件註解

條件註解可讓某些對象在某些條件知足的狀況下,才加入到IOC容器中(等價於建立該對象),若是該條件不知足則該對象不建立,也就是不加入到對應的IOC容器中。那條件該怎麼告訴Spring框架呢? 也是經過一個類,這個類要求實現Condition接口。
順帶提一下Spring Boot很大程度上也依賴於條件註解。
首先兩個bean:

public class HomeStudent extends Student {
    /**
     * 出入證 無參和構造函數 get set方法再也不列出
     */
    private String pass;    
}
public class BoardStudent extends Student {
    /**
     * 宿舍號 無參和構造函數 get set方法再也不列出
     */
    private String dormitoryNumber;
}

而後準備條件,須要實現Condition接口:

public class HomeStudentCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        // 獲取當前的環境,通常開發環境分紅三套: 開發、測試、生產。
        Environment environment = conditionContext.getEnvironment();
        // 從環境中獲取學生類型
        String studentType = environment.getProperty("studentType");
        // 若是是住宿學生就加入到IOC容器中
        if ("HomeStudent".equals(studentType)){
            return true;
        }
        return false;
    }
}
public class BoardStudentCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        String studentType = environment.getProperty("studentType");
        if ("BoardStudent".equals(studentType)){
            return true;
        }
        return false;
    }
}

而後在對應的bean上加上條件變量:

@Configuration
@ComponentScan(basePackages = "org.example")
public class SpringConfig {
    @Bean
    @Conditional(HomeStudentCondition.class)
    public HomeStudent homeStudent() {
        return new HomeStudent("出入證");
    }
    @Bean
    @Conditional(BoardStudentCondition.class)
    public BoardStudent boardStudent() {
        return new BoardStudent("宿舍200");
    }
}

在IDEA中配置環境。

測試代碼:

private static void annotationTest() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        String[] beanNames = applicationContext.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            System.out.println(beanName);
        }
    }

測試結果:

會發現boardStudent沒了。

bean的做用域

  • singleton 單例(默認值),在每一個Spring IOC容器中,一個bean僅對應一個對象實例
  • prototype 原型,每次從IOC容器中獲取對應的對象的時候,都會返回一個新的對象實例。
  • request 一次HTTP請求中,一個bean定義對應一個實例,即每次HTTP請求會將有各自的bean實例,他們依據某個bean定義建立而成。僅在基於WEB的Spring ApplicationContext的狀況才生效
  • session 在一個HTTP Session中,一個bean對應一個實例,僅在基於WEB的Spring ApplicationContext的狀況才生效。
  • 在一個全局的的HTTP Session中,一個bean定義對應一個實例。典型狀況下僅在使用porlet(一個Tomcat容器)的時候有效。

僅在基於WEB的Spring ApplicationContext的狀況才生效。
咱們主要經常使用的是singleton和prototype,下面咱們來測試一下:

public class SpringConfig {
    @Bean(name = "studentCard")
    public StudentCard studentCard(){
        return  new StudentCard(11,"22");
    }

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Student student(@Qualifier("studentCard") StudentCard studentCard){
        return new Student(20,"zs",studentCard);
    }

    @Bean
    public StudentCard studentCard2(){
        return  new StudentCard(11,"22");
    }
}
  private static void annotationTest() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        Student student1 = (Student) applicationContext.getBean("student");
        Student student2 = (Student) applicationContext.getBean("student");
        System.out.println(student1 == student2);
    }

結果:

ConfigurableBeanFactory不是枚舉類型,只是有兩個常量字符串: singleton和prototype。你直接寫這兩個字符串中任意一個也行。
配置文件中進行測試:

<bean id="student" class="org.example.Student" scope="prototype">
        <property name = "name" >
            <value type="java.lang.String"><![CDATA[z<3]]></value>
        </property>
        <property name="age">
            <value>34</value>
        </property>
    </bean>
private static void xmlTest() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student1 = (Student) applicationContext.getBean("student");
        Student student2 = (Student) applicationContext.getBean("student");
        System.out.println(student1 == student2);
    }

一點點細節的補充

在singleton做用域下,容器在啓動的時候就會建立該對象,可是也支持懶加載,即在首次向容器中獲取該對象的時候建立。
那麼怎麼告知IOC容器,在啓動的時候,先不要建立呢? 經過@Lazy註解。如何測試呢? 你能夠在對應對象的構造函數上,打斷點或者輸出點東西測試,也能夠在啓動的時候,打印IOC容器來全部對象的名字來打印。
這裏是在對應對象的構造函數上輸出一點東西來測試。

  • 配置類懶加載
@Configuration
public class SpringConfig {
    @Bean(name = "studentCard")
    public StudentCard studentCard(){
        return  new StudentCard(11,"22");
    }
    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Student student(@Qualifier("studentCard") StudentCard studentCard){
        return new Student(20,"zs",studentCard);
    }
    @Bean
    @Lazy
    public StudentCard studentCard2(){
        return  new StudentCard(11,"22");
    }
}
  • 配置文件懶加載
<bean id="student" class="org.example.Student" lazy-init="true">
        <property name="age">
            <value>34</value>
        </property>
    </bean>

這裏測試結果就再也不展現了。

自動裝配的幾種形式

上面咱們已經介紹了,自動裝配的兩個註解了:

  • @Autowired 默認根據類型去查找,若是找不到,默認會報錯。
@Autowired(required = false),就能夠避免在容器中找不到對應類型時拋出錯誤。
  • @Qualifier 按照名字進行裝配

這裏咱們再介紹幾個:

  • @Primary
優先,能夠配合@Autowired使用,假設容器裏有兩個@Autowired須要的對象,在裝配時,被打上@Primary的對象,優先被選中
  • @Resource 並不是來自Spring,來自Java的JSR250提案,默認按照bean名進行匹配,若是沒找到對應的bean名,則去匹配對應的類型,JDK已經自帶。
  • @Inject 並不是來自Spring,來自於Java的 JSR330提案。須要引入jar包。

Spring帝國簡介

從剛開始的Spring framework,到如今Spring家族已經有不少產品了:

後面咱們將會介紹SpringMVC,接管MVC的C的一個框架。Java領域的問題,在Spring帝國,幾乎均可以找到解決方案(一個是方案整合(Spring Cloud),一個是本身提供(Spring MVC)。)

總結一下

最開始我是從視頻開始學習Spring框架的,看視頻也是最快學框架的方式,其實看視頻的時候,內心仍是有些疑問的,可是又找不到人去問。感受視頻中講的有的時候很牽強,不成系統,零零碎碎的。我不是很喜歡零零碎碎的知識點,我喜歡系統一點的,因而就又系統的整理了一下本身對Spring的理解,也算是入門教程,也算是總結。但願對各位學習Spring有所幫助。

參考資料:

相關文章
相關標籤/搜索