Spring 4.1.6(三)

Dependency Injection(依賴注入)

對於一個application 來說,需要組織很多的class,讓他們相互獨立,便於重用、方便測試。依賴注入就可以實現。

SpellChecker 是一個空class。

public class TextEditor {
	private SpellChecker sc;

	public TextEditor() {
		sc = new SpellChecker();
	}
}

從代碼中,我們可以看出TextEditor 是依賴於SpellChecker ,TextEditor 在創建的時候,必須要有SpellChecker ,這時是實際上,是通過TextEditor 去控制SpellChecker ,寫死在TextEditor 構造器中,假如這時我們要依賴一個其他的class,就要手動該代碼,這很痛苦,那麼,我們看下面這種:

public class TextEditor {
	private SpellChecker sc;

	public TextEditor(SpellChecker sc) {
		this.sc = sc;
	}
}

在TextEditor 的構造器中,傳入SpellChecker 對象,然後在賦值。這時,就很靈活,就不是由TextEditor 去控制死了,而是交給Spring 容器去控制。這時如果傳入不同對象,我們只需要讓它繼承共同的父類,或者實現共同的接口即可。

依賴注入的兩種形式
  1. 通過構造器

在前面的例子,我們是通過set方法來演示了依賴注入,下面我們看看如何通過構造器去注入:

public class SpellChecker {
    public void spellCheck() {
    	System.out.println("i am the inner texteditor");
    }
}
public class TextEditor {
	private SpellChecker sc;
	private String name;
	public TextEditor(SpellChecker sc, String name) {
		super();
		this.sc = sc;
		this.name = name;
	}

	public void spellCheck() {
		sc.spellCheck();
	}

}
        <bean name="texteditor" class="test.TextEditor">
        <constructor-arg name="name" value="hello world"></constructor-arg>//通過構造器傳參,達到注入
	<constructor-arg name="sc" ref="spellchecker"></constructor-arg>//注入的是一個引用對象
	</bean>
	<bean name="spellchecker" class="test.SpellChecker"></bean>

使用構造器傳參設置的時候,和你Bean 裏面提供的帶參的構造器參數順序,無所謂。

  1. 通過set方法

注入內部Bean

內部Bean 簡而言之就和Java 的內部類差不多。就是在一個外部Bean中在依賴一個Bean。

public class SpellChecker {
    public void spellCheck() {
    	System.out.println("i am the inner texteditor");
    }
}
public class TextEditor {
	private SpellChecker sc;

	public SpellChecker getSc() {
		return sc;
	}

	public void setSc(SpellChecker sc) {
		this.sc = sc;
	}
	public void spellCheck() {
		sc.spellCheck();
	}
	
}

這裏通過set 把SpellChecker 對象注入到TextEditor 。

<bean name="texteditor" class="test.TextEditor">
 <property name="sc">
 <bean name="spellchecker" class="test.SpellChecker"></bean>
 </property>
 </bean>
 

這裏實際和之前配置property 差不多,只不過這時不是簡單的屬性值,而是一個Bean。

運行之後,會調用SpellChecker 的方法。

注入集合

之前無論是注入一個基本屬性,還是一個Bean,注入的value 都是一個,如果是多個值,比如Java 的集合類型。Spring 提供了四種集合注入的屬性。

Element Description
list 允許注入一系列list 的值,可以重複
set 允許注入一系列set的值,不可以重複
map 允許注入鍵值對的集合,鍵和值可以是任何類型
Property 允許注入鍵值對集合,但是必須鍵和值都是String 類型
public class InjectCollection {
	private List list;
	private Set set;
	private Map<Integer,String> map;
	private Properties prop;
	}

