Spring ⑤ @Resource @Inject 注解依赖注入详解

原创
01/28 20:32
阅读数 87

spring-logo.png

Spring ⑤ @Resource @Inject 注解依赖注入详解

Spring 源码系列文章会遵循由浅入深,由易到难,由宏观到微观的原则,目标是尽量降低学习难度,而不是一上来就迷失在源码当中. 文章会从一个场景作为出发点,针对性的目的性极强的针对该场景对 Spring 的实现原理,源码进行探究学习。该系列文章会让你收获什么? 从对 Spring 的使用者成为 Spring 专家。该文章会同步在微信公众号 【DevXJava】, 方便在微信客户端阅读。

本章的内容是对 bean 创建过程中依赖项注入的探究,针对 @Resource@Inject (JSR330 标准) 注解进行讲解分析。

@Inject 注解依赖注入

在第4章 Spring ④ Autowired 注解依赖注入详解 | 看不懂你打我 中我们详细分析了使用 @Autowired 注解如何进行依赖注入的过程(对于理解了上期内容的读者,本期内容对你来说会是非常简单的。)。核心是 AutowiredAnnotationBeanPostProcessorpostProcessProperties 方法实现的。AutowiredAnnotationBeanPostProcessor 不仅支持处理 @Autowired 注解同时也支持 JSR-330 标准的 @Inject 注解。所以在使用过程中 @Autowired 注解和 @Inject 注解并没有实质上的不同,理解了 @Autowired 的原理也就理解了 @Inject

AutowiredAnnotationBeanPostProcessor 构造函数中将支持的注解添加到 autowiredAnnotationTypes 中。如果项目中引入了 JSR-330 的 jar 包会将 @Inject 注解也添加进去。

/**
 * Create a new {@code AutowiredAnnotationBeanPostProcessor} for Spring's
 * standard {@link Autowired @Autowired} and {@link Value @Value} annotations.
 * <p>Also supports JSR-330's {@link javax.inject.Inject @Inject} annotation,
 * if available.
 */
@SuppressWarnings("unchecked")
public AutowiredAnnotationBeanPostProcessor() {
   this.autowiredAnnotationTypes.add(Autowired.class);
   this.autowiredAnnotationTypes.add(Value.class);
   try {
      this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
            ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
      logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
   }
   catch (ClassNotFoundException ex) {
      // JSR-330 API not available - simply skip.
   }
}

AutowiredAnnotationBeanPostProcessor#findAutowiredAnnotation

将需要注入的元素上的注解和 autowiredAnnotationTypes 中的注解进行比对。

@Nullable
private MergedAnnotation<?> findAutowiredAnnotation(AccessibleObject ao) {
   MergedAnnotations annotations = MergedAnnotations.from(ao);
   for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {
      MergedAnnotation<?> annotation = annotations.get(type);
      if (annotation.isPresent()) {
         return annotation;
      }
   }
   return null;
}

JSR-330

Java 依赖注入标准(JSR-330,Dependency Injection for Java)1.0 规范 2009 年 10 月 13 日发布。该规范主要是面向依赖注入使用者,而对注入器实现、配置并未作详细要求。目前 Spring 、Guice 已经开始兼容该规范,JSR-299(Contexts and Dependency Injection for Java EE platform,参考实现 Weld )在依赖注入上也使用该规范。JSR-330 规范并未按 JSR 惯例发布规范文 档,只发布了规范 API 源码,本文翻译了该规范 API 文档(Javadoc )以作为对 Java 依赖注入标准规 范的简介。

spring 从 3.0 版本开始,实现了 JSR-330 标准。

@Resource 注解依赖注入

场景

该场景中包含 属性注入方法注入。与 @Autowired 依赖注入场景不同的是,不再使用 AutowiredAnnotationBeanPostProcessor 而是换成了 CommonAnnotationBeanPostProcessor.

public class ResourceInjectionExp {

    public static void main(String[] args) {

        // @Resource 注解依赖注入

        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        CommonAnnotationBeanPostProcessor processor = new CommonAnnotationBeanPostProcessor();
        processor.setBeanFactory(factory);
        factory.addBeanPostProcessor(processor);

        factory.registerBeanDefinition("bean1" , BeanDefinitionBuilder
                .genericBeanDefinition(Bean1.class)
                .getBeanDefinition());

        factory.registerBeanDefinition("bean2" , BeanDefinitionBuilder
                .genericBeanDefinition(Bean2.class)
                .getBeanDefinition());

        factory.registerBeanDefinition("bean3" , BeanDefinitionBuilder
                .genericBeanDefinition(Bean3.class)
                .getBeanDefinition());

        Bean1 bean1 = factory.getBean(Bean1.class);
        System.out.println("bean1 -> " + bean1);
    }

