Java Spring各種依賴注入註解的區別
Spring對於Bean的依賴注入,支援多種註解方式:
@Resource javax.annotation JSR250 (Common Annotations for Java) @Inject javax.inject JSR330 (Dependency Injection for Java) @Autowired org.springframework.bean.factory Spring
直觀上看起來,@Autowired是Spring提供的註解,其他幾個都是JDK本身內建的註解,Spring對這些註解也進行了支援。但是使用起來這三者到底有什麼區別呢?筆者經過方法的測試,發現一些有意思的特性。
區別總結如下:
一、@Autowired有個required屬性,可以配置為false,這種情況下如果沒有找到對應的bean是不會拋異常的。@Inject和@Resource沒有提供對應的配置,所以必須找到否則會拋異常。
二、 @Autowired和@Inject基本是一樣的,因為兩者都是使用AutowiredAnnotationBeanPostProcessor來處理依賴注入。但是@Resource是個例外,它使用的是CommonAnnotationBeanPostProcessor來處理依賴注入。當然,兩者都是BeanPostProcessor。
@Autowired和@Inject - 預設 autowired by type - 可以 通過@Qualifier 顯式指定 autowired by qualifier name。 - 如果 autowired by type 失敗(找不到或者找到多個實現),則退化為autowired by field name @Resource - 預設 autowired by field name - 如果 autowired by field name失敗,會退化為 autowired by type - 可以 通過@Qualifier 顯式指定 autowired by qualifier name - 如果 autowired by qualifier name失敗,會退化為 autowired by field name。但是這時候如果 autowired by field name失敗,就不會再退化為autowired by type了。
TIPS Qualified name VS Bean name
在Spring設計中,Qualified name並不等同於Bean name,後者必須是唯一的,但是前者類似於tag或者group的作用,對特定的bean進行分類。可以達到getByTag(group)的效果。對於XML配置的bean,可以通過id屬性指定bean name(如果沒有指定,預設使用類名首字母小寫),通過標籤指定qualifier name:
<bean id="lamborghini" class="me.arganzheng.study.spring.autowired.Lamborghini"> <qualifier value="luxury"/> <!-- inject any dependencies required by this bean --> </bean>
如果是通過註解方式,那麼可以通過@Qualifier註解指定qualifier name,通過@Named或者@Component(@Service,@Repository等)的value值指定bean name:
@Component("lamborghini") @Qualifier("luxury") public class Lamborghini implements Car { }
或者
@Component @Named("lamborghini") @Qualifier("luxury") public class Lamborghini implements Car { }
同樣,如果沒有指定bean name,那麼Spring會預設是用類名首字母小寫(Lamborghini=>lamborghini)。
三、 通過Anotation注入依賴的方式在XML注入方式之前進行。如果對同一個bean的依賴同時使用了兩種注入方式,那麼XML的優先。但是不同擔心通過Anotation注入的依賴沒法注入XML中配置的bean,依賴注入是在bean的註冊之後進行的。
四、目前的autowired by type方式(筆者用的是3.2.3.RELEASE版本),Spring的AutowiredAnnotationBeanPostProcessor實現都是有”bug”的,也就是說@Autowired和@Inject都是有坑的(稱之為坑,不稱之為bug是因為貌似是故意的。。)。這是來源於線上的一個bug,也是這邊文章的寫作原因。現場如下:
application-context.xml中有如下定義:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd"> <context:annotation-config /> <context:component-scan base-package="me.arganzheng.study" /> <util:constant id="en" static-field="me.arganzheng.study.spring.autowired.Constants.Language.EN" /> <util:constant id="ja" static-field="me.arganzheng.study.spring.autowired.Constants.Language.JP" /> <util:constant id="ind" static-field="me.arganzheng.study.spring.autowired.Constants.Language.IND" /> <util:constant id="pt" static-field="me.arganzheng.study.spring.autowired.Constants.Language.PT" /> <util:constant id="th" static-field="me.arganzheng.study.spring.autowired.Constants.Language.TH" /> <util:constant id="ar" static-field="me.arganzheng.study.spring.autowired.Constants.Language.AR" /> <util:constant id="en-rIn" static-field="me.arganzheng.study.spring.autowired.Constants.Language.EN_RIN" /> <util:map id="languageChangesMap" key-type="java.lang.String" value-type="java.lang.String"> <entry key="pt" value="pt" /> <entry key="br" value="pt" /> <entry key="jp" value="ja" /> <entry key="ja" value="ja" /> <entry key="ind" value="ind" /> <entry key="id" value="ind" /> <entry key="en-rin" value="en-rIn" /> <entry key="in" value="en-rIn" /> <entry key="en" value="en" /> <entry key="gb" value="en" /> <entry key="th" value="th" /> <entry key="ar" value="ar" /> <entry key="eg" value="ar" /> </util:map> </beans>
其中static-field應用的常量定義在如下類中:
package me.arganzheng.study.spring.autowired; public interface Constants { public interface Language { public static final String EN = "CommonConstants.LANG_ENGLISH"; public static final String JP = "CommonConstants.LANG_JAPANESE"; public static final String IND = "CommonConstants.LANG_INDONESIAN"; public static final String PT = "CommonConstants.LANG_PORTUGUESE"; public static final String TH = "CommonConstants.LANG_THAI"; public static final String EN_RIN = "CommonConstants.LANG_ENGLISH_INDIA"; public static final String AR = "CommonConstants.LANG_Arabic"; } }
然後如果我們在程式碼中如下宣告依賴:
public class AutowiredTest extends BaseSpringTestCase { @Autowired private Map<String, String> languageChangesMap; @Test public void testAutowired() { notNull(languageChangesMap); System.out.println(languageChangesMap.getClass().getSimpleName()); System.out.println(languageChangesMap); } }
Guess what,詭異的事情發生了!
執行結果如下:
LinkedHashMap {en=CommonConstants.LANG_ENGLISH, ja=CommonConstants.LANG_JAPANESE, ind=CommonConstants.LANG_INDONESIAN, pt=CommonConstants.LANG_PORTUGUESE, th=CommonConstants.LANG_THAI, ar=CommonConstants.LANG_Arabic, en-rIn=CommonConstants.LANG_ENGLISH_INDIA}
也就是說Map
嚴重: Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@5c51ee0a] to prepare test instance [me.arganzheng.study.spring.autowired.AutowiredTest@6e301e0] org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'me.arganzheng.study.spring.autowired.AutowiredTest': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private java.util.Map me.arganzheng.study.spring.autowired.AutowiredTest.languageChangesMap; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [java.lang.String] found for dependency [map with value type java.lang.String]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} ... Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [java.lang.String] found for dependency [map with value type java.lang.String]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:986) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:843) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:768) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:486) ... 28 more
debug了一下,發現確實是Spring的一個bug。在DefaultListableBeanFactory的這個方法出問題了:
protected Object doResolveDependency(DependencyDescriptor descriptor, Class<?> type, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException { ... else if (Map.class.isAssignableFrom(type) && type.isInterface()) { Class<?> keyType = descriptor.getMapKeyType(); if (keyType == null || !String.class.isAssignableFrom(keyType)) { if (descriptor.isRequired()) { throw new FatalBeanException("Key type [" + keyType + "] of map [" + type.getName() + "] must be assignable to [java.lang.String]"); } return null; } Class<?> valueType = descriptor.getMapValueType(); if (valueType == null) { if (descriptor.isRequired()) { throw new FatalBeanException("No value type declared for map [" + type.getName() + "]"); } return null; } Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType, descriptor); if (matchingBeans.isEmpty()) { if (descriptor.isRequired()) { raiseNoSuchBeanDefinitionException(valueType, "map with value type " + valueType.getName(), descriptor); } return null; } if (autowiredBeanNames != null) { autowiredBeanNames.addAll(matchingBeans.keySet()); } return matchingBeans; } ... }
關鍵在這一句:Map
嚴重: Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@9476189] to prepare test instance [me.arganzheng.study.spring.autowired.AutowiredTest@2d546e21] ... Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [java.lang.String] found for dependency [map with value type java.lang.String]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true), @org.springframework.beans.factory.annotation.Qualifier(value=languageChangesMap)} at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:986) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:843) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:768) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:486) ... 28 more
debug了一下,發現跟沒有指定qualifie name是一樣的執行路徑。不是指定了bean name了嗎?為什麼還是autowired by type呢?仔細檢視了一下才發現。DefaultListableBeanFactory的doResolveDependency方法對首先對型別做了區別:
protected Object doResolveDependency(DependencyDescriptor descriptor, Class<?> type, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException { Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); if (value != null) { if (value instanceof String) { String strVal = resolveEmbeddedValue((String) value); BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null); value = evaluateBeanDefinitionString(strVal, bd); } TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); return (descriptor.getField() != null ? converter.convertIfNecessary(value, type, descriptor.getField()) : converter.convertIfNecessary(value, type, descriptor.getMethodParameter())); } if (type.isArray()) { Class<?> componentType = type.getComponentType(); Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType, descriptor); if (matchingBeans.isEmpty()) { if (descriptor.isRequired()) { raiseNoSuchBeanDefinitionException(componentType, "array of " + componentType.getName(), descriptor); } return null; } if (autowiredBeanNames != null) { autowiredBeanNames.addAll(matchingBeans.keySet()); } TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); return converter.convertIfNecessary(matchingBeans.values(), type); } else if (Collection.class.isAssignableFrom(type) && type.isInterface()) { Class<?> elementType = descriptor.getCollectionType(); if (elementType == null) { if (descriptor.isRequired()) { throw new FatalBeanException("No element type declared for collection [" + type.getName() + "]"); } return null; } Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType, descriptor); if (matchingBeans.isEmpty()) { if (descriptor.isRequired()) { raiseNoSuchBeanDefinitionException(elementType, "collection of " + elementType.getName(), descriptor); } return null; } if (autowiredBeanNames != null) { autowiredBeanNames.addAll(matchingBeans.keySet()); } TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); return converter.convertIfNecessary(matchingBeans.values(), type); } else if (Map.class.isAssignableFrom(type) && type.isInterface()) { Class<?> keyType = descriptor.getMapKeyType(); if (keyType == null || !String.class.isAssignableFrom(keyType)) { if (descriptor.isRequired()) { throw new FatalBeanException("Key type [" + keyType + "] of map [" + type.getName() + "] must be assignable to [java.lang.String]"); } return null; } Class<?> valueType = descriptor.getMapValueType(); if (valueType == null) { if (descriptor.isRequired()) { throw new FatalBeanException("No value type declared for map [" + type.getName() + "]"); } return null; } Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType, descriptor); if (matchingBeans.isEmpty()) { if (descriptor.isRequired()) { raiseNoSuchBeanDefinitionException(valueType, "map with value type " + valueType.getName(), descriptor); } return null; } if (autowiredBeanNames != null) { autowiredBeanNames.addAll(matchingBeans.keySet()); } return matchingBeans; } else { Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor); if (matchingBeans.isEmpty()) { if (descriptor.isRequired()) { raiseNoSuchBeanDefinitionException(type, "", descriptor); } return null; } if (matchingBeans.size() > 1) { String primaryBeanName = determinePrimaryCandidate(matchingBeans, descriptor); if (primaryBeanName == null) { throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet()); } if (autowiredBeanNames != null) { autowiredBeanNames.add(primaryBeanName); } return matchingBeans.get(primaryBeanName); } // We have exactly one match. Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next(); if (autowiredBeanNames != null) { autowiredBeanNames.add(entry.getKey()); } return entry.getValue(); } }
如果是Array,Collection或者Map,則根據集合類中元素的型別來進行autowired by type(Map使用value的型別)。為什麼這麼特殊處理呢?原來,Spring是為了達到這樣的目的:讓你可以一次注入所有符合型別的實現,也就是說可以這樣子注入:
@Autowired private List<Car> cars;
如果你的car有多個實現,那麼都會注入進來,不會再報
org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [me.arganzheng.study.spring.autowired.Car] is defined: expected single matching bean but found 2: [audi, toyota].
然而,上面的情況如果你用@Resource則不會有這個問題:
public class AutowiredTest extends BaseSpringTestCase { @Resource @Qualifier("languageChangesMap") private Map<String, String> languageChangesMap; @Test public void testAutowired() { assertNotNull(languageChangesMap); System.out.println(languageChangesMap.getClass().getSimpleName()); System.out.println(languageChangesMap); } }
正常執行:
LinkedHashMap {pt=pt, br=pt, jp=ja, ja=ja, ind=ind, id=ind, en-rin=en-rIn, in=en-rIn, en=en, gb=en, th=th, ar=ar, eg=ar}
當然,你如果不指定@Qualifier(“languageChangesMap”),同時field name不是languageChangesMap,那麼還是一樣報錯的。
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [java.lang.String] found for dependency [map with value type java.lang.String]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@javax.annotation.Resource(shareable=true, mappedName=, description=, name=, type=class java.lang.Object, authenticationType=CONTAINER, lookup=)} at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:986) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:843) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:768) at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:438) at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:416) at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:550) at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:150) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87) at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:303) ... 26 more
而且,@Resource也可以實現上面的List接收所有實現:
public class AutowiredTest extends BaseSpringTestCase { @Resource @Qualifier("languageChangesMap") private Map<String, String> languageChangesMap; @Resource private List<Car> cars; @Test public void testAutowired() { assertNotNull(languageChangesMap); System.out.println(languageChangesMap.getClass().getSimpleName()); System.out.println(languageChangesMap); assertNotNull(cars); System.out.println(cars.getClass().getSimpleName()); System.out.println(cars); } }
執行的妥妥的:
LinkedHashMap {pt=pt, br=pt, jp=ja, ja=ja, ind=ind, id=ind, en-rin=en-rIn, in=en-rIn, en=en, gb=en, th=th, ar=ar, eg=ar} ArrayList [me.arganzheng.study.spring.autowired.Audi@579584da, me.arganzheng.study.spring.autowired.Toyota@19453122]
這是因為@Resource註解使用的是CommonAnnotationBeanPostProcessor處理器,跟AutowiredAnnotationBeanPostProcessor不是同一個作者[/偷笑]。這裡就不分析了,感興趣的同學可以自己看程式碼研究一下。
最終結論如下:
1、@Autowired和@Inject
autowired by type 可以 通過@Qualifier 顯式指定 autowired by qualifier name(非集合類。注意:不是autowired by bean name!) 如果 autowired by type 失敗(找不到或者找到多個實現),則退化為autowired by field name(非集合類)
2、@Resource
預設 autowired by field name 如果 autowired by field name失敗,會退化為 autowired by type 可以 通過@Qualifier 顯式指定 autowired by qualifier name 如果 autowired by qualifier name失敗,會退化為 autowired by field name。但是這時候如果 autowired by field name失敗,就不會再退化為autowired by type了
測試工程儲存在GitHub上,是標準的maven工程,感興趣的同學可以clone到本地執行測試一下。
補充
有同事指出Spring官方文件上有這麼一句話跟我的結有點衝突:
However, although you can use this convention to refer to specific beans by name, @Autowired is fundamentally about type-driven injection with optional semantic qualifiers. This means that qualifier values, even with the bean name fallback, always have narrowing semantics within the set of type matches; they do not semantically express a reference to a unique bean id.
也就是說@Autowired即使加了@Qualifier註解,其實也是autowired by type。@Qualifier只是一個限定詞,過濾條件而已。重新跟進了一下程式碼,發現確實是這樣子的。Spring設計的這個 @Qualifier name 並不等同於 bean name。他有點類似於一個tag。不過如果這個tag是唯一的化,那麼其實效果上等同於bean name。實現上,Spring是先getByType,得到list candicates,然後再根據qualifier name進行過濾。
再定義一個蘭博基尼,這裡使用@Qualifier指定:
package me.arganzheng.study.spring.autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @Component @Qualifier("luxury") public class Lamborghini implements Car { }
再定義一個勞斯萊斯,這裡故意用@Named指定:
package me.arganzheng.study.spring.autowired; import javax.inject.Named; import org.springframework.stereotype.Component; @Component @Named("luxury") public class RollsRoyce implements Car { }
測試一下注入定義的豪華車:
package me.arganzheng.study.spring.autowired; import static junit.framework.Assert.assertNotNull; import java.util.List; import me.arganzheng.study.BaseSpringTestCase; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; /** * * @author zhengzhibin * */ public class AutowiredTest extends BaseSpringTestCase { @Autowired @Qualifier("luxury") private List<Car> luxuryCars; @Test public void testAutowired() { assertNotNull(luxuryCars); System.out.println(luxuryCars.getClass().getSimpleName()); System.out.println(luxuryCars); } }
執行結果如下:
ArrayList [me.arganzheng.study.spring.autowired.Lamborghini@66b875e1, me.arganzheng.study.spring.autowired.RollsRoyce@58433b76]
補充:Autowiring modes
Spring支援四種autowire模式,當使用XML配置方式時,你可以通過autowire屬性指定。
no. (Default) No autowiring. Bean references must be defined via a ref element. Changing the default setting is not recommended for larger deployments, because specifying collaborators explicitly gives greater control and clarity. To some extent, it documents the structure of a system. byName. Autowiring by property name. Spring looks for a bean with the same name as the property that needs to be autowired. For example, if a bean definition is set to autowire by name, and it contains a master property (that is, it has a setMaster(..) method), Spring looks for a bean definition named master, and uses it to set the property. byType. Allows a property to be autowired if exactly one bean of the property type exists in the container. If more than one exists, a fatal exception is thrown, which indicates that you may not use byType autowiring for that bean. If there are no matching beans, nothing happens; the property is not set. constructor. Analogous to byType, but applies to constructor arguments. If there is not exactly one bean of the constructor argument type in the container, a fatal error is raised.
如果使用@Autowired、@Inject或者@Resource註解的時候,則稍微複雜一些,會有一個失敗退化過程,並且引入了Qualifier。不過基本原理是一樣。
相關文章
- Spring的三種依賴注入的方式Spring依賴注入
- spring常用的三種依賴注入方式Spring依賴注入
- spring 的依賴注入Spring依賴注入
- Spring依賴注入Spring依賴注入
- Spring依賴注入---Spring依賴注入
- 簡單瞭解下Spring中的各種Aware介面實現依賴注入Spring依賴注入
- 聊聊依賴注入註解@Resource和@Autowired依賴注入
- 反射、註解與依賴注入總結反射依賴注入
- 從Hessian RPC 註解方式看Spring依賴注入RPCSpring依賴注入
- Spring 依賴注入的理解Spring依賴注入
- 依賴注入?依賴注入是如何實現解耦的?依賴注入解耦
- Spring依賴注入的兩種方式(根據例項詳解)Spring依賴注入
- Spring IOC——依賴注入Spring依賴注入
- Spring 依賴注入 DISpring依賴注入
- Spring的依賴注入的方式Spring依賴注入
- Java Web系列:Spring依賴注入基礎JavaWebSpring依賴注入
- spring 依賴注入的學習Spring依賴注入
- Spring系列.依賴注入配置Spring依賴注入
- Spring.Net 依賴注入Spring依賴注入
- 大白話spring依賴注入Spring依賴注入
- Android依賴注入之BufferKnife 8.0註解使用Android依賴注入
- SpringDI四種依賴注入方式詳解Spring依賴注入
- Java開發學習(十二)----基於註解開發依賴注入Java依賴注入
- Spring 各種註解備註Spring
- spring 詳細講解(ioc,依賴注入,aop)Spring依賴注入
- 反射,註解,動態代理,依賴注入控制反轉反射依賴注入
- HelloWorld版的SpringMVC使用註解驅動的依賴注入SpringMVC依賴注入
- spring框架學習 依賴注入Spring框架依賴注入
- Spring依賴注入原理學習Spring依賴注入
- Spring學習(三)依賴注入Spring依賴注入
- spring下應用@Resource, @Autowired 和 @Inject註解進行依賴注入的差異Spring依賴注入
- spring中的控制反轉和依賴注入 --講解Spring依賴注入
- 2.3 Spring的核心機制:依賴注入Spring依賴注入
- PHP 設計模式答疑-物件池與依賴注入的區別PHP設計模式物件依賴注入
- Spring6框架中依賴注入的多種方式(推薦構造器注入)Spring框架依賴注入
- 使用ReflectionTestUtils解決依賴注入依賴注入
- 死磕Spring原始碼-依賴注入Spring原始碼依賴注入
- Spring原始碼解析——依賴注入(二)Spring原始碼依賴注入