Annotation-based Startup Analysis of Spring IOC

Keywords: Spring Attribute

Spring annotation-based startup

There are two main Class implementations for annotation startup

  • AnnotationConfigApplicationContext
  • AnnotationConfigWebApplicationContext

We take Annotation Config Application Context as our research object.


AnnotationConfigApplicationContext.png

Introducing Spring Minimum Dependency

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

Writer startup code

 public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(BeanConfig.class);
        applicationContext.refresh();
        Date date = applicationContext.getBean("date",Date.class);
        System.out.println(date);
    }

Annotation Config Application Context constructor

    public AnnotationConfigApplicationContext() {
                //Register Class, Reader
        this.reader = new AnnotatedBeanDefinitionReader(this);
               //Responsible for scanning Class under the specified class path and registering bean s
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

Construction Method of Annotated Bean Definition Reader

    public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
        this(registry, getOrCreateEnvironment(registry));
    }

    public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        Assert.notNull(environment, "Environment must not be null");
        this.registry = registry;
               //Initialize ConditionEvaluator
        this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
                 
        /** Register all relevant post processors in a given registry
         *  Determine whether the container already has beans for a given registry, if no beans are registered, and place the beans in the container
         *  List all processing processors
         * ConfigurationClassPostProcessor Configuration Annotation Processor for Internal Management
         * AutowiredAnnotationBeanPostProcessor  Internal Management @Autowire Processor
         * RequiredAnnotationBeanPostProcessor  @Required Processor
         * CommonAnnotationBeanPostProcessor  JSR-250 Annotation Processor, first determine whether it supports jsr, if it supports registration
         * PersistenceAnnotationBeanPostProcessor JPA Management first uses the class loader to find out if there is a package, and if there is one, register.
         * EventListenerMethodProcessor  @EventListener Processor
         * DefaultEventListenerFactory Managing EventListenerFactory Processors
         */
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

