Detailed explanation of @import of Spring series (bean batch registration)

Background where @Import appears

So far, the annotation method has been used to register beans in batches. In the previous two articles, we introduced two methods:

So far, we know of two ways to define beans in batches:

  1. The way @Configuration combines @Bean annotations

  2. How @ComponentScan scans packages

Let's look at a few questions.

Question 1

If the class that needs to be registered is in the third-party jar, then if we want to register these beans, there are two ways:

  1. Register one by one by @Bean annotation method

  2. The way of @ComponentScan: the default @ComponentScan is powerless. By default, only the classes marked by @Component will be registered. At this time, only the filters in @ComponentScan can be customized to achieve

Neither of these two methods is very good. Every time there is a change, there are more codes to adjust.

Question 2

Usually there are many sub-modules in our project, each module may be developed independently, and finally introduced by jar, each module has its own @Configuration, @Bean marked class, or use @ComponentScan to mark The classes marked by @Configuration, @Bean, and @ComponentScan are collectively referred to as bean configuration classes. Configuration classes can be used to register beans . What if we only want to use the configuration classes of several modules?

@Import can solve these two problems very well. Let's see how @Import works.

@Import uses

Let’s first look at Spring’s annotations on it. In summary, the function is the same as the <import /> tag of xml configuration. It allows the introduction of classes marked with @Configuration, the implementation of the ImportSelector interface and the ImportBeanDefinitionRegistrar interface, including the common @Component annotation. kind.

In general: @Import can be used to batch import various classes that need to be registered, such as common classes and configuration classes, and complete the registration of all beans in common classes and configuration classes after completion.

Source code of @Import:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

    /**
     * {@link Configuration @Configuration}, {@link ImportSelector},
     * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
     */
    Class<?>[] value();

}

@Import can be used on any type. Usually, classes and annotations are used more.

value: a Class array, setting the class to be imported, which can be the column marked by @Configuration, which can be the type of ImportSelector interface or ImportBeanDefinitionRegistrar interface, or a common component class that needs to be imported.

Steps for usage

  1. Mark @Import on the class and set the value parameter

  2. Create an AnnotationConfigApplicationContext object with the class marked by @Import as an AnnotationConfigApplicationContext construction parameter

  3. Use the AnnotationConfigApplicationContext object

There are 5 common usages of value of @Import

  1. value is an ordinary class

  2. value is the class annotated by @Configuration

  3. value is the class marked with @ComponentScan

  4. value is the interface type of ImportBeanDefinitionRegistrar

  5. value is ImportSelector interface type

  6. value is DeferredImportSelector interface type

Below we introduce each case in detail.

value is an ordinary class

come 2 classes

Service1

package com.javacode2018.lesson001.demo24.test1;

public class Service1 {
}

Service2

package com.javacode2018.lesson001.demo24.test1;

public class Service2 {
}

General configuration class: use @Import annotation

package com.javacode2018.lesson001.demo24.test1;

import org.springframework.context.annotation.Import;

@Import({Service1.class, Service2.class})
public class MainConfig1 {
}

Two common classes are imported in @Import: Service1 and Service2, and these two classes will be automatically registered in the container

test case

package com.javacode2018.lesson001.demo24;

import com.javacode2018.lesson001.demo24.test1.MainConfig1;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ImportTest {
    @Test
    public void test1() {
        //1.通过AnnotationConfigApplicationContext创建spring容器,参数为@Import标注的类
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig1.class);
        //2.输出容器中定义的所有bean信息
        for (String beanName : context.getBeanDefinitionNames()) {
            System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
        }
    }
}

run output

Part of the output is as follows:

com.javacode2018.lesson001.demo24.test1.Service1->com.javacode2018.lesson001.demo24.test1.Service1@7e0b85f9
com.javacode2018.lesson001.demo24.test1.Service2->com.javacode2018.lesson001.demo24.test1.Service2@63355449

