Scan the qr code at the end of this article or search the wechat official account, you can follow the wechat official account, rookie Fei Yafei, read more Spring source analysis articles

1. @ Import annotations

With Import annotations, we have three ways to register beans with the Spring container. The Spring equivalent of an XML tag.

1.1 Direct Registration

  • For example: @import (regularbean.class). RegularBean is a developer custom class. The code looks like this, with a line comment on the AppConfig class:@Import(RegularBean.class)In order to get the RegularBean instance object from the container.
/** * a custom common class */
public class RegularBean {}Copy the code
/** * Register RegularBean */ with the Spring container via Import annotations on the configuration class
@Configuration
@Import(RegularBean.class)
public class AppConfig {}Copy the code
public class MainApplication {

	public static void main(String[] args) {
		// Start the container
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
		/ / get a bean
		RegularBean bean = applicationContext.getBean(RegularBean.class);
		/ / print the bean
		/ / print results: com.tiantang.study.com ponents. 7 a675056 RegularBean @System.out.println(bean); }}Copy the code

1.2 Through the ImportSelector interface

  • The Import annotations can be registered by ImportSelector interface implementation class Bean, @ Import (DemoImportRegistrar. Class). The sample code is as follows:. DemoImportRegistrar is a developer class that implements the ImportSelector interface and overrides the selectImports() method in the string array returned by selectImports(), Added the full class name of the SelectorBean class, which is a custom class.
/** * a custom common class */
public class SelectorBean {}Copy the code
/ * * *@ImportDemoImportSelector Is a custom class that implements the ImportSelector interface */
@Configuration
@Import(DemoImportSelector.class)
public class AppConfig {}Copy the code
/** * Adds a Bean to the Spring container by implementing the ImportSelector interface * this class overrides the selectImports() method of the ImportSelector interface */
public class DemoImportSelector implements ImportSelector {

	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		// The return value of this method is a String[] array
		// Add the full class name of the class to the array so that the class can be registered with the Spring container
		return new String[]{"com.tiantang.study.components.SelectorBean"}; }}Copy the code
public class MainApplication {

	public static void main(String[] args) {
		// Start the container
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
		/ / get a bean
		SelectorBean bean = applicationContext.getBean(SelectorBean.class);
		/ / print the bean
		/ / print results: com.tiantang.study.com ponents. 4 ef37659 SelectorBean @System.out.println(bean); }}Copy the code

1.3 through ImportBeanDefinitionRegistrar interface

  • The Import annotations can be registered by ImportBeanDefinitionRegistrar interface implementation class Bean, @ Import (DemoImportRegistrar. Class). Example code is as follows: DemoImportRegistrar is a custom class developers, it implements the ImportBeanDefinitionRegistrar interface, rewrite the registerBeanDefinitions () method. RegisterBeanDefinitions () registers the RegistrarBean class with Spring through a piece of code (the RegistrarBean class is a custom class).
  • The registerBeanDefinitions() method is annotated in great detail. The code in this method may be unfamiliar to those who have never seen the BeanDefinition API.
/** * a custom common class */
public class RegistrarBean {}Copy the code
/ * * *@ImportImport DemoImportRegistrar * DemoImportRegistrar class is a custom class * * implements ImportBeanDefinitionRegistrar interface Rewrite the registerBeanDefinitions() method */
@Configuration
@Import(DemoImportRegistrar.class)
public class AppConfig {}Copy the code
/ * * * by implementing ImportBeanDefinitionRegistrar interface, * rewrite registerBeanDefinitions () method to collect the Spring container to register a bean * /
public class DemoImportRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		// Create a BeanDefinition with BeanDefinitionBuilder
		// It is also possible to create a BeanDefinition directly with the keyword new. Since BeanDefinition is an interface, an interface cannot be new, so you need to new its implementation class
		GenericBeanDefinition GenericBeanDefinition = new GenericBeanDefinition();
		// genericBeanDefinition.setBeanClass(RegistrarBean.class);
		// The above two lines are exactly equivalent to the following two lines. You can also new an AnnotatedBeanDefinition. The AppConfig class we wrote is resolved by Spring into an AnnotatedBeanDefinition
		// There are many apis, such as methods for registering beans in BeanDefinitionRegistry, and methods for setting properties for beans in BeanDefinition
		BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(RegistrarBean.class);
		AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
		
		// The above two lines parse the RegistrarBean into a BeanDefinition, and the following lines parse the BeanDefinition corresponding to the RegistrarBean class registered in Spring
		// Notice that we specify beanName for the Bean when we call the registerBeanDefinition() method of the Registry class.
		registry.registerBeanDefinition("demoBean",beanDefinition); }}Copy the code