    @ToString
    static class Bean1 {

        @Resource
        Bean2 bean2;

        Bean3 bean3;

        public Bean1() {
            System.out.println("======================== bean1 实例化");
        }

        @Resource
        public void setBean3(Bean3 bean3) {
            this.bean3 = bean3;
        }
    }

    static class Bean2 {

        public Bean2() {
            System.out.println("++++++++++++++++++++++++++ bean2 实例化");
        }
    }

    static class Bean3 {

        public Bean3() {
            System.out.println("++++++++++++++++++++++++++ Bean3 实例化");
        }
    }
}

CommonAnnotationBeanPostProcessor

CommonAnnotationBeanPostProcessor 静态代码块中将 @Resource 注解加到了 resourceAnnotationTypes 中.

static {
   resourceAnnotationTypes.add(Resource.class);

   webServiceRefClass = loadAnnotationType("javax.xml.ws.WebServiceRef");
   if (webServiceRefClass != null) {
      resourceAnnotationTypes.add(webServiceRefClass);
   }

   ejbClass = loadAnnotationType("javax.ejb.EJB");
   if (ejbClass != null) {
      resourceAnnotationTypes.add(ejbClass);
   }
}
postProcessProperties

AutowiredAnnotationBeanPostProcessorpostProcessProperties 几乎是一模一样.

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
   InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
   try {
      metadata.inject(bean, beanName, pvs);
   }
   catch (Throwable ex) {
      throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
   }
   return pvs;
}
findResourceMetadata
private InjectionMetadata findResourceMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
    // Fall back to class name as cache key, for backwards compatibility with custom callers.
    String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
    // Quick check on the concurrent map first, with minimal locking.
    InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
    if (InjectionMetadata.needsRefresh(metadata, clazz)) {
            synchronized (this.injectionMetadataCache) {
                    metadata = this.injectionMetadataCache.get(cacheKey);
                    if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                            if (metadata != null) {
                                    metadata.clear(pvs);
                            }
                            metadata = buildResourceMetadata(clazz);
                            this.injectionMetadataCache.put(cacheKey, metadata);
                    }
            }
    }
    return metadata;
}
buildResourceMetadata
private InjectionMetadata buildResourceMetadata(Class<?> clazz) {
   if (!AnnotationUtils.isCandidateClass(clazz, resourceAnnotationTypes)) {
      return InjectionMetadata.EMPTY;
   }

   List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
   Class<?> targetClass = clazz;

   do {
      final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();

      ReflectionUtils.doWithLocalFields(targetClass, field -> {
         if (webServiceRefClass != null && field.isAnnotationPresent(webServiceRefClass)) {
            if (Modifier.isStatic(field.getModifiers())) {
               throw new IllegalStateException("@WebServiceRef annotation is not supported on static fields");
            }
            currElements.add(new WebServiceRefElement(field, field, null));
         }
         else if (ejbClass != null && field.isAnnotationPresent(ejbClass)) {
            if (Modifier.isStatic(field.getModifiers())) {
               throw new IllegalStateException("@EJB annotation is not supported on static fields");
            }
            currElements.add(new EjbRefElement(field, field, null));
         }
         else if (field.isAnnotationPresent(Resource.class)) {
            if (Modifier.isStatic(field.getModifiers())) {
               throw new IllegalStateException("@Resource annotation is not supported on static fields");
            }
            if (!this.ignoredResourceTypes.contains(field.getType().getName())) {
               currElements.add(new ResourceElement(field, field, null));
            }
         }
      });

      ReflectionUtils.doWithLocalMethods(targetClass, method -> {
         Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
         if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
            return;
         }
         if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
            if (webServiceRefClass != null && bridgedMethod.isAnnotationPresent(webServiceRefClass)) {
               if (Modifier.isStatic(method.getModifiers())) {
                  throw new IllegalStateException("@WebServiceRef annotation is not supported on static methods");
               }
               if (method.getParameterCount() != 1) {
                  throw new IllegalStateException("@WebServiceRef annotation requires a single-arg method: " + method);
               }
               PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
               currElements.add(new WebServiceRefElement(method, bridgedMethod, pd));
            }
            else if (ejbClass != null && bridgedMethod.isAnnotationPresent(ejbClass)) {
               if (Modifier.isStatic(method.getModifiers())) {
                  throw new IllegalStateException("@EJB annotation is not supported on static methods");
               }
               if (method.getParameterCount() != 1) {
                  throw new IllegalStateException("@EJB annotation requires a single-arg method: " + method);
               }
               PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
               currElements.add(new EjbRefElement(method, bridgedMethod, pd));
            }
            else if (bridgedMethod.isAnnotationPresent(Resource.class)) {
               if (Modifier.isStatic(method.getModifiers())) {
                  throw new IllegalStateException("@Resource annotation is not supported on static methods");
               }
               Class<?>[] paramTypes = method.getParameterTypes();
               if (paramTypes.length != 1) {
                  throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);
               }
               if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {
                  PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
                  currElements.add(new ResourceElement(method, bridgedMethod, pd));
               }
            }
         }
      });

      elements.addAll(0, currElements);
      targetClass = targetClass.getSuperclass();
   }
   while (targetClass != null && targetClass != Object.class);

   return InjectionMetadata.forElements(elements, clazz);
}