Result analysis

As can be seen from the output:

  1. Service1 and Service2 are successfully registered to the container.

  2. 2 classes imported through @Import, the bean name is the full class name

We can also specify the bean name of the imported class, just use the @Component annotation, as follows:

@Component("service1")
public class Service1 {
}

Running test1 again outputs:

service1->com.javacode2018.lesson001.demo24.test1.Service1@45efd90f

in conclusion

Import by module, which one needs to be imported, and when not needed, directly modify the general configuration class and adjust @Import, which is very convenient.

value is the configuration class annotated by @Configuration

In the case of a relatively large project, it will be independently developed according to the modules, and each module will be constructed one by one in maven, and then the required modules will be imported through coordinates.

If there are 2 modules in the project, both modules have their own configuration classes, as follows

Module 1 configuration class

package com.javacode2018.lesson001.demo24.test2;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 模块1配置类
 */
@Configuration
public class ConfigModule1 {
    @Bean
    public String module1() {
        return "我是模块1配置类!";
    }
}

Module 2 configuration class

package com.javacode2018.lesson001.demo24.test2;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 模块2配置类
 */
@Configuration
public class ConfigModule2 {
    @Bean
    public String module2() {
        return "我是模块2配置类!";
    }
}

General configuration class: import the configuration class of 2 modules through @Import

package com.javacode2018.lesson001.demo24.test2;

import org.springframework.context.annotation.Import;

/**
 * 通过Import来汇总多个@Configuration标注的配置类
 */
@Import({ConfigModule1.class, ConfigModule2.class}) //@1
public class MainConfig2 {
}

@1 imports the module configuration classes in 2 modules, which can be imported on demand.

test case

Add a new method in ImportTest

@Test
public void test2() {
    //1.通过AnnotationConfigApplicationContext创建spring容器,参数为@Import标注的类
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
    //2.输出容器中定义的所有bean信息
    for (String beanName : context.getBeanDefinitionNames()) {
        System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
    }
}

run output

mainConfig2->com.javacode2018.lesson001.demo24.test2.MainConfig2@ba2f4ec
com.javacode2018.lesson001.demo24.test2.ConfigModule1->com.javacode2018.lesson001.demo24.test2.ConfigModule1$$EnhancerBySpringCGLIB$$700e65cd@1c1bbc4e
module1->我是模块1配置类!
com.javacode2018.lesson001.demo24.test2.ConfigModule2->com.javacode2018.lesson001.demo24.test2.ConfigModule2$$EnhancerBySpringCGLIB$$a87108ee@55fe41ea
module2->我是模块2配置类!

value is the class marked with @ComponentScan

There are multiple modules in the project, and each module has its own independent package. We configure a @ComponentScan class in the package where each module is located, and then import the modules that need to be enabled through @Import.

Define module 1

2 components and a component scanning class, the package of all classes in module 1 is:

com.javacode2018.lesson001.demo24.test3.module1

Component 1: Module1Service1

package com.javacode2018.lesson001.demo24.test3.module1;

import org.springframework.stereotype.Component;

@Component
public class Module1Service1 {
}

Component 2: Module1Service2

package com.javacode2018.lesson001.demo24.test3.module1;

import org.springframework.stereotype.Component;

@Component
public class Module1Service2 {
}

Component scan class: ComponentScanModule1

Responsible for scanning components in the current module

package com.javacode2018.lesson001.demo24.test3.module1;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;

/**
 * 模块1的主键扫描
 */
@ComponentScan
public class CompontentScanModule1 {
}

Define module 2 in the same way

2 components and a component scanning class, the package of all classes in module 1 is:

com.javacode2018.lesson001.demo24.test3.module2

Component 1: Module2Service1

package com.javacode2018.lesson001.demo24.test3.module2;

import org.springframework.stereotype.Component;

@Component
public class Module2Service1 {
}

Component 2: Module2Service2

