Spring-boot|如何自定义@Enable模块装配

Spring-boot|如何自定义@Enable模块装配

背景

在学习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采用注解驱动方式

1
2
3
4
5
6
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
1
2
3
4
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
//...
}

@EnableCaching采用接口编程方式

1
2
3
4
5
6
7
8
9
10
11
@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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//
// 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);
}
}
1
2
3
4
5
6
7
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模块

基于注解驱动实现

定义给一个配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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需要导入一个配置类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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);
}
}

测试结果

1
2
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中直接添加配置类既即可以。

基于接口驱动实现

配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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的实现类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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);
}
}

测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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装配是不是清晰了。

坚持原创技术分享,您的支持将鼓励我继续创作!