ResourceElement

private class ResourceElement extends LookupElement {

   private final boolean lazyLookup;

   public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
      super(member, pd);
      Resource resource = ae.getAnnotation(Resource.class);
      String resourceName = resource.name();
      Class<?> resourceType = resource.type();
      this.isDefaultName = !StringUtils.hasLength(resourceName);
      if (this.isDefaultName) {
         resourceName = this.member.getName();
         if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
            resourceName = Introspector.decapitalize(resourceName.substring(3));
         }
      }
      else if (embeddedValueResolver != null) {
         resourceName = embeddedValueResolver.resolveStringValue(resourceName);
      }
      if (Object.class != 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();
      this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName());
      Lazy lazy = ae.getAnnotation(Lazy.class);
      this.lazyLookup = (lazy != null && lazy.value());
   }

   @Override
   protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
      return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :
            getResource(this, requestingBeanName));
   }
}
CommonAnnotationBeanPostProcessor#getResource
/**
 * Obtain the resource object for the given name and type.
 * @param element the descriptor for the annotated field/method
 * @param requestingBeanName the name of the requesting bean
 * @return the resource object (never {@code null})
 * @throws NoSuchBeanDefinitionException if no corresponding target resource found
 */
protected Object getResource(LookupElement element, @Nullable String requestingBeanName)
      throws NoSuchBeanDefinitionException {

   // JNDI lookup to perform?
   String jndiName = null;
   if (StringUtils.hasLength(element.mappedName)) {
      jndiName = element.mappedName;
   }
   else if (this.alwaysUseJndiLookup) {
      jndiName = element.name;
   }
   if (jndiName != null) {
      if (this.jndiFactory == null) {
         throw new NoSuchBeanDefinitionException(element.lookupType,
               "No JNDI factory configured - specify the 'jndiFactory' property");
      }
      return this.jndiFactory.getBean(jndiName, element.lookupType);
   }

   // Regular resource autowiring
   if (this.resourceFactory == null) {
      throw new NoSuchBeanDefinitionException(element.lookupType,
            "No resource factory configured - specify the 'resourceFactory' property");
   }
   return autowireResource(this.resourceFactory, element, requestingBeanName);
}
CommonAnnotationBeanPostProcessor#autowireResource

autowireResource 中会根据 @Resource 注解 name 属性的值作为 bean 的名称去寻找或创建 bean. @Resource 注解根据名称注入的优先级更高,如果 name 属性的值和实际的 bean 类型不一致会抛出 BeanNotOfRequiredTypeException. 而 @Autowired@Inject 注解则会根据属性的变量名和类型进行 bean 实例的匹配。

/**
 * Obtain a resource object for the given name and type through autowiring
 * based on the given factory.
 * @param factory the factory to autowire against
 * @param element the descriptor for the annotated field/method
 * @param requestingBeanName the name of the requesting bean
 * @return the resource object (never {@code null})
 * @throws NoSuchBeanDefinitionException if no corresponding target resource found
 */
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();
      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)) {
            beanFactory.registerDependentBean(autowiredBeanName, requestingBeanName);
         }
      }
   }

   return resource;
}

@Resource@Inject 注解实现依赖注入的原理和过程基本介绍完毕,更多的技术原理会在系列文章的后续内容中提供。该文章会同步在微信公众号 【DevXJava】, 方便在微信客户端阅读。


展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部