package com.javacode2018.lesson001.demo24.test3.module2;

import org.springframework.stereotype.Component;

@Component
public class Module2Service2 {
}

Component scan class: ComponentScanModule1

Responsible for scanning components in the current module

package com.javacode2018.lesson001.demo24.test3.module2;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;

/**
 * 模块2的组件扫描
 */
@ComponentScan
public class CompontentScanModule2 {
}

Total configuration class: import the component scanning class in each module through @Import

package com.javacode2018.lesson001.demo24.test3;

import com.javacode2018.lesson001.demo24.test3.module1.CompontentScanModule1;
import com.javacode2018.lesson001.demo24.test3.module2.CompontentScanModule2;
import org.springframework.context.annotation.Import;

/**
 * 通过@Import导入多个@CompontentScan标注的配置类
 */
@Import({CompontentScanModule1.class, CompontentScanModule2.class}) //@1
public class MainConfig3 {
}

@1 imports the component scanning classes in 2 modules, which can be imported on demand.

test case

Add a new method in ImportTest

@Test
public void test3() {
    //1.通过AnnotationConfigApplicationContext创建spring容器,参数为@Import标注的类
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
    //2.输出容器中定义的所有bean信息
    for (String beanName : context.getBeanDefinitionNames()) {
        System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
    }
}

run output

Part of the output is as follows:

module1Service1->com.javacode2018.lesson001.demo24.test3.module1.Module1Service1@5b239d7d
module1Service2->com.javacode2018.lesson001.demo24.test3.module1.Module1Service2@6572421
module2Service1->com.javacode2018.lesson001.demo24.test3.module2.Module2Service1@6b81ce95
module2Service2->com.javacode2018.lesson001.demo24.test3.module2.Module2Service2@2a798d51

The 4 beans defined by @Component in both modules are exported.

If you only want to register the bean in module 1, you only need to modify @Import and remove ComponentScanModule2, as follows:

@Import({CompontentScanModule1.class})

Running it again outputs:

module1Service1->com.javacode2018.lesson001.demo24.test3.module1.Module1Service1@6379eb
module1Service2->com.javacode2018.lesson001.demo24.test3.module1.Module1Service2@294425a7

At this time, the bean of module 2 is gone.

Let's first understand a few related interfaces

ImportBeanDefinitionRegistrar interface

This interface provides a way to directly register beans in the container through the spring container api .

The full name of the interface:

org.springframework.context.annotation.ImportBeanDefinitionRegistrar

source code:

public interface ImportBeanDefinitionRegistrar {

    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
            BeanNameGenerator importBeanNameGenerator) {

        registerBeanDefinitions(importingClassMetadata, registry);
    }

    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    }

}

The two default methods can be used to call the spring container api to register beans.

There are mainly 3 parameters in the 2 methods

importingClassMetadata

AnnotationMetadata type, through which you can get all the annotation information of the class marked by @Import annotation.

registry

The BeanDefinitionRegistry type is an interface that provides various methods for registering beans internally.

importBeanNameGenerator

The BeanNameGenerator type is an interface with a method inside to generate the name of the bean.

Let's talk about the BeanDefinitionRegistry and BeanNameGenerator interfaces in detail.

BeanDefinitionRegistry interface: bean definition register

The bean definition register provides various methods of bean registration, let's take a look at the source code:

public interface BeanDefinitionRegistry extends AliasRegistry {

    /**
     * 注册一个新的bean定义
     * beanName:bean的名称
     * beanDefinition:bean定义信息
     */
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException;

    /**
     * 通过bean名称移除已注册的bean
     * beanName:bean名称
     */
    void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

    /**
     * 通过名称获取bean的定义信息
     * beanName:bean名称
     */
    BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

    /**
     * 查看beanName是否注册过
     */
    boolean containsBeanDefinition(String beanName);

    /**
     * 获取已经定义(注册)的bean名称列表
     */
    String[] getBeanDefinitionNames();

