本文已参与「新人创作礼」活动,一起开启掘金创作之路。
自动配置
SpringBoot 中使用部分功能时,通过引入依赖,在启动类上添加 @Enablexxx 注解,即可启用相应的功能,常见的 Enable 注解有:
- @EnableAsync : 开启异步
- @EnableScheduling : 开启定时器
- @EnableTransactionManagement 开启注解式事物
- @EnableCaching 开启 Cache 缓存
- @EnableDiscoveryClient Spring cloud 实现服务发现
- @EnableEurekaServer Spring cloud 实现服务注册
Enable 注解原理
@Enable 注解的底层原理是使用@Import注解导入指定配置类,从而实现 Bean 的动态加载。
背景
在一个 SpringBoot 项目 A 中,引入了依赖 B,( 可以自己写两个项目来测试 ),假如说依赖 B 中使用 @Bean 注解定义了一个 Bean,那我们启动项目 A,然后 在 项目 A 中获取 这个 Bean 的实例,是否能获取到呢?
答案是不能获取,因为 @SpringBootApplication 注解的扫描范围是当前项目启动类所在的包以及子包,所以是没有办法扫描到依赖 B 的 包,并创建 Bean 实例的。
既然扫描不到,那我们增加一下扫描的范围不就可以了?代码如下:
@SpringBootApplication
@ComponentScan("xxx.xxx.xx.config") // 依赖 B 的 Bean 所在的包
public class SpringbootEnableApplication {
...
}
这样确实的是可以获取到了.
上面的方式确实是可以使用了,但是当我们要开启一个功能时,还需要知道要扫描的包的路径,这样很不方便。
我们知道 @Import 注解可以直接把配置类加入 Spring 中,进行简单修改如下:
@SpringBootApplication
@Import(xxxxConfig.class) // 依赖 B 的 Config Bean
public class SpringbootEnableApplication {
...
}
这样记住类型比记住包名要简单一些。
那能不能让依赖去干这件事儿呢?让依赖自己去引入指定的配置,加载指定的 Bean 呢?
这就是 @Enable 注解诞生的用处了。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(XXXXConfig.class)
public @interface EnableXXXX {
}
在依赖 B 中自己提供一个 @Enable 注解,在该注解上引入需要的配置 Bean。这样用户使用起来也只需要使用@Enable 注解即可,不但方便,而且很容易记住。
总结
SpringBoot 应用默认扫描不到其他工程的 bean
原因:@ComponentScan 扫描范围:当前引导类所在包及其子包
解决方案:
- 使用 @ComponentScan 扫描第三方工程的包
- 使用 @Import 注解,加载类。这些类都会被Spring创建,并放入IOC容器
- 对 @Import 注解封装为 @Enable 注解。
重点:Enable 注解底层原理是使用 @Import 注解实现 Bean 的动态加载。
自己实现 Ebable
比如说,我们自己实现一个 输出所有请求日志的 Enable 注解功能。
整个流程比较简单:
1.要输出所有请求日志,使用 Filter 可以方便的实现,所以编写 Filter 如下:
public class RequestLogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("请求日志开始:" + new Date());
chain.doFilter(request, response);
System.out.println("请求日志结束:" + new Date());
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
2.然后就是把这个 FIlter 注册为 Bean.
@ConditionalOnWebApplication
public class RequestLogFilterWebConfig {
@Bean
public RequestLogFilter requestLogFilter() {
return new RequestLogFilter();
}
}
3.自定义 Enable 注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(RequestLogFilterWebConfig.class) // 注意这里
public @interface EnableRequestLog {
}
4.在启动类上使用:
@EnableRequestLog // 使用
public class CmsApplication{
...
}
此处代码在 Github 上的地址: