Java Spring DI之旅
做過.NET的人很多都用過Microsoft Enterprise Library,里面有一個Dependency injection工具Unity,我們可以使用它來實現依賴注入;什么是依賴注入呢?我個人認為依賴注入就是脫藕,當類A一個對象要引用另外一個類B對象才能完成操作時,我們說兩個類之間具有依賴關系;如果類A只是通過類B實現的接口來引用類B的對象,我們說這兩個類之間是松耦合的;那么我們如何通過一種更靈活的方式把類B的對象賦值給類A對象,使得類A對象根本不需要了解到B這個類的存在,這種方式叫做依賴注入。
在Java中,Spring作為開發利器,其核心就是DI和AOP;我們只需要在xml中配置好類A的對象生成過程,然后調用上下文方法,Spring就會為我們創造出一個類A的對象,至于如何把B類的一個對象創建出來并賦給類A對象的,我們不需要關心,并且類A在編碼時都無需知道類B的存在,一切將由Spring自動完成。
那么我們來看看如何構造這個類A對象的創建過程,通常來講我們把Java中需要用Spring來創建的對象都稱之為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對象的值:
public A(B obj) {..... }那么我們可以使用構造函數注入:
<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;} }那么我們的配置應該是:
<bean id="objB" class="com.company.project.B" factory-method="getSingletonInstance"></bean>注意,所有通過Spring上下文來創建的bean都是單列的,也就是說每一次通過相同的id來得到一個bean時,都得到的是相同的對象,我們可以通過xml中bean元素的scope屬性來改變這種行為;
還有一種情況,我們需要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>自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>自動裝配的意思是我們無需指定由哪一個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>注解裝配屬于自動裝配的范疇,如果我們為某個屬性或者屬性的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;} }轉載于:https://www.cnblogs.com/cdutedu/p/3636974.html
總結
以上是生活随笔為你收集整理的Java Spring DI之旅的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《java基础知识》Java变量作用域
- 下一篇: 201903-2二十四点