    /**
     * 返回注册器中已注册的bean数量
     */
    int getBeanDefinitionCount();

    /**
     * 确定给定的bean名称或者别名是否已在此注册表中使用
     * beanName:可以是bean名称或者bean的别名
     */
    boolean isBeanNameInUse(String beanName);

}

Basically, all bean factories implement this interface, allowing bean factories to have various capabilities for bean registration.

The classes we used above AnnotationConfigApplicationContextalso implement this interface.

BeanNameGenerator interface: bean name generator

Bean name generator, this interface has only one method, which is used to generate the name of the bean:

public interface BeanNameGenerator {
    String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);
}

Spring has 3 built-in implementations

DefaultBeanNameGenerator

The default bean name generator, when the name of the bean in xml is not specified, this generator will be used by default, the default is: complete class name#bean number

AnnotationBeanNameGenerator

The bean name generator in the annotation mode, such as specifying the bean name by @Component(bean name), if the name is not specified by annotation, the full class name will be used as the bean name by default.

FullyQualifiedAnnotationBeanNameGenerator

Use the full class name as the bean's name

BeanDefinition interface: bean definition information

The interface used to represent the bean definition information. Before we register the bean in the container, we will define various configuration information of the bean through xml or other methods. All configuration information of the bean will be converted into a BeanDefinition object, and then through the BeanDefinitionRegistry interface in the container In the method, register the BeanDefinition into the spring container to complete the bean registration operation.

There are many implementation classes for this interface. If you are interested, you can go to look at the source code and various usages of BeanDefinition, which will be explained in detail in the future.

value is the interface type of ImportBeanDefinitionRegistrar

Usage (4 steps)

1. 定义ImportBeanDefinitionRegistrar接口实现类,在registerBeanDefinitions方法中使用registry来注册bean
2. 使用@Import来导入步骤1中定义的类
3. 使用步骤2中@Import标注的类作为AnnotationConfigApplicationContext构造参数创建spring容器
4. 使用AnnotationConfigApplicationContext操作bean

the case

Come to 2 common classes.

Service1

package com.javacode2018.lesson001.demo24.test4;

public class Service1 {
}

Service2

Service1 needs to be injected into this class

package com.javacode2018.lesson001.demo24.test4;

public class Service2 {
    private Service1 service1;

    public Service1 getService1() {
        return service1;
    }

    public void setService1(Service1 service1) {
        this.service1 = service1;
    }

    @Override
    public String toString() {
        return "Service2{" +
                "service1=" + service1 +
                '}';
    }
}

Come to a class to implement the ImportBeanDefinitionRegistrar interface, and then implement the registration of the above two classes in it, as follows:

MyImportBeanDefinitionRegistrar

package com.javacode2018.lesson001.demo24.test4;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;


public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //定义一个bean:Service1
        BeanDefinition service1BeanDinition = BeanDefinitionBuilder.genericBeanDefinition(Service1.class).getBeanDefinition();
        //注册bean
        registry.registerBeanDefinition("service1", service1BeanDinition);

        //定义一个bean:Service2,通过addPropertyReference注入service1
        BeanDefinition service2BeanDinition = BeanDefinitionBuilder.genericBeanDefinition(Service2.class).
                addPropertyReference("service1", "service1").
                getBeanDefinition();
        //注册bean
        registry.registerBeanDefinition("service2", service2BeanDinition);
    }
}

Note that the registerBeanDefinitions method above registers two beans, Service1 and Service2, internally.

The BeanDefinitionBuilder class is used above. This is the constructor of BeanDefinition. It provides many static methods to facilitate the construction of BeanDefinition objects.

The two beans defined above have the same effect as the following xml method:

<bean id="service1" class="com.javacode2018.lesson001.demo24.test4.Service1" />
<bean id="service2" class="com.javacode2018.lesson001.demo24.test4.Service2">
    <property name="service1" ref="service1"/>
