javascript
框架:Spring的自动装配
提到依賴注入(DI),就不能不說裝配。有些初學者總是會把這兩個概念搞混,這個博文就是來跟大家討論這兩個概念以及其中詳細的原理。
????????依賴注入的本質就是裝配,裝配是依賴注入的具體行為。這就是兩者的關系。例如:
???????首先,確定一下裝配的概念。《spring實戰》中給裝配下了一個定義:
? 創建應用對象之間協作關系的行為稱為裝配。也就是說當一個對象的屬性是另一個對象時,實例化時,需要為這個對象屬性進行實例化。這就是裝配。如果一個對象只通過接口來表明依賴關系,那么這種依賴就能夠在對象本身毫不知情的情況下,用不同的具體實現進行切換。但是這樣會存在一個問題,在傳統的依賴注入配置中,我們必須要明確要給屬性裝配哪一個bean的引用,一旦bean很多,就不好維護了。
基于這樣的場景,spring使用注解來進行自動裝配,解決這個問題。自動裝配就是開發人員不必知道具體要裝配哪個bean的引用,這個識別的工作會由spring來完成。與自動裝配配合的還有“自動檢測”,這個動作會自動識別哪些類需要被配置成bean,進而來進行裝配。這樣我們就明白了,自動裝配是為了將依賴注入“自動化”的一個簡化配置的操作。
???????裝配分為四種:
byName,byType, constructor, autodetect。
byName就是會將與屬性的名字一樣的bean進行裝配。
byType就是將同屬性一樣類型的bean進行裝配。
constructor就是通過構造器來將類型與參數相同的bean進行裝配。
autodetect是constructor與byType的組合,會先進行constructor,如果不成功,再進行byType。
具體選擇哪一種裝配方式,需要配置<bean>標簽的autowire屬性,如果沒有配置,默認是byName類型,就是會根據屬性的名字來進行自動裝配。上面最常用的還是byName和byType。自動裝配時,裝配的bean必須是唯一與屬性進行吻合的,不能多也不能少,有且只有一個可以進行裝配的bean,才能自動裝配成功。否則會拋出異常。如果要統一所有bean的自動裝配類型,可以在<beans>標簽中配置default-autowire屬性。當然如果配置了autowire屬性,我們依然可以手動裝配屬性,手動裝配會覆蓋自動裝配。
???????以上是通過xml配置的方式實現自動裝配的,spring2.5之后提供了注解方式的自動裝配。
但是要使用這些注解,需要在配置文件中配置<context:annotation-config />。只有加上這一配置,才可以使用注解進行自動裝配,默認情況下基于注解的裝配是被禁用的。
???????常用的自動裝配注解有以下幾種:@Autowired,@Resource,@Inject,@Qualifier,@Named。
@Autowired注解是byType類型的(byName就是通過Bean的id或者name,byType就是按Bean的Class的類型。)
這個注解可以用在屬性上面,setter方法上面以及構造器上面。使用這個注解時,就不需要在類中為屬性添加setter方法了。但是這個屬性是強制性的,也就是說必須得裝配上,如果沒有找到合適的bean能夠裝配上,就會拋出異常。這時可以使用required=false來允許可以不被裝配上,默認值為true。當required=true時,@Autowired要求必須裝配,但是在沒有bean能裝配上時,就會拋出異常:NoSuchBeanDefinitionException,如果required=false時,則不會拋出異常。
另一種情況是同時有多個bean是一個類型的,也會拋出這個異常。
此時需要進一步明確要裝配哪一個Bean,這時可以組合使用@Qualifier注解,值為Bean的名字即可。
@Qualifier注解使用byName進行裝配,這樣可以在多個類型一樣的bean中,明確使用哪一個名字的bean來進行裝配。@Qualifier注解起到了縮小自動裝配候選bean的范圍的作用。
注意:
@Autowired注解是spring提供的,所以會依賴spring的包。還有一個byType的注解@Inject,與@Autowired注解作用一樣,也是byType類型,而且是java ee提供的,完全可以代替@Autowired注解,但是@Inject必須是強制裝配的,沒有required屬性,也就是不能為null,如果不存在匹配的bean,會拋出異常。@Autowired與@Qualifier可以組合使用,@Inject也有一個組合的注解,就是@Named注解,與@Qualifier作用一樣,也是byName,但不是spring的,是java ee標準的。
這樣就出現了兩套自動裝配的注解組合,
@Autowired與@Qualifier是spring提供的,@Inject與@Named是java ee的。
但是@Qualifier注解在java ee中也有一樣,作用與spring的@Qualifier注解一模一樣,只是所在的包不一樣。不過建議大家使用spring的。最后還有一個@Resouce注解,這個注解也是java ee的,也是byName類型的,原理同@Qualifier和@Named是一樣的。
最后說一說,
自動檢測配置,也是springmvc中最牛的一項功能。只要一個配置<context:component-scan base-package="">,
base-package屬性指定要自動檢測掃描的包。
該配置會自動掃描指定的包及其子包下面被構造型注解標注的類,并將這些類注冊為springbean,這樣就不用在配置文件一個一個地配置成bean標簽。
構造型注解包括:@Controller,@Component,@Service,@Repository和使用@Component標注的自定義注解。
生成的bean的ID默認為類的非限定名,也就是把類的名字的首字母換成小寫。可以在這些注解的值中寫名beanid的
值,如@Controller("helloworld")。
如果你想細化包被掃描的范圍,可以使用<context:include-filter>和<context:exclude-filter>。
具體使用方法這里不再詳說。注意,沒有被掃描到的類是不能注冊為bean,也就不能被用來裝配其他類。
所以這個配置的base-package的范圍非常重要。
?
Java的注解機制——Spring自動裝配的實現原理
JDK1.5加入了對注解機制的支持,實際上我學習Java的時候就已經使用JDK1.6了,而且除了@Override和@SuppressWarnings(后者還是IDE給生成的……)之外沒接觸過其他的。
使用注解主要是在需要使用Spring框架的時候,特別是使用SpringMVC。
因為這時我們會發現它的強大之處:預處理。
注解實際上相當于一種標記,它允許你在運行時(源碼、文檔、類文件我們就不討論了)動態地對擁有該標記的成員進行操作。
實現注解需要三個條件(我們討論的是類似于Spring自動裝配的高級應用):
? ? ? ? 注解聲明、使用注解的元素、操作 使用注解的元素 的代碼。
首先是注解聲明,注解也是一種類型,我們要定義的話也需要編寫代碼,如下:
?
?1package annotation;
?2
?3import java.lang.annotation.ElementType;
?4import java.lang.annotation.Retention;
?5import java.lang.annotation.RetentionPolicy;
?6import java.lang.annotation.Target;
?7
?8/**
?9?* 自定義注解,用來配置方法
10?*
11?* @authorJohness
12?*
13? */
14 @Retention(RetentionPolicy.RUNTIME) // 表示注解在運行時依然存在
15 @Target(ElementType.METHOD) // 表示注解可以被使用于方法上
16public @interfaceSayHiAnnotation{
17????String paramValue() default "johness"; // 表示我的注解需要一個參數名為"paramValue" 默認值為"johness"
18 }
然后是使用我們注解的元素:
?
?1package element;
?2
?3import annotation.SayHiAnnotation;
?4
?5/**
?6?* 要使用SayHiAnnotation的元素所在類
?7?* 由于我們定義了只有方法才能使用我們的注解,我們就使用多個方法來進行測試
?8?*
?9?* @author Johness
10?*
11? */
12publicclass SayHiEmlement {
13
14???? // 普通的方法
15???? publicvoid SayHiDefault(String name){
16????????System.out.println("Hi, " + name);
17???? }
18????
19???? // 使用注解并傳入參數的方法
20????@SayHiAnnotation(paramValue="Jack")
21???? publicvoid SayHiAnnotation(String name){
22????????System.out.println("Hi, " + name);
23???? }
24????
25???? // 使用注解并使用默認參數的方法
26????@SayHiAnnotation
27???? publicvoid SayHiAnnotationDefault(String name){
28????????System.out.println("Hi, " + name);
29???? }
30 }
?
最后,是我們的操作方法(值得一提的是雖然有一定的規范,但您大可不必去浪費精力,您只需要保證您的操作代碼在您希望的時候執行即可):
?
?1package Main;
?2
?3import java.lang.reflect.InvocationTargetException;
?4import java.lang.reflect.Method;
?5
?6import element.SayHiEmlement;
?7import annotation.SayHiAnnotation;
?8
?9publicclass AnnotionOperator {
10???? publicstaticvoid main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException,ClassNotFoundException {
11????????SayHiEmlement element = new SayHiEmlement(); // 初始化一個實例,用于方法調用
12????????Method[] methods = SayHiEmlement.class.getDeclaredMethods();// 獲得所有方法
13????????
14????????for (Method method : methods) {
15????????????SayHiAnnotation annotationTmp = null;
16????????????if((annotationTmp =method.getAnnotation(SayHiAnnotation.class))!=null) // 檢測是否使用了我們的注解
17????????????????method.invoke(element,annotationTmp.paramValue()); // 如果使用了我們的注解,我們就把注解里的"paramValue"參數值作為方法參數來調用方法
18????????????else
19???????????????? method.invoke(element,"Rose"); // 如果沒有使用我們的注解,我們就需要使用普通的方式來調用方法了
20????????}
21???? }
22 }
?
結果為:
Hi, Jack
Hi, johness
Hi, Rose
可以看到,注解是進行預處理的很好方式(這里的預處理和編譯原理有區別)!
接下來我們看看Spring是如何使用注解機制完成自動裝配的:
首先讓Spring知道我們要進行自動裝配的操作,無外乎兩種:
1.繼承org.springframework.web.context.support.SpringBeanAutowiringSupport類或者
2.添加@Component/@Controller等注解并(只是使用注解方式需要)在Spring配置文件里聲明context:component-scan元素。
我說說繼承方式是如何實現自動裝配的,我們打開Spring源代碼查看SpringBeanAutowiringSupport類。我們會發現以下語句:
1public SpringBeanAutowiringSupport() {
2????????processInjectionBasedOnCurrentContext(this);
3???? }
?
眾所周知,Java實例構造時會調用默認父類無參構造方法,Spring正是利用了這一點,讓"操作元素的代碼"得以執行!(我看到第一眼就震驚了!真是奇思妙想啊。果然,高手都要善于用Java來用Java)
后面的我就不就不多說了,不過還是要糾正一些人的觀點:說使用注解的自動裝配來完成注入也需要setter。這明顯是錯誤的嘛!我們看Spring自動裝配(繼承方式)的方法調用順序:?org.springframework.web.context.support.SpringBeanAutowiringSupport#SpringBeanAutowiringSupport=>
???org.springframework.web.context.support.SpringBeanAutowiringSupport#processInjectionBasedOnCurrentContext=>
?org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#processInjection=>
??????????????org.springframework.beans.factory.annotation.InjectionMetadata#Injection(繼承,方法重寫)。最后看看Injection方法的方法體:
?
?1/**
?2?????????* Either this or {@link #getResourceToInject} needs to be overridden.
?3????????? */
?4???????? protected void inject(Object target, String requestingBeanName, PropertyValues pvs) throws Throwable {
?5???????????? if (this.isField) {
?6?????????? ??????Field field = (Field) this.member;
?7????????????????ReflectionUtils.makeAccessible(field);
?8???????????????? field.set(target,getResourceToInject(target, requestingBeanName));
?9???????????? }
10????????????else {
11???????????????? if(checkPropertySkipping(pvs)) {
12???????????????????? return;
13???????????????? }
14???????????????? try {
15???????????????????? Method method = (Method) this.member;
16????????????????????ReflectionUtils.makeAccessible(method);
17???????????????????? method.invoke(target,getResourceToInject(target, requestingBeanName));
18???????????????? }
19???????????????? catch(InvocationTargetException ex) {
20???????????????????? throwex.getTargetException();
21???????????????? }
22????????????}
23????????}
?
雖然不完全,但可以基本判定此種自動裝配是使用了java反射機制。
?
總結
以上是生活随笔為你收集整理的框架:Spring的自动装配的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 框架:简单实现Spring的IOC容器
- 下一篇: 框架:Spring Aop、拦截器、过滤