原文地址:http://blog.fawnanddoug.com/2012/08/how-those-spring-enable-annotations-work.html

Spring's Java Config is a great way to configure your application without writing a lot of configuration code.  One reason is those awesome @Enable* annotations that let you magically set up things like Transactions (@EnableTransactionManagement), Spring MVC (@EnableWebMvc) or timed jobs (@EnableScheduling) with a simple class level annotation on your configuration class. These simple statements provide a lot of functionality but their machinations are fairly obscure.  On the one hand, it's great to get so much functionality for so little work, but on the other hand, if you don't understand how something works it makes debugging and problem solving much harder.  I couldn't find any posts or documents that covered how those annotations work so I figured I would write up one based on the research I did while debugging.  I don't work for Spring and I didn't write any of this code so please post any corrections or improvements in the comments and I'll update the post.

Design Goals

From what I can tell, the design goal of those @Enable* annotations is to allow the user set up complex functionality with a minimal amount of code.  In addition, it seems clear that users must be able to use either a  simple default or be allowed to manually configure that code.  Finally, the complexities of the code are intended to be hidden from the user.  In short, let the user set up a lot of beans and optionally configure them without having to know the details of those beans (or really what's being set up).  I think there are a lot of pros (and some cons) to this approach but I'll discuss that at the end.

Case #1: @EnableScheduling (importing an @Configuration class)

The first thing to note is that the @Enable* annotations are not magic.  Nothing in the Bean Factory knows anything about them specifically and there are no dependencies in the Bean Factory classes between the core functionality and specific annotations (like @EnableWebMvc) or the jars they're stored in (like spring-web).  Let's take a look at @EnableScheduling and see how it works.  You might have a MainConfig class that looks like this:

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

There's nothing special in the above. Just a standard Java Config that's annotated with @EnableScheduling. @EnableScheduling lets you execute certain methods at a set frequency. For example, you can run BankService.sendMoneyToDoug() every 20 minutes.   The @EnableScheduling annotation itself looks like this:

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

If we look at the annotation above, we can see that it is just a standard Class level annotation (@Target/@Retention) that should be included in JavaDocs (@Documented), but it has one Spring-specific annotation: @Import.  @Import is the key that ties everything together.  In this case, since our MainConfig is annotated as @EnableScheduling, when the Bean Factory is parsing the file (technically the ConfigurationClassPostProcessor is parsing it) it will also find the @Import(SchedulingConfiguration.class) annotation and it will import the class defined in the value.  In this case SchedulingConfiguration.  What does it mean to import?  Well, in this case it's just treated as another Spring bean.  SchedulingConfiguration is actually annotated as @Configuration, so the Bean Factory will see it as another configuration class and all of the beans defined in that class will get pulled into your Application Context just as if you had defined another @Configuration class yourself.  If we inspect SchedulingConfiguration we can see that it only defines one bean (a Post Processor) which does the scheduling we described above.

1
2
3
4
5
6
7
8
9
10
@Configuration
public class SchedulingConfiguration {
 
 @Bean(name=AnnotationConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
 @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
  return new ScheduledAnnotationBeanPostProcessor();
 }
 
}

OK, but what if I want to configure the beans defined in SchedulingConfiguration?  Well, at this point we're just dealing with regular beans.  So the same mechanisms that you would use for any other beans apply here.  In this case, the ScheduledAnnotationBeanPostProcessor uses a standard Spring Application Event to find out when the Application Context is refreshed.  When this happens it checks to see if any beans implement SchedulingConfigurer and if so, uses those beans to configure itself.  This is not at all intuitive (or easy to find with an IDE) but it is completely separated from the Bean Factory and is a fairly common pattern for a bean to be used to configure another bean.  And now that we can connect all of the dots it is (somewhat) easy to find (or you could google the documentation or read the JavaDocs).

Case #2: @EnableTransactionManagement (importing an ImportSelector)

In the previous case we discussed how an annotation like @EnableScheduling can use @Import to pull in another @Configuration Class and make all of its beans available (and configurable) to your application. But what happens if you want to load a different set of beans based on some configuration?  @EnableTransactionManaged is a good example of this.  You might have a MainConfig class that looks like this:
 
1
2
3
4
5
@Configuration
@EnableTransactionManagement(mode=AdviceMode.ASPECTJ)
public class MainConfig {
// some beans
}
 
Once again, there's  nothing special in the above. Just a standard Java Config that's annotated with @EnableTransactionManagement. The only thing that's a little different from the previous example is that the user specified a parameter to the annotation (mode=AdviceMode.ASPECTJ).  The @EnableTransactionManagement annotation itself looks like this:

1
2
3
4
5
6
7
8
9
@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, I mentioned previously that the @Import annotation was the key that ties everything together and that is true once again.  The distinction though, is that this time we are importing TransactionManagementConfigurationSelector.class which is not a class that is annotated with @Configuration.  TransactionManagementConfigurationSelector is a class that implements ImportSelector.  The purpose of ImportSelector is to allow your code to choose which configuration classes to load at runtime.  It has one method that takes some metadata about an annotation and returns an array of class names.  In this case, the TransactionManagementConfigurationSelector looks at the mode and returns some classes based on the mode:

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

Most of these classes are @Configuration classes (e.g. ProxyTransactionManagementConfiguration) so we know that they'll work like before, but some of them are not (let's ignore those for now).  For the @Configuration classes they get loaded and configured in the exact same way that we previously saw.  So in short, we can use @Import with an @Configuration class to load a standard set of beans or we can use @Import with an ImportSelector to load a set of beans that are decided at run time.  Sweet! But what about those classes we ignored?