</bean>

Come up with a test case

Add a new method in ImportTest

@Test
public void test4() {
    //1.通过AnnotationConfigApplicationContext创建spring容器,参数为@Import标注的类
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig4.class);
    //2.输出容器中定义的所有bean信息
    for (String beanName : context.getBeanDefinitionNames()) {
        System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
    }
}

run output

service1->com.javacode2018.lesson001.demo24.test4.Service1@62150f9e
service2->Service2{service1=com.javacode2018.lesson001.demo24.test4.Service1@62150f9e}

value is ImportSelector interface type

First look at the ImportSelector interface

ImportSelector interface

Import selector, look at the source code:

public interface ImportSelector {

    /**
     * 返回需要导入的类名的数组,可以是任何普通类,配置类(@Configuration、@Bean、@CompontentScan等标注的类)
     * @importingClassMetadata:用来获取被@Import标注的类上面所有的注解信息
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);

}

Usage (4 steps)

1. 定义ImportSelector接口实现类,在selectImports返回需要导入的类的名称数组
2. 使用@Import来导入步骤1中定义的类
3. 使用步骤2中@Import标注的类作为AnnotationConfigApplicationContext构造参数创建spring容器
4. 使用AnnotationConfigApplicationContext操作bean

the case

Come to a common class: Service1

package com.javacode2018.lesson001.demo24.test5;

public class Service1 {
}

Come to a configuration class marked with @Configuration: Module1Config

package com.javacode2018.lesson001.demo24.test5;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Module1Config {
    @Bean
    public String name() {
        return "公众号:路人甲java";
    }

    @Bean
    public String address() {
        return "上海市";
    }
}

Two beans of string type are defined above: name and address

Customize an ImportSelector below, and then return the names of the above two classes

package com.javacode2018.lesson001.demo24.test5;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{
                Service1.class.getName(),
                Module1Config.class.getName()
        };
    }
}

Come to a class marked with @Import and import MyImportSelector

package com.javacode2018.lesson001.demo24.test5;

import com.javacode2018.lesson001.demo24.test4.MyImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.Import;

/**
 * 通过@Import导入MyImportSelector接口实现类
 */
@Import({MyImportSelector.class})
public class MainConfig5 {
}

Add test case

Add a new method in ImportTest

@Test
public void test5() {
    //1.通过AnnotationConfigApplicationContext创建spring容器,参数为@Import标注的类
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);
    //2.输出容器中定义的所有bean信息
    for (String beanName : context.getBeanDefinitionNames()) {
        System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
    }
}

run output

Part of the output is as follows:

com.javacode2018.lesson001.demo24.test5.Service1->com.javacode2018.lesson001.demo24.test5.Service1@45b4c3a9
name->公众号:路人甲java
address->上海市

In the output, you can see that there are two beans defined in Service1 and Module1Config.

Let's make an awesome case

need

For those whose class name contains service, call any method inside them, and we hope to output the time-consuming of these methods after calling.

Realize analysis

We talked about proxies before, and here we can implement them through proxies. During the process of creating bean instances, we can generate proxies for these beans, and count the time consumption of methods in proxies. There are two points in it:

  1. Create a proxy class to indirectly access the time-consuming bean object through the proxy

  2. Intercept the creation of beans and generate proxies for bean instances

Implementation

First come two Service classes

Service1

package com.javacode2018.lesson001.demo24.test6;

import org.springframework.stereotype.Component;

@Component
public class Service1 {
    public void m1() {
        System.out.println(this.getClass() + ".m1()");
    }
}

Service2

package com.javacode2018.lesson001.demo24.test6;

import org.springframework.stereotype.Component;

@Component
public class Service2 {
    public void m1() {
        System.out.println(this.getClass() + ".m1()");
    }
}

Create time-consuming proxy classes for statistics

