@Resource详解
@Resource詳解
閱讀本文之前希望讀者最好已經(jīng)對整個Bean的大體Spring執(zhí)行順序已經(jīng)有了一定的了解。
示例
定義一個接口,表示水果類,只包含一個方法代表售賣。
public interface Fruit {void sell(); }有兩個具體實現(xiàn)類,Apple🍎和Banana🍌。
@Service public class Apple implements Fruit {public Apple() {System.out.println("Apple......");}@Overridepublic void sell() {System.out.println("蘋果2元一斤");} } @Service public class Banana implements Fruit {public Banana() {System.out.println("Banana...");}@Overridepublic void sell() {System.out.println("香蕉3元一串");} }具體的商店類來注入Fruit接口。
@Component public class Store {@Resourceprivate Fruit fruit;public void getFruit() {fruit.sell();}}測試方法:
public class Test {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);context.getBean(Store.class).getFruit();// 優(yōu)雅關(guān)閉,SpringWEB中已有相關(guān)實現(xiàn)context.registerShutdownHook();} }實際執(zhí)行結(jié)果:
這里可以清楚的看到報了NoUniqueBeanDefinitionException異常,說是希望單個Bean的匹配,卻找到了多個。
下面就來具體的講下為什么。
首先,@Resource中沒有設(shè)置任何屬性值,統(tǒng)統(tǒng)采用的是默認的值。
按照Spring Bean的加載順序,Store Bean創(chuàng)建的時候,BeanFactory中已經(jīng)創(chuàng)建了Apple和Banana Bean。
1. newInstance
第一步就是先創(chuàng)建出Store對象。
2. 解析類中的字段
Spring在實例化對象后,會調(diào)用 applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);方法對BeanDefinition進行完善,主要為了后續(xù)的BeanPostProcessor處理器在注入對應的字段時能獲取到需要注入的類的相關(guān)信息。
而對應需要注入的一個個類而言,就是使用ResourceElement對象來進行保存,相關(guān)重要的構(gòu)造函數(shù)如下:
public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {super(member, pd);Resource resource = ae.getAnnotation(Resource.class);// 獲取@Resource的name屬性String resourceName = resource.name();// 獲取@Resource的type屬性Class<?> resourceType = resource.type();this.isDefaultName = !StringUtils.hasLength(resourceName);if (this.isDefaultName) {// 如果沒有設(shè)置@Resource name屬性就用字段名稱作為bean nameresourceName = this.member.getName();// 如果member是setter方法,則取setXXX的XXX部分為bean nameif (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {resourceName = Introspector.decapitalize(resourceName.substring(3));}}else if (embeddedValueResolver != null) {// 如果設(shè)置了@Resource name的屬性,則使用EmbeddedValueResolver對象先做一次SpringEL解析得到真正的bean nameresourceName = embeddedValueResolver.resolveStringValue(resourceName);}if (Object.class != resourceType) {// 確保字段或setter方法類型與resourceType一致checkResourceType(resourceType);}else {// No resource type specified... check field/method.resourceType = getResourceType();}this.name = (resourceName != null ? resourceName : "");this.lookupType = resourceType;String lookupValue = resource.lookup();// 如果使用jndi查找名字this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName());Lazy lazy = ae.getAnnotation(Lazy.class);// 是否延遲注入this.lazyLookup = (lazy != null && lazy.value()); }當name屬性沒有被設(shè)置時,就會執(zhí)行下面的分支,根據(jù)是方法注入還是屬性注入,分別設(shè)置為方法名稱set后面的字符串或字段名稱。
if (this.isDefaultName) {// 如果沒有設(shè)置@Resource name屬性就用字段名稱作為bean nameresourceName = this.member.getName();// 如果member是setter方法,則取setXXX的XXX部分為bean nameif (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {resourceName = Introspector.decapitalize(resourceName.substring(3));}}下面是具體的ResourceElement類對象中的各屬性。
然后可以測試下在@Resource注解中加入name屬性;
@Resource(name = “apple”)
得到的是下面的對象。
這里可以看出name屬性會有明顯的不同。
這里name屬性和lookupType屬性其實可以對應于@Resource中的name和type屬性。
3. populateBean
第三步就是類屬性的注入。
執(zhí)行到對應的處理器(@Resource是通過CommonAnnotationBeanPostProcessor處理器進行處理的)進行屬性注入的時候,autowireResource就會用來獲取對應屬性需要注入的對象。
protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName)throws NoSuchBeanDefinitionException {// 自動裝配的對象Object resource;// 自動裝配的名字Set<String> autowiredBeanNames;// 依賴的屬性名String name = element.name;if (factory instanceof AutowireCapableBeanFactory) {AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;DependencyDescriptor descriptor = element.getDependencyDescriptor();// 判斷是否設(shè)置了name屬性的值if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {autowiredBeanNames = new LinkedHashSet<>();resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);if (resource == null) {throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");}}else {resource = beanFactory.resolveBeanByName(name, descriptor);autowiredBeanNames = Collections.singleton(name);}}else {resource = factory.getBean(name, element.lookupType);autowiredBeanNames = Collections.singleton(name);}if (factory instanceof ConfigurableBeanFactory) {ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) factory;for (String autowiredBeanName : autowiredBeanNames) {if (requestingBeanName != null && beanFactory.containsBean(autowiredBeanName)) {//注冊依賴關(guān)系beanFactory.registerDependentBean(autowiredBeanName, requestingBeanName);}}}return resource; }這里的element對象就是我們之前解析保存的ResourceElement。
然后最重要的,上面強調(diào)過的name屬性在不同Resource注解中解析出來的ResourceElement是會不同的,這里就會有不同的處理方式。
當isDefaultName為false時,說明name屬性被設(shè)置了值,此時執(zhí)行的如下邏輯;
resource = beanFactory.resolveBeanByName(name, descriptor);autowiredBeanNames = Collections.singleton(name);resolveBeanByName方法就是通過設(shè)置的名稱來進行解析對應的Bean對象。接著就能看到經(jīng)常使用到的getBean方法,從BeanFactory中拿到Bean對象。
public Object resolveBeanByName(String name, DependencyDescriptor descriptor) {InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);try {// 獲取beanreturn getBean(name, descriptor.getDependencyType());}finally {// 為目標工廠方法提供依賴描述符ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);} }當Resource注解中name沒有設(shè)置值,即使用的是字段名稱作為beanName,執(zhí)行的就是
resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);resolveDependency方法中實際工作的方法就是doResolveDependency:
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {//設(shè)置新得當前切入點對象,得到舊的當前切入點對象InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);// ...省略// 查找相關(guān)所有類型匹配的beanMap<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);//如果沒有候選bean對象if (matchingBeans.isEmpty()) {//如果descriptor需要注入if (isRequired(descriptor)) {//拋出NoSuchBeanDefinitionException或BeanNotOfRequiredTypeException以解決不可 解決的依賴關(guān)系raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);}//返回null,表示么有找到候選Bean對象return null;}//定義用于存儲唯一的候選Bean名變量String autowiredBeanName;//定義用于存儲唯一的候選Bean對象變量Object instanceCandidate;//如果候選Bean對象Map不止有一個if (matchingBeans.size() > 1) {//確定candidates中可以自動注入的最佳候選Bean名稱autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);if (autowiredBeanName == null) {// 如果有多個匹配結(jié)果,拋出異常if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {//讓descriptor嘗試選擇其中一個實例,默認實現(xiàn)是拋出NoUniqueBeanDefinitionException.return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);}else {return null;}}instanceCandidate = matchingBeans.get(autowiredBeanName);}else {//獲取machingBeans唯一的元素Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();autowiredBeanName = entry.getKey();instanceCandidate = entry.getValue();}// ...省略//返回最佳候選Bean對象【result】return result;}finally {//設(shè)置上一個切入點對象ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);} }doResolveDependency方法中會獲取所有和指定type相同的所有bean集合,當發(fā)現(xiàn)bean集合中的元素個數(shù)超過1時就拋出了上面出現(xiàn)的錯誤了,如果只有一個那自然而然就是拿那個bean來返回。
這里拋出的異常其實不是直接拋出的,而是調(diào)用的DependencyDescriptor中的resolveNotUnique方法,而該方法默認實現(xiàn)就是會直接拋出一個NoUniqueBeanDefinitionException異常,這也是Spring留的一個可擴展的地方。
我們可以通過繼承DependencyDescriptor,然后重寫該方法來自定義選擇一個最優(yōu)的bean(比如說選擇第一個),然后在BeanPostProcessor中選擇我們的子類作為實現(xiàn),這樣在@Resource不指定任何屬性的情況下,有多個實現(xiàn)類的bean也不會拋出異常。
拓展
- 當只指定type屬性時;
@Resource(type = Apple.class)
設(shè)置type屬性后,isDefaultName的值還是為true,所以執(zhí)行的還是resolveDependency方法。但是由于添加了類型的限制,所以也就不會匹配到多個Bean,而產(chǎn)生異常。
- 既指定了name屬性,又指定了type類型,但是是不同的類;
@Resource(name = “banana”, type = Apple.class)
name屬性被設(shè)置為banana,isDefaultName變?yōu)閒alse,執(zhí)行resolveBeanByName方法。
但是由于找不到對應beanName為banana,但是類型又為Apple.class的bean,還是會拋出異常。
總結(jié)
多種@Resource不同使用情況所執(zhí)行方法如下所示;
-
當@Resource不設(shè)置任何值時,isDefaultName會為true,當對應字段名稱的bean或者BeanDefinition已存在時會走byName的形式,否則走byType的形式;
-
只指定了type屬性時,只有當對應的名稱不存在對應的bean或BeanDefinition,才會通過byType找到唯一的一個類型匹配的bean;
-
只指定了name屬性,會執(zhí)行getBean方法,根據(jù)指定的name來獲取bean;
-
既指定了name屬性,又指定了type屬性,會先根據(jù)那么查找對應的bean,然后進行type類型比較。
如何解決本文最上面出現(xiàn)的問題?
- @Resource中指定name或著type;
- @Qualifier指定bean名稱;
- 將字段名稱修改為指定的bean名稱;
- 直接修改對象類型。
只推薦第一種方法。
總結(jié)
以上是生活随笔為你收集整理的@Resource详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (附源码)springboot民宿网站
- 下一篇: 现代软件工程第三章