Case #3: @EnableAspectJAutoProxy (importing at the Bean Definition Level)

@Import supports one last case which is when you want to deal with a Bean Registry (Factory) directly. If you need to manipulate the Bean Factory or work with beans at the Bean Definition level then this case is for you and it's very similar to the ones above.  Your MainConfig might look like:

1
2
3
4
5
@Configuration
@EnableAspectJAutoProxy
public class MainConfig {
// some beans
}

Once again, there's nothing special in the above. Just a standard Java Config that's annotated with @EnableAspectJAutoProxy.  Here's the source for @EnableAspectJAutoProxy:

1
2
3
4
5
6
7
8
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
 boolean proxyTargetClass() default false;
 
}

As before, the @Import is the key, but this time it's pointing to AspectJAutoProxyRegistrar which is neither an @Configuration class nor implements ImportSelector.  The trick this time is that it implements ImportBeanDefinitionRegistrar.  This interface gives access to the Bean Registry and Annotation Metadata so that we can manipulate the registry at runtime based off of the parameters in the annotation.  If you look at the previous case you can see that the classes we ignored were also ImportBeanDefinitionRegistrars.  These classes directly manipulate the Bean  Factory for those times when @Configuration classes aren't enough.

So now we've covered all of the different ways the @Enable* annotations use @Import to pull various beans into your Application Context.  They either pull in a set of @Configuration classes directly and all of the beans from those classes get imported into your Application Context.  Or they pull in an ImportSelector which chooses a set of @Configuration classes at runtime and imports those beans into your Application Context.  Or finally, they pull in an ImportBeanDefinitionRegistrars which can directly work with the Bean Factory at the Bean Definition level.

Conclusion

In general I think this approach to importing beans into an Application Context is great because it makes set up very easy for the developer.  Unfortunately it obscures how to find the available options and how to configure them.  In addition, it doesn't directly take advantage of the IDE so it's hard to tell which beans are being created (and why).  However, now that we know about the @Import annotation we can use our IDE to dig a little into each Annotation and its related configuration classes and understand which beans are being created, how they're being added to your Application Context and how to configure them.  I hope this helps! Please leave comments to let me know what you think and what I've missed / messed up.