Below we use cglib to implement a proxy class, as follows:

package com.javacode2018.lesson001.demo24.test6;


import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CostTimeProxy implements MethodInterceptor {
    //目标对象
    private Object target;

    public CostTimeProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        long starTime = System.nanoTime();
        //调用被代理对象(即target)的方法,获取结果
        Object result = method.invoke(target, objects); //@1
        long endTime = System.nanoTime();
        System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
        return result;
    }

    /**
     * 创建任意类的代理对象
     *
     * @param target
     * @param <T>
     * @return
     */
    public static <T> T createProxy(T target) {
        CostTimeProxy costTimeProxy = new CostTimeProxy(target);
        Enhancer enhancer = new Enhancer();
        enhancer.setCallback(costTimeProxy);
        enhancer.setSuperclass(target.getClass());
        return (T) enhancer.create();
    }
}

The createProxy method can be used to generate a proxy object for an object

If you need to know cglib, you can see: Agent Detailed Explanation (Java Dynamic Proxy & cglib Proxy)

Intercept the creation of the bean instance and return the proxy object

Here we need to use an interface in spring:

org.springframework.beans.factory.config.BeanPostProcessor

public interface BeanPostProcessor {

    /**
     * bean初始化之后会调用的方法
     */
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    /**
     * bean初始化之后会调用的方法
     */
    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

}

This interface is a bean processor, and there are two methods inside, which will be called before and after the bean is initialized, and will be described in detail later when we talk about the declaration cycle. Here you only need to know that the method will be called after the bean is initialized. In this postProcessAfterInitializationmethod We will create a proxy object for the bean.

Below we create a BeanPostProcessor implementation class:

package com.javacode2018.lesson001.demo24.test6;

import com.javacode2018.lesson001.demo23.test4.CostTimeProxy;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.Nullable;

public class MethodCostTimeProxyBeanPostProcessor implements BeanPostProcessor {
    @Nullable
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean.getClass().getName().toLowerCase().contains("service")) {
            return CostTimeProxy.createProxy(bean); //@1
        } else {
            return bean;
        }
    }
}

@1: Use the proxy class created above to create a proxy for the current bean object

The MethodCostTimeProxyBeanPostProcessor needs to be registered in the container to work. Next, we import this class through @Import combined with ImportSelector and register it in the container.

MethodCostTimeImportSelector

package com.javacode2018.lesson001.demo24.test6;


import com.javacode2018.lesson001.demo23.test4.MethodCostTimeProxyBeanPostProcessor;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class MethodCostTimeImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{MethodCostTimeProxyBeanPostProcessor.class.getName()};
    }
}

Come to a @Import to import MethodCostTimeImportSelector

Next, we use annotations to use @Import on annotations, as follows:

package com.javacode2018.lesson001.demo24.test6;


import com.javacode2018.lesson001.demo23.test4.MethodCostTimeImportSelector;
import org.springframework.context.annotation.Import;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MethodCostTimeImportSelector.class)
public @interface EnableMethodCostTime {
}

Come to a general configuration class

package com.javacode2018.lesson001.demo24.test6;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
@EnableMethodCostTime //@1
public class MainConfig6 {
}

The @ComponentScan annotation is used above, and the two classes Servce1 and Service2 will be registered in the container at this time.

@1: The @EnableMethodCostTime annotation is used here, and @Import(MethodCostTimeImportSelector.class) is used on the @EnableMethodCostTime annotation. At this time, the MethodCostTimeProxyBeanPostProcessor in the MethodCostTimeImportSelector class will be registered to the container, which will intercept the creation of beans and create time-consuming proxy objects .

Come up with a test case

Add a new method in ImportTest

@Test
public void test6() {
    //1.通过AnnotationConfigApplicationContext创建spring容器,参数为@Import标注的类
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig6.class);
    Service1 service1 = context.getBean(Service1.class);
    Service2 service2 = context.getBean(Service2.class);
    service1.m1();
    service2.m1();
}

The methods of service1 and service2 will be called above

run output

class com.javacode2018.lesson001.demo24.test6.Service1.m1()
public void com.javacode2018.lesson001.demo24.test6.Service1.m1(),耗时(纳秒):74200
class com.javacode2018.lesson001.demo24.test6.Service2.m1()
public void com.javacode2018.lesson001.demo24.test6.Service2.m1(),耗时(纳秒):33800

It's awesome, the demand has been fulfilled.

If we don't want to enable the time-consuming statistics of the method, we only need to remove @EnableMethodCostTime on MainConfig6. Isn't it very cool to use?

There are many similar annotations in spring. The annotations starting with @EnableXXX are basically implemented in the above way, such as:

@EnableAspectJAutoProxy
@EnableCaching
@EnableAsync

Continue to look down, and there is a more powerful interface DeferredImportSelector.

DeferredImportSelector interface

Let me tell you first, the core function @EnableAutoConfiguration in springboot is realized by DeferredImportSelector.

DeferredImportSelector is a subinterface of ImportSelector. Since it is a subinterface of ImportSelector, it can also be imported through @Import. There are two differences between this interface and ImportSelector:

  1. delayed import

  2. Specifies the processing order of imported classes

delayed import

For example, the value of @Import includes multiple common classes, multiple configuration classes marked by @Configuration, multiple implementation classes of the ImportSelector interface, multiple implementation classes of the ImportBeanDefinitionRegistrar interface, and the implementation class of the DeferredImportSelector interface. At this time, spring processes these by When importing classes, the DeferredImportSelector type will be processed last, and other imported classes will be processed first, and other classes will be processed in the order in which the value is located .

Then we can do many things. For example, we can judge whether a bean has been registered in the container in the class imported by DeferredImportSelector. If it has not been registered, then register again.

In the future, we will talk about another annotation @Conditional. This annotation can register beans according to conditions. For example, it can be judged that a bean is registered only when it does not exist, and it is registered only when a certain class exists. Combining DeferredImportSelector with @Conditional can do a lot of things.

A case of delayed import

There are 3 configuration classes, and each configuration class defines a bean of type string through @Bean, and outputs a sentence of text internally.

Configuration1

package com.javacode2018.lesson001.demo24.test7;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Configuration1 {
    @Bean
    public String name1() {
        System.out.println("name1");
        return "name1";
    }
}

Configuration2

package com.javacode2018.lesson001.demo24.test7;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Configuration2 {
    @Bean
    public String name2() {
        System.out.println("name2");
        return "name2";
    }
}

Configuration3

package com.javacode2018.lesson001.demo24.test7;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Configuration3 {
    @Bean
    public String name3() {
        System.out.println("name3");
        return "name3";
    }
}

Come to an ImportSelector implementation class, import Configuration1

package com.javacode2018.lesson001.demo24.test7;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class ImportSelector1 implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{
                Configuration1.class.getName()
        };
    }
}

Come to a DeferredImportSelector implementation class and import Configuration2

package com.javacode2018.lesson001.demo24.test7;

import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class DeferredImportSelector1 implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{Configuration2.class.getName()};
    }
}

Come to a general configuration class

package com.javacode2018.lesson001.demo24.test7;

import org.springframework.context.annotation.Import;

@Import({
        DeferredImportSelector1.class,
        Configuration3.class,
        ImportSelector1.class,
})
public class MainConfig7 {
}

Note the order of imported classes in @Import above:

DeferredImportSelector1->Configuration3->ImportSelector1

Let's take a test case to see the order in which the methods marked by @Bean in the three configuration files are executed.

test case

Add a new method in ImportTest

@Test
public void test7() {
    //1.通过AnnotationConfigApplicationContext创建spring容器,参数为@Import标注的类
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig7.class);
}

run output

name3
name1
name2