What does ConditionEvaluator do? Click in.

    public ConditionEvaluator(@Nullable BeanDefinitionRegistry registry,
            @Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {

        this.context = new ConditionContextImpl(registry, environment, resourceLoader); 
    }
        //ConditionContextImpl implements the ConditionContext interface, ConditionEvaluator static internal class
        public ConditionContextImpl(@Nullable BeanDefinitionRegistry registry,
                @Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {

            this.registry = registry;
            this.beanFactory = deduceBeanFactory(registry);
            this.environment = (environment != null ? environment : deduceEnvironment(registry));
            this.resourceLoader = (resourceLoader != null ? resourceLoader : deduceResourceLoader(registry));
            this.classLoader = deduceClassLoader(resourceLoader, this.beanFactory);
        }

As you can see, ConditionEvaluator initializes the Spring container top-level object using an external parameter passing method
BeanFactory, Environment, Resource Loader, Class Loader. Prepare for the next analysis of the @Conditional annotation by passing these to ConditionContextImpl

ClassPathBean Definition Scanner constructor

    public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        this(registry, true);
    }

    public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
        this(registry, useDefaultFilters, getOrCreateEnvironment(registry));
    }

    public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
            Environment environment) {
        this(registry, useDefaultFilters, environment,
                (registry instanceof ResourceLoader ? (ResourceLoader) registry : null));
    }

    public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
            Environment environment, @Nullable ResourceLoader resourceLoader) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        this.registry = registry;
        if (useDefaultFilters) {
            registerDefaultFilters();
        }
        setEnvironment(environment);
        setResourceLoader(resourceLoader);
    }

    protected void registerDefaultFilters() {
        this.includeFilters.add(new AnnotationTypeFilter(Component.class));
        ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
        try {
            this.includeFilters.add(new AnnotationTypeFilter(
                    ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
            logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
        }
        catch (ClassNotFoundException ex) {
        }
        try {
            this.includeFilters.add(new AnnotationTypeFilter(
                    ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
            logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
        }
        catch (ClassNotFoundException ex) {
            // JSR-330 API not available - simply skip.
        }
    }

Circle the Earth a few times, in fact, Spring top-level interface Environment, Resource Loader assignment, the use of default annotation filter, the first @Component into the List, to determine whether the current environment supports JSR-250, JSR-330, the corresponding filter. That is, by default, the scanner only scans Class es for @Component or JSR-250, JSR-330 tags.

applicationContext.register(BeanConfig.class)

    public void register(Class<?>... annotatedClasses) {
        Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");
        this.reader.register(annotatedClasses); //Call the reader just initialized
    }
                                            |
============================AnnotatedBeanDefinitionReader Reader code======================================================================================================
       public void register(Class<?>... annotatedClasses) {
        for (Class<?> annotatedClass : annotatedClasses) {
            registerBean(annotatedClass);
        }
     }

    public void registerBean(Class<?> annotatedClass) {
        doRegisterBean(annotatedClass, null, null, null);
    }
        
    /**
    *Resolve the annotations given by Class from a given bean, perform the corresponding initialization, and save them in the Spring container
    */
    <T> void doRegisterBean(Class<T> annotatedClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name,
            @Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {

        //Obtain metadata Annotation Metadata from Class Annotated
        AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
       /**
        *  Determine whether registered lass contains the @Conditional annotation, and if you get all the value s, put them in the List
        *  After sorting, it traverses all the Conditions implementations, uses reflection to get objects, and executes matches method.
        * If a return false is found, the interrupt loop returns true directly.
        */
        if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) { //If the @Conditional condition is not satisfied, no registration is required.
            return;
        }

        abd.setInstanceSupplier(instanceSupplier);
        //Resolve whether Class has @Scope, Resolve @Scope annotation returns ScopeMetadata object, not empty directly
        ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
        abd.setScope(scopeMetadata.getScopeName());
       //To determine whether the Value on the annotation is valued, use this as BeanName if you have it, and take the class name if you don't. 
        String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
        //Continue to parse AnnotationMetadata's @Lazy,@Primary,@DependsOn,@Role,@Description annotations and put the results into the object's properties
        AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
        //This class is just a BeanDefinition wrapper class
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
        //Whether the proxy class is needed, and if so, modify the internal properties to regenerate the BeanDefinition object
        definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
      //Call the DefaultListableBeanFactory.registerBeanDefinition method, do some security checks, and then put the definition Holder into the register container
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
    }

This method is to register beans, parse annotations on Class, initialize annotation data, do the corresponding processing, convert them into Bean Definition, and store them in Spring container.
Let's see how BeanDefinition can be registered in Spring containers, mainly by DefaultListableBeanFactory.registerBeanDefinition

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {

        Assert.hasText(beanName, "Bean name must not be empty");
        Assert.notNull(beanDefinition, "BeanDefinition must not be null");

        if (beanDefinition instanceof AbstractBeanDefinition) {
            try {
                //Check the bean Definition to determine that Method Overrides cannot be empty and must have factory methods
                ((AbstractBeanDefinition) beanDefinition).validate();
            }
            catch (BeanDefinitionValidationException ex) {
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                        "Validation of bean definition failed", ex);
            }
        }

        BeanDefinition oldBeanDefinition;

        oldBeanDefinition = this.beanDefinitionMap.get(beanName); 
        if (oldBeanDefinition != null) {
             //This method is to determine whether renamed beans are allowed and whether they can be overridden by different definitions of beans.
            if (!isAllowBeanDefinitionOverriding()) {
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                        "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
                        "': There is already [" + oldBeanDefinition + "] bound.");
            }
            else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
                // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
                            "' with a framework-generated bean definition: replacing [" +
                            oldBeanDefinition + "] with [" + beanDefinition + "]");
                }
            }
            else if (!beanDefinition.equals(oldBeanDefinition)) {
                if (this.logger.isInfoEnabled()) {
                    this.logger.info("Overriding bean definition for bean '" + beanName +
                            "' with a different definition: replacing [" + oldBeanDefinition +
                            "] with [" + beanDefinition + "]");
                }
            }
            else {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Overriding bean definition for bean '" + beanName +
                            "' with an equivalent definition: replacing [" + oldBeanDefinition +
                            "] with [" + beanDefinition + "]");
                }
            }
            this.beanDefinitionMap.put(beanName, beanDefinition);
        }
        else {
            //Call alreadyCreated.isEmpty(), alreadyCreated Set object, and save the created beanName
           //Documents show that created, registered here should not be the same behavior, this is to see what the latter will know.
            if (hasBeanCreationStarted()) {
                synchronized (this.beanDefinitionMap) {//Update data
                    this.beanDefinitionMap.put(beanName, beanDefinition);
                    List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                    updatedDefinitions.addAll(this.beanDefinitionNames);
                    updatedDefinitions.add(beanName);
                    this.beanDefinitionNames = updatedDefinitions;
                    if (this.manualSingletonNames.contains(beanName)) {
                        Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
                        updatedSingletons.remove(beanName);
                        this.manualSingletonNames = updatedSingletons;
                    }
                }
            }
            else { 
                //Spring bean Definition container, a Map reprint
                this.beanDefinitionMap.put(beanName, beanDefinition);
              //Save beanName, which is mainly used to record the order of registration for each bean
                this.beanDefinitionNames.add(beanName);
              //Delete the singleton and register it as a regular bean
                this.manualSingletonNames.remove(beanName);
            }
            this.frozenBeanDefinitionNames = null;
        }

        if (oldBeanDefinition != null || containsSingleton(beanName)) { 
            //Update beanName in Spring Container
            resetBeanDefinition(beanName);
        }
    }

Registering bean Definition into Spring containers doesn't have much complex logic, just some security checks.

BeanDefinition

A BeanDefinition describes an instance of a bean, including attribute values, constructor parameter values, and more information about classes inherited from it. BeanDefinition is just the simplest interface. Its main function is to allow BeanFactoryPostProcessor, such as PropertyPlaceHolder Configure, to retrieve and modify attribute values and metadata of other beans.
Spring container bean Definition is mainly divided into RootBean Definition and Annotated GenericBean Definition.

  • Specific bean s in RootBean Definition Spring Factory
  • Annotated GenericBean Definition User-defined Beans

Summary of Spring Startup Process

Annotation Config Application Context initialization.png

These BeanDefinition s are only put into Spirng containers, without any initialization of the object. The real IOC operation is refresh(), which is available for further analysis.

Posted by pinxxx on Sun, 19 May 2019 10:52:24 -0700