How those spring enable annotations work--转的更多相关文章

  1. Spring Enable annotation – writing a custom Enable annotation

    原文地址:https://www.javacodegeeks.com/2015/04/spring-enable-annotation-writing-a-custom-enable-annotati ...

  2. Spring Enable* 注解

    Spring提供了一系列以Enable开头的注解,这些注解本质上是激活Spring的某些管理功能.比如,EnableWebMvc. 这个注解引入了MVC框架在Spring 应用中需要用到的所有bean ...

  3. Spring Project Annotations

       Project  Annotation  Discovered By  Package     Target(s)  Parameters  Notes . AspectJ @EnableSpr ...

  4. Spring Enable*高级应用及原理

    Enable* 之前的文章用到了一些Enable*开头的注解,比如EnableAsync.EnableScheduling.EnableAspectJAutoProxy.EnableCaching等, ...

  5. Spring Enable高级应用及原理

    Enable* 之前的文章用到了一些Enable*开头的注解,比如EnableAsync.EnableScheduling.EnableAspectJAutoProxy.EnableCaching等, ...

  6. 译:Spring框架参考文档之IoC容器(未完成)

    6. IoC容器 6.1 Spring IoC容器和bean介绍 这一章节介绍了Spring框架的控制反转(IoC)实现的原理.IoC也被称作依赖注入(DI).It is a process wher ...

  7. Developing JSF applications with Spring Boot

    Developing JSF applications with Spring Boot Spring Boot can leverage any type of applications, not ...

  8. spring@PropertySource用法

    测试例子 package com.hjzgg.auth.config; import org.springframework.beans.factory.annotation.Autowired; i ...

  9. Spring框架文档与API(4.3.6版本)

    http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/ Table of Contents I ...

随机推荐

  1. js-静态、原型、实例属性

    本篇来说一下js中的属性: 1.静态属性 2.原型属性 3.实例属性 静态属性: function klass(){} var obj=new klass(); klass.count=0; klas ...

  2. .NET 提升教育 第一期:VIP 付费课程培训通知!

    为响应 @当年在远方 同学的建议,在年前尝试进行一次付费的VIP培训. 培训的课件:点击下载培训周期:10个课程左右,每晚1个半小时培训价格:1000元/人.报名方式:有意向的请加QQ群:路过秋天.N ...

  3. 来,给Entity Framework热热身

    先来看一下Entity Framework缓慢的初始化速度给我们更新程序带来的一种痛苦. 我们手动更新程序时通常的操作步骤如下: 1)把Web服务器从负载均衡中摘下来 2)更新程序 3)预热(发出一个 ...

  4. 【腾讯bugly干货分享】HTML 5 视频直播一站式扫盲

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=1277 视频直 ...

  5. 从零开始编写自己的C#框架(27)——什么是开发框架

    前言 做为一个程序员,在开发的过程中会发现,有框架同无框架,做起事来是完全不同的概念,关系到开发的效率.程序的健壮.性能.团队协作.后续功能维护.扩展......等方方面面的事情.很多朋友在学习搭建自 ...

  6. 【原创分享·微信支付】C# MVC 微信支付之微信模板消息推送

    微信支付之微信模板消息推送                    今天我要跟大家分享的是“模板消息”的推送,这玩意呢,你说用途嘛,那还是真真的牛逼呐.原因在哪?就是因为它是依赖微信生存的呀,所以他能不 ...

  7. PayPal高级工程总监:读完这100篇论文 就能成大数据高手(附论文下载)

    100 open source Big Data architecture papers for data professionals. 读完这100篇论文 就能成大数据高手 作者 白宁超 2016年 ...

  8. 代码的坏味道(17)——夸夸其谈未来性(Speculative Generality)

    坏味道--夸夸其谈未来性(Speculative Generality) 特征 存在未被使用的类.函数.字段或参数. 问题原因 有时,代码仅仅为了支持未来的特性而产生,然而却一直未实现.结果,代码变得 ...

  9. C# Entity Framework并发处理

    原网站:C# Entity Framework并发处理 在软件开发过程中,并发控制是确保及时纠正由并发操作导致的错误的一种机制.从 ADO.NET 到 LINQ to SQL 再到如今的 ADO.NE ...

  10. 【干货分享】流程DEMO-付款申请单

    流程名: 付款申请单  业务描述: 包括每月固定开支.固定资产付款.办公用品付款.工资发放.个人所得税缴纳.营业税缴纳.公积金.社保缴纳和已签订合同的按期付款,最后是出纳付款,出纳核对发票. 流程发起 ...