背景
在学习SpringBoot的时候,我们会使用到@Enable***注解的地方,使用上也都是加在@Configuration 类注解的类上面,比如:
(1)@EnableAutoConfiguration 开启自动扫描装配Bean
(2)@EnableScheduling 开启计划任务的支持
(3)@EnableTransactionManagement 开启注解式事务的支持。
(4)@EnableCaching开启注解式的缓存支持。
(5)@EnableAspectJAutoProxy 开启对AspectJ自动代理的支持,
(6) @EnableAsync 开启异步方法的支持
(7) @EnableWebMvc 开启Web MVC的配置支持。
(8) @EnableConfigurationProperties 开启对@ConfigurationProperties注解配置Bean的支持。
(9)@EnableJpaRepositories 开启对Spring Data JPA Repository的支持。
通过上述的一些注解装配,可以省去了以往xml的很多配置。那么@Enable是如何实现的呢?
何为@Enable模块装配
Spring Framework 3.1 开始支持”@Enable 模块驱动“。所谓“模块”是指具备相同领域的功能组件集合, 组合所形成一个独立
的单元。比如 Web MVC 模块、AspectJ代理模块、Caching(缓存)模块、JMX(Java 管 理扩展)模块、Async(异步处
理)模块等。
模块装配是spring-boot 另外一种装配方式
在spring框架中@EnableWebMvc就是自动组装webMVc相关的组件
在spring-boot框架中@EnableAutoConfiguration 开启自动扫描装配Bean
- 实现方式
- 注解方式
- 编程方式
查看@Enable的源码
查看这些注解的实现,我们发现每一个注解都有一个 @Import 注解。@Import注解在4.2之前只支持导入配置类,在4.2,@Import注解支持导入普通的java类,并将其声明成一个bean。这也说明了,自动开启的实现,其实是导入了一些配置类。
@EnableWebMvc采用注解驱动方式
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
//...
}
@EnableCaching采用接口编程方式
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({CachingConfigurationSelector.class})
public @interface EnableCaching {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default 2147483647;
}
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.cache.annotation;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.AdviceModeImportSelector;
import org.springframework.context.annotation.AutoProxyRegistrar;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
private static final String PROXY_JCACHE_CONFIGURATION_CLASS = "org.springframework.cache.jcache.config.ProxyJCacheConfiguration";
private static final String CACHE_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.cache.aspectj.AspectJCachingConfiguration";
private static final String JCACHE_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.cache.aspectj.AspectJJCacheConfiguration";
private static final boolean jsr107Present;
private static final boolean jcacheImplPresent;
public CachingConfigurationSelector() {
}
public String[] selectImports(AdviceMode adviceMode) {
switch(adviceMode) {
case PROXY:
return this.getProxyImports();
case ASPECTJ:
return this.getAspectJImports();
default:
return null;
}
}
private String[] getProxyImports() {
List<String> result = new ArrayList(3);
result.add(AutoProxyRegistrar.class.getName());
result.add(ProxyCachingConfiguration.class.getName());
if (jsr107Present && jcacheImplPresent) {
result.add("org.springframework.cache.jcache.config.ProxyJCacheConfiguration");
}
return StringUtils.toStringArray(result);
}
private String[] getAspectJImports() {
List<String> result = new ArrayList(2);
result.add("org.springframework.cache.aspectj.AspectJCachingConfiguration");
if (jsr107Present && jcacheImplPresent) {
result.add("org.springframework.cache.aspectj.AspectJJCacheConfiguration");
}
return StringUtils.toStringArray(result);
}
static {
ClassLoader classLoader = CachingConfigurationSelector.class.getClassLoader();
jsr107Present = ClassUtils.isPresent("javax.cache.Cache", classLoader);
jcacheImplPresent = ClassUtils.isPresent("org.springframework.cache.jcache.config.ProxyJCacheConfiguration", classLoader);
}
}
package org.springframework.context.annotation;
import org.springframework.core.type.AnnotationMetadata;
public interface ImportSelector {
String[] selectImports(AnnotationMetadata var1);
}
通过源码可以看到:
- 首先@EnableCaching需要@Import一个CachingConfigurationSelector类
- CachingConfigurationSelector又继承AdviceModeImportSelector
- AdviceModeImportSelector又实现了ImportSelector
- 所有,当我们自定以一个@Enable模块时需要ImportSelector的实现类
总结
从上述的两个@Enbale注解看,有两种实现方式。
下面我们分别进行自定义@enable模块
自定义@Enable模块
基于注解驱动实现
定义给一个配置类
package com.dsdj.springbootdemo1.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @ClassName MyConfiguration
* @Description 我的自定义配置类
* @Author dsdj
* @Date 2018/12/16 下午10:01
* @Version 1.0
**/
@Configuration
public class MyConfiguration {
/**
* 方法名为 Bean 名称
* @return
*/
@Bean
public String test() {
return "自定义配置类";
}
}
使用注解的方式,@Import需要导入一个配置类。
package com.dsdj.springbootdemo1.annotation;
import com.dsdj.springbootdemo1.configuration.MyConfiguration;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* @ClassName EnableMyConfig
* @Description 自定义enable模块装载
* @Author dsdj
* @Date 2018/12/16 下午10:32
* @Version 1.0
**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({MyConfiguration.class})
public @interface EnableMyConfig {
}
package com.dsdj.springbootdemo1.bootstrap;
import com.dsdj.springbootdemo1.annotation.EnableMyConfig;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
/**
* @ClassName EnableTestBootstrap
* @Description 测试自定义@Enable模块装载
* @Author dsdj
* @Date 2018/12/16 下午10:34
* @Version 1.0
**/
// 使用自定义的Enable加载相关的bean模块
@EnableMyConfig
public class EnableTestBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableTestBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
// 验证test bean是否存在
String test = context.getBean("test",String.class);
System.out.println("bean是否存在--->"+test);
}
}
测试结果
2018-12-16 22:54:09.653 INFO 27590 --- [ main] c.d.s.bootstrap.EnableTestBootstrap : Started EnableTestBootstrap in 0.593 seconds (JVM running for 1.045)
bean是否存在--->自定义配置类
总结
使用注解方式需要在@EnableXXX中直接添加配置类既即可以。
基于接口驱动实现
配置类
package com.dsdj.springbootdemo1.configuration;
import org.springframework.context.annotation.Bean;
/**
* @ClassName MyConfiguration
* @Description 配置类
* @Author dsdj
* @Date 2018/12/16 下午10:01
* @Version 1.0
**/
public class MyConfiguration {
/**
* 方法名为 Bean 名称
* @return
*/
@Bean
public String test() {
return "自定义配置类";
}
}
自定义ImportSelector的实现类。
package com.dsdj.springbootdemo1.annotation;
import com.dsdj.springbootdemo1.configuration.MyConfiguration;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
/**
* @ClassName MyImportSelector
* @Description 自定义ImportSelector的实现类
* @Author dsdj
* @Date 2018/12/16 下午9:57
* @Version 1.0
**/
public class MyImportSelector implements ImportSelector {
/**
*
* @param annotationMetadata
* @return
*/
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 返回一个自定义类
// 使用MyImportSelector导入MyConfiguration,而不是直接导入MyConfiguration
return new String[]{MyConfiguration.class.getName()};
}
}
自定义@Enable
package com.dsdj.springbootdemo1.annotation;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* @ClassName EnableMyConfig
* @Description 自定义enable模块装载
* @Author dsdj
* @Date 2018/12/16 下午10:32
* @Version 1.0
**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({MyImportSelector.class})
public @interface EnableMyConfig {
}
spring-boot启动类
package com.dsdj.springbootdemo1.bootstrap;
import com.dsdj.springbootdemo1.annotation.EnableMyConfig;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
/**
* @ClassName EnableTestBootstrap
* @Description 测试自定义@Enable模块装载
* @Author dsdj
* @Date 2018/12/16 下午10:34
* @Version 1.0
**/
// 使用自定义的Enable加载相关的bean模块
@EnableMyConfig
public class EnableTestBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableTestBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
// 验证test bean是否存在
String test = context.getBean("test",String.class);
System.out.println("bean是否存在--->"+test);
}
}
测试结果
package com.dsdj.springbootdemo1.bootstrap;
import com.dsdj.springbootdemo1.annotation.EnableMyConfig;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
/**
* @ClassName EnableTestBootstrap
* @Description 测试自定义@Enable模块装载
* @Author dsdj
* @Date 2018/12/16 下午10:34
* @Version 1.0
**/
// 使用自定义的Enable加载相关的bean模块
@EnableMyConfig
public class EnableTestBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableTestBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
// 验证test bean是否存在
String test = context.getBean("test",String.class);
System.out.println("bean是否存在--->"+test);
}
}
总结
- 运行顺序
首先,EnableTestBootstrap启动,之后根据@EnableMyConfig注解,找到MyImportSelector类,找到selectImports方法,进行加载“test”bean操作。
- 对比
这种实现方式,我们可以在装载bean中间进行一些操作,相对注解实现方式比较自由,编程实现方式可以有一些弹性操作。
总结
此时我们在回顾看开头的两种enbale装配是不是清晰了。