通過set注入,set、get省略,自行補充。

 <bean name="injectcollection" class="test.InjectCollection">
    <property name="list">
     <list>
        <value>list1</value>
        <value>list2</value>
     </list>
    </property>
    <property name="set">
    <set>
    <value>set1</value>
    <value>set2</value>
    </set>
    </property>
    <property name="map">
    <map>
    <entry key="1" value="map1"></entry>
    <entry key="2" value="map2"></entry>
    </map>
    </property>
    <property name="prop">
     <props>
      <prop key="one">one</prop>
      <prop key="two">two</prop>
     </props>
    </property>
 </bean>

這裏的集合配置,實際上是作爲屬性的value,去配置的。

這配置沒啥好說,就是規定你按照寫即可。

上述的配置,集合的值全部是基本類型,假如集合的值全部是引用類型,我們又該如何處理,加個引用唄!

 <bean name="injectcollection" class="test.InjectCollection">
    <property name="list">
     <list>
     <ref bean="address1"></ref>//List 的直接引用Bean 的id
     </list>
    </property>
    <property name="set">
    <set>
     <ref bean="address1"></ref>
    </set>
    </property>
    <property name="map">
    <map>
    <entry key-ref="address1" value-ref="address2"></entry>//map 的鍵和值都可以是引用類型
    </map>
    </property>
 </bean>

爲了方便,這裏引用的Bean ,我就不定義了。properties 因爲規定了鍵和值類型,就不存在引用類型。

注入空和null值

 <property   name="message" value="" ></property>

等同於setXXX("")

 <property   name="message"  ><null></null></property>

等同於setXXX(null)

自動裝載

自動裝載,裝載的對象是引用類型對象,不是普通類型的對象。

知道我們的依賴注入,都是通過set、構造函數,去手動裝載,現在,我們可以通過自動裝載減少相應的代碼。

但是自動也也有不好的,之前手動注入,你設置注入,最多沒有值,但是現在,它會跟着相應的策略去自己尋找,自動裝載。

自動裝載的模式
Mode Description
no 該Bean 不採用任何裝配模式,你顯式定義你的裝配
byName 根據你的屬性名去自動裝配,它會根據你xml定義的屬性名和你Bean 定義的屬性名,一致自動裝配
byType 根據的屬性的類型去自動裝配,它會根據XML配置文件中查看屬性,然後,如果屬性的類型與配置文件中的一個bean名稱匹配,則會嘗試匹配並連接屬性。 如果存在多個這樣的bean,則拋出異常。
constructor 應用於構造函數,根據構造函數參數的數據類型,進行byType模式的自動裝配。
autodetect 第一次採用constructor 去自動裝載,如果不行,採用 byType
  1. byname

spring 容器會根據xml 配置自動裝載的屬性,到相應的Bean 中找名字一致的屬性,如果找到,就裝配成功;否則,無法注入該屬性。

public class SpellChecker {
    public void spellCheck() {
    	System.out.println("i am the inner texteditor");
    }
}
public class TextEditor {
	private SpellChecker sc;

	public void spellCheck() {
		sc.spellCheck();
	}
	
}

基於屬性注入,set、get方法省略,自行補充

<bean name="texteditor" class="test.TextEditor" autowire="byName">//同時,你這裏
 <property name="sc" ref="spellchecker">//這裏我們不是採用在texteditor Bean 內部配置SpellCheckerBean,而是直接讓使用去引用另外的Bean。
 </property>
 </bean>
  <bean name="spellchecker" class="test.SpellChecker"></bean>

運行結果和之前的一致。

  1. bytype

如果設置該自動裝載模式,Spring 容器會通過Bean 中該屬性的類型,去自動加載。但是你還是可以在xml 配置給基本類型去注入值。

public class SpellChecker {
    public void spellCheck() {
    	System.out.println("i am the inner texteditor");
    }
}
public class TextEditor {
	private SpellChecker sc;
	private String name;//屬性注入,set、get 方法省略,自行補充。

