@Role of Import annotation

catalogue

1. Introduction

2. Source code analysis

2.1 three types of import configuration

2.2 source code interpretation

3. Test example

3.1 import general class

3.2 import Configuration class with @ Configuration

3.3 classes imported through ImportSelector

3.4 classes imported through ImportBeanDefinitionRegistrar

4.2 processImports method

1. Introduction


The @ Import annotation will appear on the source code or many configuration classes. Its function is the same as that in Spring XML@ The Import annotation is used to Import configuration classes or some classes that need to be preloaded

2. Source code analysis


2.1 three types of import configuration


@Import supports three methods
1. Configuration class with @ configuration (only configuration classes can be imported before version 4.2, and ordinary classes can also be imported after version 4.2)
2. Implementation of importselector
3. Implementation of importbeandefinitionregistrar

2.2 source code interpretation
 

/**
 * Indicates one or more {@link Configuration @Configuration} classes to import.
 * 
 *The function is similar to < import / > in XML. You can import @ Configuration configuration classes, ImportSelector
 * ImportBeanDefinitionRegistrar After version 4.2, you can import ordinary classes (similar to AnnotationConfigApplicationContext#register
 * )
 * <p>Provides functionality equivalent to the {@code <import/>} element in Spring XML.
 * Allows for importing {@code @Configuration} classes, {@link ImportSelector} and
 * {@link ImportBeanDefinitionRegistrar} implementations, as well as regular component
 * classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}).
 *
 * It can be declared at the class level or as a meta annotation
 * <p>May be declared at the class level or as a meta-annotation.
 * If you need to import XML or other types of files, use @ ImportResource annotation
 * <p>If XML or other non-{@code @Configuration} bean definition resources need to be
 * imported, use the {@link ImportResource @ImportResource} annotation instead.
 */
 @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

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

}

3. Test example

3.1 import general class

  1. Create a new TestA
    public class TestA {
    
        public void fun(String str) {
            System.out.println(str);
        }
    
        public void printName() {
            System.out.println("Class name:" + Thread.currentThread().getStackTrace()[1].getClassName());
        }
    }
    

  2. Create a new ImportConfig and add @ Configuration on the class to enable Spring to scan the class and directly Import the TestA class through @ Import
    @Import({TestA.class})
    @Configuration
    public class ImportConfig {
    }
    

    3. Test results
    TestA is a generic class. It can now be annotated and invoked by @Autowired, which directly indicates that it has been injected and managed by Spring, and ordinary classes need to be instantiated first.
     

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest(classes = ApplicationMain.class)
    public class ImportAnnotionTest {
    
        @Autowired
        TestA testA;
    
        @Test
        public void TestA() {
            testA.printName();
        }
    }
    

    Print:

    Class name: com.test.importdemo.TestA