public class MainApplication {

	public static void main(String[] args) {
		// Start the container
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
		/ / get a bean
		RegistrarBean bean = applicationContext.getBean(RegistrarBean.class);
		/ / print the bean
		/ / print results: com.tiantang.study.com ponents. 2 f465398 RegistrarBean @
		System.out.println(bean);

		// Get the bean by beanName
		RegistrarBean demoBean = (RegistrarBean)applicationContext.getBean("demoBean");
		/ / print results: com.tiantang.study.com ponents. 2 f465398 RegistrarBean @
		// The print result is the same as above, which also shows that the two are the same objectSystem.out.println(demoBean); }}Copy the code

2. Principle of @import

From the previous three demos, you saw three uses of Import annotations. Did you see that you can register beans with the Spring container without @conponentScan and @Component annotations? Why, then, is it possible to register beans with the Spring container through Import annotations? How does it work?

  • We can guess:
  • Hypothesis 1: We all know that Spring automatically creates beans for us when we start up, so thisautomaticHow does that work? In the Spring container, to create an instance object for a class, the Spring container must first parse the corresponding class to BeanDefinition before instantiating the object. So we register the Bean in the container by importing annotations, alsoA certainThe class of the registered class is resolved to BeanDefinition first.
  • Guess 2: With guess 1, a new problem arises: when to make these classes BeanDefiniton, and how to do special handling for Import annotations? There are two important processes in the startup stage of the Spring container. One is to participate in the construction of the BeanFactory through the BeanFactoryPostProcessor post-processor, and the other is to participate in the construction of the Bean through the BeanPostProcessor post-processor. The latter is to create the Bean, and BeanDefinition is required to create the Bean, so the annotation of the Import annotation is not in this process. BeanFactory is a Bean factory, so BeanDefinition, as the raw material to create the Bean, probably has special processing for Import annotations in this step. The BeanDefinition of the Bean to be registered is resolved.
  • In the previous article (Click here for the previous article) details a very important Spring class:ConfigurationClassPostProcessorAnd this class is the implementation class of BeanFactoryPostProcessor, which participates in the construction of BeanFactory. This class handles @Configuration, @ComponentScan, etc., and in fact, Import annotations are handled in this step as well.

Let’s look at how the Import annotation works. In Spring container automation, the refresh() method is executed, and postProcessBeanFactory() is called in the refresh() method. All BeanFactoryPostProcessor postprocessors are executed in the postProcessBeanFactory() method. That will be executed to ConfigurationClassPostProcessor postProcessBeanDefinitionRegistry () method, and in this method calls the processConfigBeanDefinitions () method. Below is processConfigBeanDefinitions () part of the code (with only a few lines and content about the code today, can read an article on the author’s click here to view an article, it introduces in detail the method of full code).

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	
	// Create a parser
	ConfigurationClassParser parser = new ConfigurationClassParser(
			this.metadataReaderFactory, this.problemReporter, this.environment,
			this.resourceLoader, this.componentScanBeanNameGenerator, registry);

	do {
		// Parse configuration classes, where annotations on configuration classes are resolved (classes scanned by ComponentScan, classes registered by @import, and classes defined by @bean methods)
		// Note: This step adds BeanDefinitionMap only classes with @Configuration annotations and classes scanned by @ComponentScan annotations
		// With other annotations (e.g. @import, @bean), the Parse () method step does not parse BeanDefinitionMap to BeanDefinitionMap, but first to the ConfigurationClass class
		/ / into the real map is below this. Reader. LoadBeanDefinitions () method
		parser.parse(candidates);
		
		/ / will step on the parser parsed the ConfigurationClass BeanDefinition class loading
		// Actually after the previous step parse(), the parsed beans are already in the BeanDefinition, but since they may introduce new beans, Realized ImportBeanDefinitionRegistrar or ImportSelector interface beans, for example, or exist in the bean is @ bean annotation methods
		/ / so you need to perform a loadBeanDefinition (), that will be executed ImportBeanDefinitionRegistrar ImportSelector interface method or @ Bean annotation methods
		this.reader.loadBeanDefinitions(configClasses);
		
	}
	while(! candidates.isEmpty()); }Copy the code
  • The parser. The parse () method, will eventually call to doProcessConfigurationClass () method.
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass) throws IOException {
	// omit irrelevant code...

	// Process any @Import annotations
	// Handle the beans registered with Import annotations. This step only changes the beans registered with Import to ConfigurationClass, not BeanDefinition
	// Instead, it becomes BeanDefinition in the loadBeanDefinitions() method, which then goes to BeanDefinitionMap
	processImports(configClass, sourceClass, getImports(sourceClass), true);
	
	// omit irrelevant code...
	return null;
}
Copy the code
  • As you can see, the processImports() method handles the Import annotations. In the processImports() method, three cases of Import annotations are handled separately. Method function explanation and source code as follows:
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

	for (SourceClass candidate : importCandidates) {
		if (candidate.isAssignable(ImportSelector.class)) {
			// The implementation class that handles DeferredImportSelector and calls back the selectImports() method overridden by the developerClass<? > candidateClass = candidate.loadClass(); ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class); ParserStrategyUtils.invokeAwareMethods( selector,this.environment, this.resourceLoader, this.registry);
			if (this.deferredImportSelectors ! =null && selector instanceof DeferredImportSelector) {
				this.deferredImportSelectors.add(
						new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
			}
			else {
				// The implementation class that handles DeferredImportSelector and calls back the selectImports() method overridden by the developer
				// The return value is an array of strings whose elements are the full name of the class, which is then changed to SourceClass
				// Why SourceClass? Because when parsing here, Spring parses the class through SourceClass
				String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
				Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
				// Call processImports recursively.
				/ / because the return the full name of the class represented by the class may be ImportSelector or ImportBeanDefinitionRegistrar
				processImports(configClass, currentSourceClass, importSourceClasses, false); }}else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
			/ / processing ImportBeanDefinitionRegistrar classClass<? > candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);/ / callback here developers to rewrite the ImportBeanDefinitionRegistrar registerBeanDefinitions () method
			ParserStrategyUtils.invokeAwareMethods(
					registrar, this.environment, this.resourceLoader, this.registry);
			configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
		}
		else {
			// Handle normal classes imported through Import annotations, such as regularBeans in this Demo
			/ / here you just need to directly call processConfigurationClass () method to make RegularBean a configuration class to parse
			The RegularBean type may be annotated with @conponentScan, @bean, etc
			this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); processConfigurationClass(candidate.asConfigClass(configClass)); }}}Copy the code
  • After the parse() method is executed and classes are registered with Import annotations, the corresponding BeanDefinitionMap has not been added to the factory BeanDefinitionMap. Instead, the class class is resolved to a ConfigurationClass object. Why is that? The parse() method is only used to parse classes, and the resulting classes may be special configuration classes, such as @bean annotations, @import annotations, or Spring implementation classes that extend the point interface. Therefore, it has not been added to the BeanDefinitionMap for now
  • After executing the parse() method, we then change the resolved ConfigurationClass class to a BeanDefinition through the loadBeanDefinitions() method, which we then place in a BeanDefinitionMap. In loadBeanDefinition () method will call to registerBeanDefinitionForImportedConfigurationClass ().
  • The source code is as follows. Does the following code feel familiar? Yes, the code in the DemoImportRegistrar class in the demo above is referenced here. A similar scenario in the real world requires us to add a BeanDefiniton to the container, which can be done by referring to the sample code here. (^-^ This is also a benefit of reading the source code.)
