SpringBoot-自动配置 Enable

432 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

自动配置

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 上的地址:

github.com/SanJingYe88…