Java Spring DI之旅

作過.NET的人不少都用過Microsoft Enterprise Library,裏面有一個Dependency injection工具Unity,咱們可使用它來實現依賴注入;什麼是依賴注入呢?我我的認爲依賴注入就是脫藕,當類A一個對象要引用另一個類B對象才能完成操做時,咱們說兩個類之間具備依賴關係;若是類A只是經過類B實現的接口來引用類B的對象,咱們說這兩個類之間是鬆耦合的;那麼咱們如何經過一種更靈活的方式把類B的對象賦值給類A對象,使得類A對象根本不須要了解到B這個類的存在,這種方式叫作依賴注入。java

在Java中,Spring做爲開發利器,其核心就是DI和AOP;咱們只須要在xml中配置好類A的對象生成過程,而後調用上下文方法,Spring就會爲咱們創造出一個類A的對象,至於如何把B類的一個對象建立出來並賦給類A對象的,咱們不須要關心,而且類A在編碼時都無需知道類B的存在,一切將由Spring自動完成。正則表達式

那麼咱們來看看如何構造這個類A對象的建立過程,一般來說咱們把Java中須要用Spring來建立的對象都稱之爲Bean,而把這個建立的過程叫作裝配。spring

    1. 如何申明Bean

      申明Bean的方式有兩種,一種是經過一個或多個xml文件做爲配置文件,還有一種是使用Java註解。函數

      咱們如今主要講前面這種方式:工具

      <bean id="objA" class="com.company.project.A"></bean>
      <bean id="objB" class="com.company.project.B"></bean>

       咱們如今申明瞭兩個Bean,因爲類A的對象須要使用到類B的對象,如何講類B對象告知類A對象?假如類A對象有一個構造函數須要傳入類B對象的值:ui

      public A(B obj)
      {
         .....   
      }

      那麼咱們可使用構造函數注入:this

      <bean id="objA" class="com.company.project.A">
         <constructor-arg ref="objB"/>
      </bean>
      <bean id="objB" class="com.company.project.B"></bean>

      若是類B只有一個單列對象,如:編碼

      public class B
      {
         private B(){}
      
         private static class BSingletonHodler
         {
            static B instance = new B();
         }
      
         private static B getSingletonInstance()
         {
            return BSingletonHodler.instance;
         } 
      }

      那麼咱們的配置應該是:spa

      <bean id="objB" class="com.company.project.B" factory-method="getSingletonInstance"></bean>

      注意,全部經過Spring上下文來建立的bean都是單列的,也就是說每一次經過相同的id來獲得一個bean時,都獲得的是相同的對象,咱們能夠經過xml中bean元素的scope屬性來改變這種行爲;prototype

      還有一種狀況,咱們須要Spring在構造一個bean對象成功以後,或者在銷燬一個bean以前執行這個bean的一個方法,應該這樣使用:

      <bean id="objA" class="com.company.project.A" init-method="構造完成後執行的方法" destory-method="銷燬以前執行的方法">
         <constructor-arg ref="objB"/>
      </bean>

       若是類A只是經過一個屬性引用了類B的對象,而並不是構造函數:

      public class A
      {
         public A(){}
         private B b;
         public B getB()
         {
            return b;
         }
         public void setB(B obj)
         {
            b = obj;
         }
      }

      那麼咱們須要屬性注入:

      <bean id="objA" class="com.company.project.A">
         <property name="b" ref="objB"/>
      </bean>
      <bean id="objB" class="com.company.project.B"></bean>

      或者使用一種內嵌的方式:

      <bean id="objA" class="com.company.project.A">
         <property name="b">
            <bean class="com.company.project.B"></bean>
         </property>
      </bean>

      或者:

      <bean id="objA" class="com.company.project.A" p:b-ref="objB">
      </bean>

      採用這種方式時,應該在文件頭申明xmlns:p="http://ww.springframework.org/schema/p"這個命名空間,加-ref後綴是用來告知spring應該裝配一個bean,而不是一個字面量。

      若是類A不是須要的一個類B的對象,而是一個類B對象的集合,如:

      public class A
      {
         public A(){}
         private Collection<B> bList;
         public void setBList(Collection<B> bList)
         {
            this.bList = bList;
         }
      }

      咱們可使用:

      <bean id="objA" class="com.company.project.A">
         <property name="bList">
            <list>
               <ref bean="objB"/>
               <ref bean="objB"/>
               <null/><!--插入一個空值-->
            </list>
         </property>
      </bean>
      <bean id="objB" class="com.company.project.B" scope="prototype"></bean>

      若是類A接受一個Map集合:

      public class A
      {
         public A(){}
         private Map<string,B> maps;
         public void setMaps(Map<string,B> maps)
         {
            this.maps = maps;
         }
      }
      public class B{...}

      咱們應該使用:

      <bean id="objA" class="com.company.project.A">
         <property name="maps">
            <map>
               <entry key="b1" value-ref="objB"/>
               <entry key="b2" value-ref="objB"/>
            </map>
         </property>
      </bean>
      <bean id="objB" class="com.company.project.B" scope="prototype"></bean>

      若是類A須要裝配一個properties:

      public class A
      {
         private Properties properties;
         public void setProperties(Properties properties)
         {
            this.properties = properties;
         }
         public A(){ ... }
      }

      咱們能夠在Spring配置文件中作以下配置:

      <bean id="objA" class="com.company.project.A">
         <property name="properties">
            <props>
               <prop key="JOEL">STRUM</prop>
               <prop key="Cymbal">SRASH</prop>
               <prop key="Harmonica">HUM</prop>
            </props>
         </property>
      </bean>
    2. 使用表達式來提供裝配值

      自Spring3提供了Spring表達式語言(即SpEL)以來,咱們即可以在配置文件中使用運行時執行的表達式將值裝配到Bean的屬性或構造器參數中。全部的SpEL都應該放置到以#{}爲界定符的標記裏面,如提供一個Integer常量表達式:

      <property name="message" value="The value is #{5}"></property>

      字符串常量表達式應該使用單引號或者雙引號做爲界定符:

      <property name="message" value="#{'This is a message'}"></property>

      Boolean類型的常量表達式:

      <property name="enabled" value="#{true}"></property>

      咱們能夠在SpEL中經過ID引用其餘的bean:

      <property name="b" value="#{objB}"></property>

      或者引用其餘Bean的一個屬性:

      <property name="message" value="#{objB.message}"/>

      或者其餘Bean的一個方法:

      <property name="message" value="#{objB.getMessage()}"/>

      若是上例中message屬性只能接收大寫字母,可是咱們不能肯定objB.getMessage()返回null,若是返回null,咱們則不須要調用toUpperCase()方法,咱們能夠利用?.符號:

      <property message="message" value="#{objB.getMessage()?.toUpperCase()}"/>

      利用一個靜態屬性或方法的返回值對某個屬性進行裝配:

      <property name="pi" value="#{T(java.lang.Math).PI}"/>

      在表達式中也可使用算數運算符:

      <property name="amount" value="#{counter.total + 5}"/>

      在表達式中使用比較操做符時,應該使用相應的文本類型,如:==(eq),<(lt),<=(le),>(gt),>=(ge),如:

      <property name="hasCapacity" value="#{counter.total le 100000}"/>

      也可使用邏輯操做符:and or not

      有時候咱們但願在某個條件爲true時,SpEL表達式的求值結果爲是某個值;當條件爲false時,求值結果是另外一個值:

      <property name="message" value="#{objB.message != null ? objB.message : 'this is a message'}"/>

      上面也能夠簡寫爲:

      <property name="message" value="#{objB.message ?: 'this is a message'}"/>

      在SpEL中使用正則表達式:

      <property name="isValid" value="#{admin.email matches '[a-zA-Z0-9._%+_]'}"/>

      SpEL做用於集合,假設咱們有這樣一個類:

      package com.thoughtworks.demo.core;
      
      public class Student {
          private String name;
          public void setName(String name)
          {
              this.name = name;
          }
      }

      咱們能夠在Spring裏面利用util:list來構建一個Student對象的List:

      <util:list id="students">
         <bean class="com.thoughtworks.demo.core.Student" p:name="Josen"></bean>
         <bean class="com.thoughtworks.demo.core.Student" p:name="Cindy"></bean>
         <bean class="com.thoughtworks.demo.core.Student" p:name="Baby"></bean>
      </util:list>

      前提是咱們必須在文件頭加入xmlns:util="http://www.springframework.org/schema/util"命名空間,並在xsi:schemaLocation加入了http://www.springframework.org/schema/util和http://www.springframework.org/schema/util/spring-util-2.0.xsd

      若是咱們要在集合中提取一個成員,咱們應該使用:

      <property name="chosenStudent" values="#{students[1]}"/>

      咱們也可使用util:properties來構造一個properties文件的bean,如:

      <util:properties id="settings" location="classpath:settings.properties">
      </util:properties>
      <bean id="man" class="com.thoughtworks.demo.core.Student">
         <property name="name" value="#{settings['project.name']}"></property>
      </bean>
    3. 自動裝配

      自動裝配的意思是咱們無需指定由哪個bean來裝配,spring會按照咱們指定的規則去尋找相應的bean,自動裝配有4種類型:

      • byName:若是某個bean的ID與property的名字同樣,則這個bean就會自動裝配;
      • byType:若是某個bean的類型與property的類型一致,則這個bean會被自動裝配;
      • constructor:假設經過構造器注入來裝配bean,咱們讓spring在應用上下文中自動選擇與入參類型相同的Bean注入到構造器參數中
      • autodetect:Spring首先嚐試constructor自動裝配,若是沒有發現與構造器相匹配的Bean,Spring會嘗試使用byType自動裝配。

      注意,前二者是針對要裝配的bean的全部property而言的,固然咱們也能夠爲某個property提供獨特的裝配方案,而constructor則不行,咱們不能爲某個構造器入參提供獨特的裝配方案,假設咱們有一個類Teacher引用了Student類:

      public class Teacher {
          private Student student;
          public void setStudent(Student student)
          {
              this.student = student;
          }
          public Student getStudent()
          {
              return this.student;
          }
          
          private String name;
          public void setName(String name)
          {
              this.name = name;
          }
      }

      咱們按照byName的方式來完成student這個property的自動裝配:

      <bean id="student" class="com.thoughtworks.demo.core.Student">
          <property name="name" value="Josen"></property>
      </bean>
      <bean id="teacher" class="com.thoughtworks.demo.core.Teacher" autowire="byName">
          <property name="name" value="Alex"/>
      </bean>

      或者按照byType來自動裝配:

      <bean id="student" class="com.thoughtworks.demo.core.Student">
         <property name="name" value="Josen"></property>
      </bean>
      <bean id="teacher" class="com.thoughtworks.demo.core.Teacher" autowire="byType">
         <property name="name" value="Alex"/><!-- 注意,這裏爲name property提供了獨特的裝配方案 -->
      </bean>

      當Teacher類有一個構造函數的時候:

      public class Teacher {
          private Student student;
          public void setStudent(Student student)
          {
              this.student = student;
          }
          public Student getStudent()
          {
              return this.student;
          }
          
          private String name;
          public void setName(String name)
          {
              this.name = name;
          }
          
          public Teacher(Student stu)
          {
              this.student = stu;
          }
      }

      咱們使用constructor自動裝配:

      <bean id="student" class="com.thoughtworks.demo.core.Student">
         <property name="name" value="Josen"></property>
      </bean>
      <bean id="teacher" class="com.thoughtworks.demo.core.Teacher" autowire="constructor">
         <property name="name" value="Alex"/><!-- 注意,這裏爲name property提供了獨特的裝配方案 -->
      </bean>
    4. 註解裝配

      註解裝配屬於自動裝配的範疇,若是咱們爲某個屬性或者屬性的setter方法添加了@Autowired,那麼這個屬性將由Spring按照byType的方式進行自動裝配:

      public class Teacher {
          private Student student;
          @Autowired //按照 byType方式自動裝配
          public void setStudent(Student student)
          {
              this.student = student;
          }
          public Student getStudent()
          {
              return this.student;
          }
          
          @Value("Cindy") //提供常量值的註解裝配
          private String name;
          public void setName(String name)
          {
              this.name = name;
          }
      }

      注意,Spring默認禁用註解裝配,因此在使用註解裝配以前,應在配置文件中配置它,首先加入xmlns:context="http://www.springframework.org/schema/context"命名空間,而後在xsi:schemaLocation裏面加入http://www.springframework.org/schema/context和http://www.springframework.org/schema/context/spring-context-3.0.xsd,最後在beans下加入

      <context:annotation-config/>

      配置節點。

      咱們也可使用@Autowired來註解構造器,那麼Spring將按照constructor的自動註解方式完成bean的裝配,假如咱們註解了多個構造器,Spring將會從知足條件的構造器中選擇參數最多的那個構造器

      這裏有一個問題,在視同@Autowired來註解屬性的時候,假如Spring找不到類型相同的bean,那麼spring會拋出異常;這時咱們可使用@Autowired(required=false)方式來註解屬性,假如Spring找不到類型相同的bean,則會裝配一個null值

      咱們也可使用@Qualifier註解來把@Autowired的byType自動裝配轉化爲byName自動裝配,可是@Qualifier必須和@Autowired一塊兒使用:

      public class Teacher {
          private Student student;
          @Autowired
          @Qualifier("student")
          public void setStudent(Student student)
          {
              this.student = student;
          }
          public Student getStudent()
          {
              return this.student;
          }
          
          @Value("Cindy")
          private String name;
          public void setName(String name)
          {
              this.name = name;
          }
      }
相關文章
相關標籤/搜索