private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
	Metadata contains information about the classes we want to register, such as RegularBean, SelectorBean, RegistrarBean in this demo
	AnnotationMetadata metadata = configClass.getMetadata();
	// New a BeanDefinition implementation class
	AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);

	ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
	configBeanDef.setScope(scopeMetadata.getScopeName());
	String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
	AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);

	BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
	definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
	// Register BeanDefinitionMap with BeanDefinitionMap via the Registry object
	this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
	configClass.setBeanName(configBeanName);

}
Copy the code

3. @enable series annotations

In practice, we often encounter annotations prefixed with @enable, which is usually called enabling something. For example @ EnableAsync (open @ Async annotations can realize the function of asynchronous execution), @ EnableScheduling (open @ the Scheduling annotations to realize the function of timing task), @ EnableTransactionManagement (open things management functions), etc Annotations, especially when most projects require SpringBoot framework construction and access to micro services such as SpringCloud, it is common to encounter @Enable annotations, such as @EnableDiscoveryClient and @EnableFeignClients. Since it’s so common, it’s important to know how the @enable annotations work.

  • Below is the source code for the EnableAsync annotations
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {

	Class<? extends Annotation> annotation() default Annotation.class;

	boolean proxyTargetClass(a) default false;

	AdviceMode mode(a) default AdviceMode.PROXY;

	int order(a) default Ordered.LOWEST_PRECEDENCE;

}
Copy the code
  • In the development tool, we select an @enable annotation, such as the @enableAsync annotation. Let’s look at the source of this annotation. There are four common annotations: @target, @Retention, and @documented. The other annotation is @import.