	public void spellCheck() {
		sc.spellCheck();
	}

}
<bean name="texteditor" class="test.TextEditor" autowire="byType">//這裏由於設置裝載模式byType 會自動掃描該Bean 的中的依賴類型,並自動裝載。
		<property name="name" value="hello"></property>
		//<property name="sc"  ></property>引用對象的屬性就沒有必要顯式設置。
	</bean>
	<bean name="spellchecker" class="test.SpellChecker"></bean>
  1. constructor

使用該模式,一定注意你的基於構造注入,纔有效,如果你是set注入,那還搞個毛毛。

該模式和bytype 很類似,但是應用於構造器上的,它會根據構造器的參數的類型,去自動裝配。

public class SpellChecker {
    public void spellCheck() {
    	System.out.println("i am the inner texteditor");
    }
}
public class TextEditor {
	private SpellChecker sc;
	private String name;
	public TextEditor(SpellChecker sc, String name) {
		super();
		this.sc = sc;
		this.name = name;
	}

	public void spellCheck() {
		sc.spellCheck();
	}

}
    <bean name="texteditor" class="test.TextEditor" autowire="constructor">
    <constructor-arg name="name" value="hello world"></constructor-arg>//這裏和butype 一致,會自動裝載SpellChecker 類型,無需你顯式設置,是需要傳入基本類型的name和value。
    </bean>
   <bean name="spellchecker" class="test.SpellChecker"></bean>
  1. autodetect
自動裝載的缺點
  1. 自動裝載的設置會被 and 重寫。
  2. 只能裝載基礎類型。
  3. 自動裝載沒有顯式裝載準確,自動裝載匹配會出現異常。

依賴注入註解

註解注入的好處,在於會覆蓋在xml 裝載的設置,爲Spring 自動裝配,我們需要在Beans.xml添加如下設置:

           <context:annotation-config/>

如果在你的Beans,沒有該標籤,請在xml 頭添加:

	xmlns:context="http://www.springframework.org/schema/context"
	http://www.springframework.org/schema/context
    	http://www.springframework.org/schema/context/spring-context-3.1.xsd

Annotation Description
@Required 該註解主要用於在屬性的set方法,它表明受影響的 bean 屬性在配置時必須放在 XML 配置文件中,否則容器就會拋出一個 BeanInitializationException 異常
@Autowired 該註解主要用在set、構造器、屬性和其他任何方法上的自動裝載
@Qualifier 配合@Autowired使用,使裝載Bean 更準確,具體看例子
JSR-250 Annotations Spring 對 JSR-250 Annotations的支持
  1. @Required

該註解是應用在Bean 的屬性的set方法上的。

public class Student {
	private String name;
	private int age;
	public String getName() {
		return name;
	}
	@Required
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	@Required
	public void setAge(int age) {
		this.age = age;
	}
	

}

<bean name="student" class="test.Student">
    <property name="name" value="lao"></property>
    <property name="age" value="11"></property>
 </bean>

一旦在Bean 的屬性中,設置了該註解,相應的必須在Beans.xml 中設置屬性,否則會出現如下的錯誤:

Caused by: org.springframework.beans.factory.BeanInitializationException: Property ‘xxx’ is required for bean 'student’

		Student s = (Student) context.getBean("student");
		System.out.println(s.getAge()+s.getName());
  1. @Autowired

該註解可以用於在set 方法、構造函數、等任意的方法上,均可以實現自動裝載。它實現是根據byType 這種方法去自動注入的。

@Autowired on Setter Methods

public class SpellChecker {
    public void spellCheck() {
    	System.out.println("i am the inner texteditor");
    }
}
public class TextEditor {
	private SpellChecker sc;
	private String name;

	public TextEditor() {
		super();
		// TODO Auto-generated constructor stub
	}

	public SpellChecker getSc() {
		return sc;
	}

	@Autowired
	public void setSc(SpellChecker sc) {
		this.sc = sc;
	}

	public String getName() {
		return name;
	}

	@Autowired
	public void setName(String name) {
		this.name = name;
	}