Combining the output results with the order of the three imported classes in @Import, it can be seen that DeferredImportSelector1 is processed last, and the other two are processed in the order in which they are located in value.

Specifies the processing order of imported classes

When there are multiple implementation classes of the DeferredImportSelector interface in @Import, their order can be specified. There are two common ways to specify the order

Ways to implement the Ordered interface

org.springframework.core.Ordered

public interface Ordered {

    int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;

    int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

    int getOrder();

}

The smaller the value, the higher the priority.

The way to implement the Order annotation

org.springframework.core.annotation.Order

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {

    int value() default Ordered.LOWEST_PRECEDENCE;

}

The smaller the value, the higher the priority.

Let's take a case to experience it.

Let's take a case of specifying the processing order of imported classes

There are two configuration classes, each of which has a method marked @Bean, which is used to register a bean, and a line of text is output inside the method

Configuration1

package com.javacode2018.lesson001.demo24.test8;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Configuration1 {
    @Bean
    public String name1() {
        System.out.println("name1");
        return "name1";
    }
}

Configuration2

package com.javacode2018.lesson001.demo24.test8;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Configuration2 {
    @Bean
    public String name2() {
        System.out.println("name2");
        return "name2";
    }
}

Come to two DeferredImportSelector implementation classes to import the above two configuration files respectively, and specify the order through the Ordered interface by the way

DeferredImportSelector1

package com.javacode2018.lesson001.demo24.test8;

import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.Ordered;
import org.springframework.core.type.AnnotationMetadata;

public class DeferredImportSelector1 implements DeferredImportSelector, Ordered {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{Configuration1.class.getName()};
    }

    @Override
    public int getOrder() {
        return 2;
    }
}

DeferredImportSelector2

package com.javacode2018.lesson001.demo24.test8;

import com.javacode2018.lesson001.demo24.test7.Configuration2;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.Ordered;
import org.springframework.core.type.AnnotationMetadata;

public class DeferredImportSelector2 implements DeferredImportSelector, Ordered {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{Configuration2.class.getName()};
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

The order of DeferredImportSelector1 is 2, and the order of DeferredImportSelector2 is 1. The smaller the order value, the higher the priority.

Come to a general configuration class and introduce the above two ImportSelectors

MainConfig8

package com.javacode2018.lesson001.demo24.test8;


import org.springframework.context.annotation.Import;

@Import({
        DeferredImportSelector1.class,
        DeferredImportSelector2.class,
})
public class MainConfig8 {
}

test case

Add a new method in ImportTest

@Test
public void test8() {
    //1.通过AnnotationConfigApplicationContext创建spring容器,参数为@Import标注的类
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig8.class);
}

run output

name2
name1

The result is matched with the value of order, and the order is processed from small to large. It can be seen that DeferredImportSelector2 is processed first.

The source code of this block in Spring

The @Import annotation is processed by the following class

org.springframework.context.annotation.ConfigurationClassPostProcessor

The @Configuration, @Bean, @ComponentScan, and @ComponentScans introduced earlier are all processed by this class. This class is the only way for masters. It is recommended to spend some time researching and researching.

Case source code

https://gitee.com/javacode2018/spring-series

Passerby A All java case codes will be put on this in the future, everyone watch it, you can continue to pay attention to the dynamics.

Summarize

  1. @Import can be used to batch import any common components and configuration classes, and register all beans defined in these classes into the container

  2. 5 common usages of @Import need to be mastered

  3. Master the usage of ImportSelector, ImportBeanDefinitionRegistrar, DeferredImportSelector

  4. The DeferredImportSelector interface can implement the functions of delayed import and sequential import

  5. Many springs that start with @Enable are implemented using the @Import collection ImportSelector

  6. BeanDefinitionRegistry interface: bean definition register, this needs to master common methods

Guess you like

Origin blog.csdn.net/weixin_46228112/article/details/124538351#comments_27336838