The routine of @EnableXXX annotation in Spring

Original link: https://www.mghio.cn/post/aa9d18bf.html

cover.jpeg

foreword

There are many useful functions in the Spring framework, you don’t need to write a lot of configuration code, just add a few annotations to open. One of the important reasons is those @EnableXXX annotations, which allow you to quickly enable functions such as transaction management (@EnableTransactionManagement), Spring MVC (@EnableWebMvc) or scheduled tasks (@EnableScheduling) by adding simple annotations to the configuration class. . These simple-looking annotation statements provide a lot of functionality, but their internal mechanics are not apparent on the surface. On the one hand it’s great for the user to get so much useful functionality with so little code, but on the other hand, if you don’t understand how something works internally, it makes debugging and problem solving more difficult.

Design goals

The design goal of those @EnableXXX annotations in the Spring Framework is to allow users to enable complex functions with the least amount of code. Additionally, the user must be able to use simple defaults, or allow manual configuration of the code. Finally, the complexity of the code is hidden from the framework user. In short, let the user set up a large number of beans, and selectively configure them, without having to know the details of those beans (or what is actually set). Here are a few specific examples:

@EnableScheduling (imports a @Configuration class)

The first thing to know is that the @EnableXXX annotation is not magic. In fact, the specific content of these annotations is not known in the BeanFactory, and in the BeanFactory class, there is no dependency between the core functions and specific annotations (such as @EnableWebMvc) or the jar package they store (such as spring-web). Let’s take a look at @EnableScheduling and see how it works below. Define a SchedulingConfig configuration class as follows:

 1
2
3
4
5
 @Configuration
@EnableScheduling
public class SchedulingConfig {
// some beans in here
}

There is nothing special about the above. Just a standard Java configuration annotated with @EnableScheduling. @EnableScheduling lets you execute certain methods at a set frequency. For example, you could run BankService.transferMoneyToMghio() every 10 minutes. The source code of the @EnableScheduling annotation is as follows:

 1
2
3
4
5
6
7
 @Target (ElementType.TYPE)