	public void spellCheck() {
		sc.spellCheck();
	}

}

       <bean name="texteditor" class="test.TextEditor">
	<property name="name" value="122"></property>
	</bean>
	<bean name="spellchecker" class="test.SpellChecker"></bean>

這裏有一點,你需要注意,你在前面的Bean 中給name 也設置該註解,如果你在不通過標籤去給它設置相應的值,它就會默認去自動注入,去找String 類型,然後矇蔽找不到,就報錯了。

@Autowired on Properties

該註解設置在屬性上,可以省去一系列的set方法,所以基於這種方法,我們不可以在配置文件中,設置,它會提示你沒有set方法,這一步,容器會自動幫我們設置值去處理。

public class TextEditor {
	@Autowired
	private SpellChecker sc;
	private String name;

	public TextEditor() {
		super();
		// TODO Auto-generated constructor stub
	}

	public SpellChecker getSc() {
		return sc;
	}


	public String getName() {
		return name;
	}

	public void spellCheck() {
		sc.spellCheck();
	}

}

SpellChecker類不變。

	<bean name="texteditor" class="test.TextEditor">
	</bean>
	<bean name="spellchecker" class="test.SpellChecker"></bean>

@Autowired on Constructors

該註解應用於構造器上

public class TextEditor {
	private SpellChecker sc;
	private String name;
    @Autowired
	public TextEditor(SpellChecker sc, String name) {
		super();
		this.sc = sc;
		this.name = name;
	}

	public void spellCheck() {
		sc.spellCheck();
	}

}

SpellChecker 類不變。

	<bean name="texteditor" class="test.TextEditor">
	<constructor-arg name="name" value="lao"/>
	</bean>
	<bean name="spellchecker" class="test.SpellChecker"></bean>

這裏需要注意一下,因爲該註解的構造器,包含兩個屬性,由於name 是String 類型,不需要自動裝載了,所以,你在這配置 文件 給出相應的值,由於該注入是基於構造器注入,所以用標籤。(上面是基於set方法注入,所以用)

@Autowired with (required = false) option

該註解有一個參數,可以設置是否強制自動裝載。

public class Student {
private String name;
private int age;
public String getName() {
return name;
}
@Autowired
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
@Autowired(required=false)
public void setAge(int age) {
this.age = age;
}

}


SpellChecker 類不變。

<bean name="texteditor" class="test.TextEditor">

</bean>
<bean name="spellchecker" class="test.SpellChecker"></bean>

```

如果這裏沒有設置age 上註解爲false,這裏會報錯。這樣就可以控制@Autowired 是否裝載。該註解的required 默認值是false。

  1. @Qualifier

該註解一般和@Autowired 一起,使用,實現自動裝載,但是該屬性,可以指定如果有多個複合的Bean類型,可以指定裝載的Bean 的name,使得裝載更精確。


public class Student {
	private String name;
	private int age;
         //set、get方法省略,請自行補充
	}
public class ProFile {
   @Autowired
   @Qualifier("stu1")
   private Student student;
   public void getInformation() {
	   System.out.println(student.getAge()+student.getName());
   }
}
<bean name="student" class="test.Student">
    <property name="name" value="lao"></property>
    <property name="age" value="11"></property>
 </bean>
 
  <bean name="stu1" class="test.Student">
    <property name="name" value="lao11"></property>
    <property name="age" value="1111"></property>
 </bean>
 <bean name="profile" class="test.ProFile"></bean>
``

 4. JSR-250 Annotations

這裏面主要涉及到這三個註解@PostConstruct, @PreDestroy and @Resource。

public class Student {
private String name;
private int age;
//set、get方法自行補充
}


public class ProFile {
@Autowired
@Resource(name=“student”)
private Student student;
public void getInformation() {
System.out.println(student.getAge()+student.getName());
}
@PostConstruct
public void init1() {
System.out.println(“bean is start lalalal”);
}
@PreDestroy
public void destory1() {
System.out.println(“bean is over kukukuk”);
}
}


```

