Explain @ Resource parsing process in detail

Keywords: Java Spring Boot

1, Resource annotation parsing process

@The logic of Resource and @ Autowired processing is basically the same. They both find the injection point first, and then inject attributes according to the injection point. The difference is that @ Resource parsing is completed by CommonAnnotationBeanPostProcessor, and @ Autowired parsing is completed by AutowiredAnnotationBeanPostProcessor. The whole process of @ Resource parsing is described below

1.1 find the injection point

The time to find the injection point is the same as that of @Autowired. After instantiation, the postProcessMergedBeanDefinition() method that implements the MergedBeanDefinitionPostProcessor interface is invoked. Similarly, the Resource annotation processing class CommonAnnotationBeanPostProcessor also implements the MergedBeanDefinitionPostProcessor interface, so the postProcessMergedBeanDefinition of the class is called. () method to find the injection point

The first line of the method will call the postProcessMergedBeanDefinition() of initdestroannotationbeanpostprocessor to cache and check the initialization method and destruction method

The second line of code looks up the metadata of @ Resource annotation

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);
    InjectionMetadata metadata = findResourceMetadata(beanName, beanType, null);
    metadata.checkConfigMembers(beanDefinition);
}

Get the beanName from the cache first. If there is no target class in the cache or the target class in the InjectionMetadata does not match the current beanType, call buildResourceMetadata to generate annotation metadata

private InjectionMetadata findResourceMetadata(String beanName, final 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;
}

Loop through the properties and methods of the current class and parent class to determine whether there is a @ Resource annotation. If so, generate a ResourceElement object, which inherits from InjectionMetadata.InjectedElement, put the ResourceElement object into the InjectedElement object combination, and then generate an InjectionMetadata object to return

For static properties and static methods, @ Autowired will not handle them, but @ Resource will throw an exception directly

private InjectionMetadata buildResourceMetadata(final 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 -> {
            ......
            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;
            }
            ......
                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);
}

The construction method of ResourceElement is as follows. The @ Resource annotation can specify the name and type of the injected Bean through the name and type methods. If no name is specified, the default name is used, and the parameter name or set method is used to remove the set prefix as name

If the type attribute is specified, check whether the type of the attribute or the first parameter type of the method matches the specified type. If not, an exception will be thrown

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();

    // If you do not specify a specific name when using @ Resource, use the name of field or xxx in setXxx()
    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));
        }
    }
    // When using @ Resource, specify a specific name to fill in placeholders
    else if (embeddedValueResolver != null) {
        resourceName = embeddedValueResolver.resolveStringValue(resourceName);
    }

    // @In addition to specifying bean s, Resource can also specify type, which is Object by default
    if (Object.class != resourceType) {
        // If type is specified, verify whether the type of field or the first parameter type of set method matches the specified resourceType
        checkResourceType(resourceType);
    }
    else {
        // No resource type specified... check field/method.
        resourceType = getResourceType();
    }
    ......
}

1.2 attribute injection

Property injection is completed during property filling. The postProcessProperties() of the instantiaawarebeanpostprocessor implementation class is called. The property injection of @ Resource annotation is completed in the postProcessProperties() of CommonAnnotationBeanPostProcessor

  • Start injection

    Gets the injection point that has been cached, then calls the inject() of InjectedElement, which is different from @Autowired. The injection of Autowired annotations will invoke inject of AutowiredFieldElement and AutowiredMethodElement respectively.

    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;
    }
    
    public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
       Collection<InjectedElement> checkedElements = this.checkedElements;
       Collection<InjectedElement> elementsToIterate =
             (checkedElements != null ? checkedElements : this.injectedElements);
       if (!elementsToIterate.isEmpty()) {
          // Traverse each injection point for dependency injection
          for (InjectedElement element : elementsToIterate) {
             element.inject(target, beanName, pvs);
          }
       }
    }
    
  • Get injection value

    The source code of InjectedElement's inject() is as follows. Directly call getResourceToInject() to obtain the dependency value for injection

    protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
          throws Throwable {
    
       if (this.isField) {
          Field field = (Field) this.member;
          ReflectionUtils.makeAccessible(field);
          field.set(target, getResourceToInject(target, requestingBeanName));
       }
       else {
          if (checkPropertySkipping(pvs)) {
             return;
          }
          try {
             Method method = (Method) this.member;
             ReflectionUtils.makeAccessible(method);
             method.invoke(target, getResourceToInject(target, requestingBeanName));
          }
          catch (InvocationTargetException ex) {
             throw ex.getTargetException();
          }
       }
    }
    

    If the attribute or method is annotated with @ Lazy annotation, a proxy object is generated; otherwise, getResource() is called to get the injected object

    protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
       return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :
             getResource(this, requestingBeanName));
    }
    

    The source code of getResource() is as follows. Call the core autowireResource() to get the bean object

    protected Object getResource(LookupElement element, @Nullable String requestingBeanName)
          throws NoSuchBeanDefinitionException {
    	......
       // Find the appropriate bean object from the BeanFactory according to the LookupElement
       return autowireResource(this.resourceFactory, element, requestingBeanName);
    }
    

    If the current beanFactory belongs to AutowireCapableBeanFactory, if the bean object cannot be found according to the name, you can also call resolveDependency() to obtain or generate the bean object according to the type

    @When searching for bean objects with the Resource annotation, first judge whether to use the default name. If the default name is used, first judge whether there is a BeanFactory according to the default name. If there is, directly call getBean() to obtain the bean object. If not, then call resolveDependency() to find or generate the bean object according to the class type. resolveDependency() is the core method of @ Autowired parsing

    If the name is specified, the getBean() method is called directly to find the bean object

    If a bean object is found, its name is cached in autowiredBeanNames. Finally, beanFactory.registerDependentBean() is invoked to cache the dependency between beanName, which bean dependency of the current injected bean object is cached beanNaem instead of bean pairs.

    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();
    
          // Assuming that no name is specified in @ Resource, and there is no corresponding bean in the name of field or xxx of setXxx(), find it from BeanFactory according to the field type or method parameter type
          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;
    }
    

1.3 difference from @ Autowired

1. There is no difference in performance between the two, @ Resource is provided by Jdk and implemented by Spring. In the process of obtaining the dependency injection value, @ Autowired first finds it according to the type, and then performs layer by layer filtering such as Qualifier filtering, while @ Resource first finds it according to the name city. If the default name is used and beanFactory cannot find it according to the name, it can be used The same logic as @ Autowired will be called to find it according to the type

2. If @ Resource specifies a name, it will only find the bean object according to the name, and throw an exception directly if it is not found

Posted by fat creative on Wed, 20 Oct 2021 22:36:50 -0700