3.2 import Configuration class with @ Configuration

  1. New TestB
    @Configuration
    public class TestB {
        public void fun(String str) {
            System.out.println(str);
        }
    
        public void printName() {
            System.out.println("Class name:" + Thread.currentThread().getStackTrace()[1].getClassName());
        }
    }
    

  2. In importconfig Class directly introduces TestB
    @Import({TestA.class,TestB.class})
    @Configuration
    public class ImportConfig {
    }
    

    3. Test results
    TestB.class has @ Configuration annotation on it, so it will be configured with spring to scan and instance, @ import introduces the Configuration file with @ Configuration, which needs to be instantiated before relevant operations
     

       @Autowired
        TestB testB;
    
    
        @Test
        public void TestB(){
            testB.printName();
        }
    

    Print:
     

    ImportAnnotionTest in 8.149 seconds (JVM running for 10.104)
    Class name: com.test.importdemo.TestB
    2019-01-31 14:12:05.737  INFO 23760 --- [       Thread-2]
    

    3.3 classes imported through ImportSelector

  3. New testc class
    public class TestC {
        public void fun(String str) {
            System.out.println(str);
        }
    
        public void printName() {
            System.out.println("Class name:" + Thread.currentThread().getStackTrace()[1].getClassName());
        }
    }
    

    2. Create a selfimportselector Class implements the ImportSelector interface and injects testc class
    //TODO ImportSelector related explanations
     

    public class SelfImportSelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[]{"com.test.importdemo.TestC"};
        }
    }
    

    3. Selfimportselector. Is introduced into importconfig class
     

    @Import({TestA.class,TestB.class,SelfImportSelector.class})
    @Configuration
    public class ImportConfig {
    }
    

    4. Test results
     

        @Autowired
        TestC testC;
    
        @Test
        public void TestC() {
            testC.printName();
        }
    

    Print:
     

    ImportAnnotionTest in 7.23 seconds (JVM running for 9.065)
    Class name: com.test.importdemo.TestC
    2019-01-31 14:23:15.330  INFO 1196 --- [ 
    

    3.4 classes imported through ImportBeanDefinitionRegistrar

    1. Create a new testd class
     

    public class TestD {
        public void fun(String str) {
            System.out.println(str);
        }
    
        public void printName() {
            System.out.println("Class name:" + Thread.currentThread().getStackTrace()[1].getClassName());
        }
    }
    

    2. Create a selfimportbeandefinitionregistrar Class, implement the interface ImportBeanDefinitionRegistrar, and inject testd class
     

    public class SelfImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            RootBeanDefinition root = new RootBeanDefinition(TestD.class);
            registry.registerBeanDefinition("testD", root);
        }
    }
    

    3. Add selfimportbeandefinitionregistrar to importconfig class class
     

    @Import({TestA.class,TestB.class,SelfImportSelector.class,
            SelfImportBeanDefinitionRegistrar.class})
    @Configuration
    public class ImportConfig {
    }
    

    4. Test results
     

        @Autowired
        TestD testD;
    
    
        @Test
        public void TestD() {
            testD.printName();
        }
    

    Print:
     

    ImportAnnotionTest in 7.817 seconds (JVM running for 9.874)
    Class name: com.test.importdemo.TestD
    2019-01-31 14:30:05.781  INFO 23476 --- [ 
    

    Through the above methods, you can successfully inject Spring

    4. Detailed process analysis
    Here we mainly look at configurationclassparser Java inside
    Doprocessconfigurationclass (configurationclass, configclass, sourceclass) this method Locate the 302 lines of code of the source code
    Why not comb from the beginning? The Spring startup process here is complex. If you comb from the beginning, there are many things involved. Many people will be tired and give up. We will be familiar with each other and summarize them at last

    4.1 getImports method
    Before analyzing this method, let's take a look at the getImports method, which is to get all the classes in @ import
    Here is to get the classes in @ import. The general process is as follows:
    1. Define a visited set, which is used as a flag to determine whether it has been judged
    2. Here is to get all the annotations above sourceClass and judge one by one. If it is not @ import, then call the corresponding annotations recursively until they are all finished
    3. Load the corresponding class name in @ Import annotation in sourceClass, and finally return
     

    	private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
    		Set<SourceClass> imports = new LinkedHashSet<>();
    		Set<SourceClass> visited = new LinkedHashSet<>();
    		collectImports(sourceClass, imports, visited);
    		return imports;
    	}
    	// Here is to get all the annotations above sourceClass. If it is not @ import, then call the corresponding annotations recursively until they are all finished
    	private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
    			throws IOException {
    
    		if (visited.add(sourceClass)) {
    			for (SourceClass annotation : sourceClass.getAnnotations()) {
    				String annName = annotation.getMetadata().getClassName();
    				if (!annName.equals(Import.class.getName())) {
    					collectImports(annotation, imports, visited);
    				}
    			}
    			imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
    		}
    	}
    
    

    4.2 processImports method

    The code logic of processImports method is also very clear. The flow chart is as follows:
     

    The general process is as follows:

    Judge whether importCandidates is empty and exit if it is empty

    Judge isChainedImportOnStack. If it is true, add error in the problemReporter and exit

    Add the current configClass to ImportStack, which inherits ArrayDeque // TODO and implements ImportRegistry// TODO

    Traverse the classes that need to be imported obtained from getImports

    4.1 if it is an ImportSelector type, first instance an ImportSelector object, and then extend it with Aware (if Aware interface is implemented)

    4.1.2 further judge whether it is the DeferredImportSelector type. If so, add it to deferredImportSelectors for final processing. Here you can see the method parse(Set configCandidates), which is called only in the last line. That is, sometimes, if you want to inject finally, you can define it as deferredImportSelectors type

    4.1.2 if it is not the DeferredImportSelector type, call the selectImports method to get all the classes to be injected. Then call the processImports method again. Calling the processImports method here actually treats these classes to be injected as ordinary @ Configuration

    If it is an ImportBeanDefinitionRegistrar type, an object is first instantiated here, and then added to importBeanDefinitionRegistrars. Later, it will be processed in the loadBeanDefinitionsFromRegistrars method of the ConfigurationClassBeanDefinitionReader class

    6. If neither of the above two types is true, it is the ordinary class with @ Configuration

    --------
    Copyright notice: This is the original article of CSDN blogger "always forging iron", which follows the CC 4.0 BY-SA copyright agreement. Please attach the original source link and this notice for reprint.
    Original link: https://blog.csdn.net/mamamalululu00000000/article/details/86711079

Added by ljCharlie on Sun, 23 Jan 2022 22:36:02 +0200