通過運行,我們可以看出@PostConstruct 是和我們之前說的,在Bean 實例初始化之前,執行的方法;@PreDestroy,在Bean 銷燬之前,執行的方法。@Resource 和 @Qualifier很類似,可以指定某一個Bean 類型,達到精確裝載。

Spring 常用註解

其實是用Spring 註解配置Bean 和在xml 中配置,是起相同的作用,你只需要二者取其一即可,另外,在學習的時候,也要對比着學習。當然,藉助註解,你可省去對xml 的操作。

@Configuration

該註解應用於某一個類,則說明該類是被Spring 容器認爲是Bean 定義的源頭。

@Bean

該註解應用於某一個方法,則說明該方法返回的是一個對象bean,並該bean 會被註冊到Spring context 中,以便使用。

使用例子

    public class HelloWord {
	private String message;
    
	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}
}

@Configurable
public class BeanConfig {
	@Bean
    public HelloWord getHelloWorld() {
    	 return new HelloWord();
    }
}

這一步就很類似於xml中:

 <bean id="helloworld" class="test.HelloWord" >
 <property   name="message"  ><null></null></property>
 </bean>
ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);//這裏要注意獲取Spring 上下文的時候,和之前的不一樣了,是通過AnnotationConfigApplicationContext
	    HelloWord h1 =  (HelloWord) context.getBean(HelloWord.class);//因爲我們在那個Bean 類中獲取Bean,是根據返回類型,沒有像xml 中有name,可以直接根據name。
	    h1.setMessage("hello annotation ");
	    System.out.println(h1.getMessage());
通過註解的方法去注入Bean 依賴

我們上面的例子只是簡單示例,如何通過註解的方式,注入一個Bean,但是如果Bean 中還依賴其他Bean,那該如何處理:

public class HelloWord {
	private String message;
	private Student s;

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}
	
	
	public HelloWord(Student s) {
		super();
		this.s = s;
	}

	public Student getS() {
		return s;
	}

	public void setS(Student s) {
		this.s = s;
	}

	public void showStudent() {
		System.out.println("student "+s.getName()+s.getAge());
	}
	
}
public class Student {
	private String name;
	private int age;
	//自行補充相應的set、get方法
	}
ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
	    HelloWord h1 =  (HelloWord) context.getBean(HelloWord.class);
	    h1.setMessage("hello annotation ");
	    System.out.println(h1.getMessage());
	    h1.getS().setAge(11);
	    h1.getS().setName("lao");
	    h1.showStudent();
@Configurable
public class BeanConfig {
	@Bean
    public HelloWord getHelloWorld() {
    	 return new HelloWord(getStudent());//**這裏實際上是通過構造器注入的Student 對象**
    }
	@Bean
	public Student getStudent() {
		return new Student();
	}
	
}

上面我們是通過構造器去注入,那麼如何使用set注入,一點小變化:

在HelloWord 中,提供Student 的set、get方法。

@Configurable
public class BeanConfig {
	@Bean
    public HelloWord getHelloWorld() {
		HelloWord hw = new HelloWord();
		hw.setS(getStudent());
    	        return hw;
    }
	@Bean
	public Student getStudent() {
		return new Student();
	}
	
}

@import

該註解主要用於在一個Bean 定義的配置文件中,引入其他Bean 定義的class,可以在配置中,直擊獲取其他Bean 配置的Bean 的實例。

@Bean

該註解中,可以指定Bean 在初始化之前執行的方法和在銷燬之前執行的方法。

	@Bean(initMethod="init",destroyMethod="destory")//這裏填入對應Bean 中的方法名,方法必須申明在對應的Bean 類中。

@Scope

該註解可以指定Bean 的模式,默認是單例模式。該註解和@Bean 一起使用

	@Scope("prototype")

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章