Here are three annotations: @target, @Retention, and @Documented. The @target annotation is used to indicate where the annotation we defined can be applied to, such as elementType. TYPE for classes, elementType. METHOD for methods, and other enumeration values that refer to ElementType’s enumerated class. The @Retention annotation indicates the lifetime of the annotation we defined. Retentionpolicy.runtime means that the annotation we wrote is retained when the JVM loads our class file. Retentionpolicy. SOURCE indicates that the annotation exists in the Java SOURCE file, and when compiled into a class file, the annotation information we defined is removed; Retentionpolicy. CLASS indicates that when the CLASS is translated into a CLASS file, our custom annotations remain, but when the virtual machine loads the CLASS file, our custom annotations are not loaded. Documented indicates that an annotation will be shown in the API documentation.

  • We notice that in the @enableAsync annotation, we have the @import annotation. In @ Import annotations introduced AsyncConfigurationSelector class. As you can see from the class name, this class implements the ImportSelector interface.
/** * This class inherits AdviceModeImportSelector, AdviceModeImportSelector implements the ImportSelector interface * and overwrites selectImports(AnnotationMetadata importingClassMetadata) in the parent class. * Overloading selectImports(AdviceMode AdviceMode) in the parent class. * /
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {

	private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME =
			"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";

	@Override
	public String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				return new String[] { ProxyAsyncConfiguration.class.getName() };
			case ASPECTJ:
				return new String[] { ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME };
			default:
				return null; }}}Copy the code
  • AsyncConfigurationSelector the parent of the source code
public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {

	public static final String DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME = "mode";

	protected String getAdviceModeAttributeName(a) {
		return DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME;
	}

	// Override the method in the ImportSelector interface
	@Override
	public finalString[] selectImports(AnnotationMetadata importingClassMetadata) { Class<? > annoType = GenericTypeResolver.resolveTypeArgument(getClass(), AdviceModeImportSelector.class); AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType);if (attributes == null) {
			throw new IllegalArgumentException(String.format(
				"@%s is not present on importing class '%s' as expected",
				annoType.getSimpleName(), importingClassMetadata.getClassName()));
		}
		// the @enableAsync annotation has a mode attribute, which can specify a value.
		// The developer using @enableAsync uses the default advicemode.proxy in the @enableAsync annotation if no specific value is specified
		AdviceMode adviceMode = attributes.getEnum(this.getAdviceModeAttributeName());
		// Call the subclass's selectImports() method
		String[] imports = selectImports(adviceMode);
		if (imports == null) {
			throw new IllegalArgumentException(String.format("Unknown AdviceMode: '%s'", adviceMode));
		}
		return imports;
	}

	// Override selectImports()
	protected abstract String[] selectImports(AdviceMode adviceMode);

}
Copy the code
  • Can know from the source, you will call to selectImports AsyncConfigurationSelector class () method. This method registers different types of beans with the Spring container by returning different full class names based on the value of the mode property specified in the EnableAsync annotation. Here is, depending on the type of the specified proxy or AspectJAsyncConfiguration ProxyAsyncConfiguration class registered to the Spring container. The former is used to enhance @async annotated methods or classes through the JDK’s dynamic proxy approach, while the latter is used to enhance target methods or classes through AspectJ.

The function of @async is to make the method execute asynchronously, so the target method needs to be enhanced. Then the bytecode can be manipulated by proxy or AspectJ technology, and the bytecode can be statically woven to achieve the target.

  • Similarly, could look at the @ EnableScheduling, @ EnableTransactionManagement, @ EnableDiscoveryClient, @ EnableFeignClients annotations, whether within their source code, Have a @ the Import, class is added in the Import annotations are either framework or develop a custom class that are either ImportSelector interface implementation class, or be ImportBeanDefinitionRegistrar interface implementation class. In their respective implementation classes, they override the methods of the interface and add a class with special functionality to the Spring container to enable such and such functionality.
  • In SpringBoot, there is usually a need to integrate third-party JAR packages. Usually we introduce a starter, and then add @enablexxx to the configuration class or startup class to integrate third-party JAR packages. After reading this article, you should know the principle by now.

4. Why?

The @enable annotation is used to register a Bean in the container. Why not register a Bean with @Component annotations? You want to use @import annotations?

  • The answer is obvious, flexible. For example, some third-party JAR packages may not be necessary for some projects. If we add the @Component annotation to the class code, then when integrating with Spring, First, make sure that Spring’s ComponentScan annotation can scan for classes in third-party JAR packages, and second, it’s not appropriate to add the Bean to the Spring container with or without this feature, so it’s not flexible enough. With the @enablexxx annotation, you can use it if you want, close it if you don’t want, and you don’t need to make sure Spring scans the package name of the third-party JAR.

5. To summarize

  • This article first through the three demo Import annotations of the three usage scenarios are introduced, and combining with the source of ConfigurationClassPostProcessor class analysis of the use of the Import annotations principle.
  • The @import annotation is then used to demylate the @enable annotations. Combined with the @enableAsync annotation source code, illustrate the principle of @enable annotation.
  • Finally, I explained the benefits of using @import and @enable annotations.
  • Now, is it time to write an @enable annotated component? Or write a third party plug-in package?

6. Recommend

Finally, I recommend Pepper-Metrics, an open source performance monitoring tool of my company

  • Address: github.com/zrbcool/pep…
  • Or click here to jump to
  • Pepper-MetricsIs an open source component developed by two colleagues sitting across from me. Its main function is to interact with common open source components in a lightweight way (jedis/mybatis/motan/dubbo/servlet) integrate, collect, and calculatemetricsAnd supports output to log and conversion to multiple sequential database compatible data formats, matchinggrafana dashboardMake a friendly presentation. The principle of the project is well documented, and all based onSPIDesigned extensible architecture, easy to develop new plug-ins. There’s another one based ondocker-composeThe independentdemoProjects can start a set quicklydemoExample Viewing effecthttps://github.com/zrbcool/pepper-metrics-demo. If you find it helpful, please give me astarWelcome to participate in the development, thank you 🙂

You can scan the qr code below to follow the wechat official account, Cainiao Feiyafei, read more Spring source code together.