@Retention (RetentionPolicy.RUNTIME)
@Import (SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {

}

The EnableScheduling annotation above, we can see that it’s just a standard class-level annotation (@Target/@Retention) that should be included in the JavaDocs (@Documented), but it has a Spring-specific annotation (@Import). @Import is the key to tying everything together. In this case, since our SchedulingConfig is annotated as @EnableScheduling, when the BeanFactory parses the file (internally ConfigurationClassPostProcessor parses it), it will also find the @Import(SchedulingConfiguration.class) annotation, which will import the value defined in the type. In this annotation, it is SchedulingConfiguration.

What does import mean here? In this case it’s just being treated as another Spring Bean. SchedulingConfiguration is actually annotated as @Configuration, so BeanFactory will see it as another configuration class, and all beans defined in that class will be pulled into your application context, just like if you defined another @Configuration class yourself Same. If we check SchedulingConfiguration, we can see that it only defines one Bean (a Post Processor), which is responsible for the scheduling we described above. The source code is as follows:

 1
2
3
4
5
6
7
8
9
10
11
 @Configuration
@Role (BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {

@Bean (name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role (BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor () {
return new ScheduledAnnotationBeanPostProcessor();
}

}

You may ask, what if you want to configure the beans defined in SchedulingConfiguration? This is also just dealing with ordinary Beans. So the same mechanism you use for other beans applies here as well. In this case, ScheduledAnnotationBeanPostProcessor uses a standard Spring Bean lifecycle (postProcessAfterInitialization) to discover when the application context is refreshed. When the conditions are met, it checks to see if any beans implement SchedulingConfigurer, and if so, uses those beans to configure itself. It’s not really specific (and not easy to find in IDEs), but it’s completely separate from the BeanFactory, and it’s a fairly common pattern where one bean is used to configure another. And now that we can connect all the dots, it’s (sort of) easy to find (you can Google the documentation or read the JavaDocs.

@EnableTransactionManagement (imports an ImportSelector)

In the previous example, we discussed how annotations like @EnableScheduling can use @Import to import another @Configuration class and make all its beans available (and configurable) to your application. But what happens if you want to load different sets of beans based on some configuration? @EnableTransactionManagement is a good example. You probably have a TransactioConfig class that looks like this.

 1
2
3
4
5
 @Configuration
@EnableTransactionManagement (mode = AdviceMode.ASPECTJ)
public class TransactioConfig {
// some beans in here
}

Again, nothing special about the above. Just a standard Java configuration annotated with @EnableTransactionManagement. The only difference from the previous example is that the user specifies a parameter for the annotation (mode=AdviceMode.ASPECTJ). The @EnableTransactionManagement annotation itself looks like this.

 1
2
3
4
5
6
7
8
9
10
11
12
 @Target (ElementType.TYPE)
@Retention (RetentionPolicy.RUNTIME)
@Documented
@Import (TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {

boolean proxyTargetClass () default false ;

AdviceMode mode () default AdviceMode.PROXY ;

int order () default Ordered.LOWEST_PRECEDENCE ;
}

As before, a fairly standard annotation, although this time it has some parameters. However, as mentioned earlier, the @Import annotation is the key to tying everything together, which is again confirmed. But the difference is that this time we import the TransactionManagementConfigurationSelector class, which can be found through the source code, but it is not a class annotated with @Configuration. TransactionManagementConfigurationSelector is a class that implements ImportSelector. The purpose of ImportSelector is to let your code choose which configuration classes to load at runtime. It has a method that receives some metadata about the annotation and returns an array of class names. In this case, the TransactionManagementConfigurationSelector looks at the schema and returns some classes based on the schema. The source code of the selectImports method is as follows:

 1
2
3
4
5
6
7
8
9
10
11
12
 @Override
protected String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {AutoProxyRegistrar.class.getName(),
ProxyTransactionManagementConfiguration.class.getName()};
case ASPECTJ:
return new String[] {determineTransactionAspectClass()};
default :
return null ;
}
}

Most of these classes are @Configuration (such as ProxyTransactionManagementConfiguration), and we know from the previous introduction that they will work as before. For @Configuration classes, they are loaded and configured in exactly the same way as we saw earlier. So in short, we can use @Import and @Configuration classes to load a standard set of beans, or use @Import and ImportSelector to load a set of beans that are determined at runtime.

@EnableAspectJAutoProxy (imported at the bean definition level)

The last case supported by @Import is when you want to deal with the BeanRegistry (factory) directly. If you need to operate a Bean Factory or handle beans at the bean definition layer, then this is the case for you, it’s very similar to the case above. Your MainConfig might look like.

 1
2
3
4
5
 @Configuration
@EnableAspectJAutoProxy
public class AspectJProxyConfig {
// some beans in here
}

Again, there is nothing special about the above definition. Just a standard Java configuration annotated with @EnableAspectJAutoProxy. Below is the source code for @EnableAspectJAutoProxy.

 1
2
3
4
5
6
7
8
9
10
 @Target (ElementType.TYPE)
@Retention (RetentionPolicy.RUNTIME)
@Documented
@Import (AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

boolean proxyTargetClass () default false ;

boolean exposeProxy () default false ;
}

As before, @Import is the key, but this time it points to AspectJAutoProxyRegistrar, which neither has the @Configuration annotation nor implements the ImportSelector interface. This time, the ImportBeanDefinitionRegistrar is implemented. This interface provides access to the Bean Registry and annotation metadata, so we can operate the Bean Registry at runtime based on the parameters in the annotation. If you look closely at the previous example, you can see that the class we ignored is also ImportBeanDefinitionRegistrar. When the @Configuration class is not enough, these classes will directly operate the BeanFactory.

So now we’ve covered all the different ways the @EnableXXX annotation uses @Import to bring various beans into your application context. They either directly import a set of @Configuration classes, where all beans are imported into your application context. Or they introduce an ImportSelector interface implementation class that selects a set of @Configuration classes at runtime and imports those beans into your application context. Finally, they introduce an ImportBeanDefinitionRegistrars that can work directly with the BeanFactory at the BeanDefinition level.

in conclusion

Overall, I personally think this method of importing beans into the application context is good because it makes it very easy for framework consumers to use a feature. Unfortunately, it obscures how to find the available options and how to configure them. Also, it doesn’t take advantage of the IDE directly, so it’s hard to know which beans are being created (and why). However, now that we know about the @Import annotation, we can use the IDE to dig through each annotation and its associated configuration class and understand which beans are being created, how they are being added to your application context, and how to configure them . Hope it helps you~

This article is reprinted from: https://www.mghio.cn/post/aa9d18bf.html
This site is for inclusion only, and the copyright belongs to the original author.

Leave a Comment