spring boot2.x 如何巧妙的自定义@EnableXXX模块装配功能

一、前言

在平时使用spring boot的时候,很多时候都会用到@EnableXXX的注解,来装配一些功能模块,有代表性的,比如:

  • @EnableWebMvc 开启Web MVC的配置支持;
  • @EnableCaching 开启注解式的缓存支持。

如果,想自定义实现这些功能,我们应该怎么做呢?先看一下spring boot是怎么帮我们做的。

  1. @EnableWebMvc(采用基于注解驱动方式
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}

三个元注解,没有什么好说的,重点是@Import注解,@Import注解支持导入普通的java类,并将其声明成一个bean。这也说明了,自动开启的实现,其实是导入了一些配置类。

@Configuration(
    proxyBeanMethods = false
)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

从源码来说,DelegatingWebMvcConfiguration 确实是一个配置类。

  1. @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 Ordered.LOWEST_PRECEDENCE;

}

可以看到,@EnableCaching注解与@EnableWebMvc注解差不多,只是多了一些注解元素,那么这些注解元素是怎么解析的呢,看一下@Import注解导入的CachingConfigurationSelector的继承关系:

在这里插入图片描述
注意ImportSelector接口,当我们自定义一个基于接口驱动的@EnableXXX注解时,模块需要实现ImportSelector接口,

public interface ImportSelector {

	String[] selectImports(AnnotationMetadata importingClassMetadata);

	@Nullable
	default Predicate<String> getExclusionFilter() {
		return null;
	}
}

ImportSelector接口的实现类,主要通过selectImports方法,来解析我们自定义的注解元素。

二、自定义基于注解驱动的@EnableXXX

1.创建一个简单的spring boot工程,项目结构如下:

在这里插入图片描述
为了方便演示,我直接把自定义的@EnableSystem注解放在同一个spring boot工程上了,实际开发肯定不是这样的。

注意:@EnableSystem所在包,不是启动类SpringbootEnableAnnotationApplication所在包的子包,这样是为了spring boot在启动的时候不会扫描到@EnableSystem注解的相关类!

  1. 创建一个SystemConfiguration配置类:
@Configuration
public class SystemConfiguration {
    @Bean
    public String fileExtension() {
        return ".bat";
    }
}
  1. 创建自定义@EnableSystem注解:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({SystemConfiguration.class})
public @interface EnableSystem {
}
  1. 测试
  • 不在启动类上加@EnableSystem 注解:
@SpringBootApplication
public class SpringbootEnableAnnotationApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableAnnotationApplication.class, args);
        String fileExtension = context.getBean("fileExtension", String.class);
        System.out.println("fileExtension=" + fileExtension);
    }
}

运行结果,抛出异常:

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'fileExtension' available
  • 在启动类上加@EnableSystem 注解:
@SpringBootApplication
@EnableSystem
public class SpringbootEnableAnnotationApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableAnnotationApplication.class, args);
        String fileExtension = context.getBean("fileExtension", String.class);
        System.out.println("fileExtension=" + fileExtension);
    }
}

运行结果,正常:

fileExtension=.bat

这样就实现了一个自定义@EnableSystem模块装配功能了,如果,我们想自动装配这个模块,连注解都不想使用,可不可以呢,答案是可以的。

自动装配

实现自动装备步骤:

在resources目录下,新建META-INF文件夹,然后创建spring.factories文件:
在这里插入图片描述
spring.factories:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.annoenable.SystemConfiguration

在启动类中注释掉@EnableSystem注解,启动:

@SpringBootApplication
//@EnableSystem
public class SpringbootEnableAnnotationApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableAnnotationApplication.class, args);
        String fileExtension = context.getBean("fileExtension", String.class);
        System.out.println("fileExtension=" + fileExtension);
    }
}

运行结果:

fileExtension=.bat

可以看到是正常运行了,可是.bat是Windows系统的可执行文件后缀名,如果是Linux系统,我不想加载这个Bean了,怎么办呢?

自定义注解@ConditionalOnXXX

想要解决上面的区分Windows和Linux系统问题,我们可以再自定义一个条件装配注解,步骤如下:

在@EnableSystem注解包下,再自定义一个@ConditionalOnSystem注解:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({SystemCondition.class})
public @interface ConditionalOnSystem {

    String value();
}

自定义注解主要是依赖@Conditional注解,它可以根据代码中设置的条件装载不同的bean。不过既然要使用@Conditional注解,就要自定义一个类实现Condition,编写自己的过滤条件:

public class SystemCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        // 获取@ConditionalOnSystem注解的所有注解元素
        Map<String, Object> attributes = annotatedTypeMetadata.getAnnotationAttributes(ConditionalOnSystem.class.getName());
        //获取注解元素value的属性值
        String value = (String) attributes.get("value");
        // 判断传入是不是windows
        return "windows".equals(value);
    }
}

重新修改一下SystemConfiguration中的代码:

@Configuration
public class SystemConfiguration {
    @Bean
    @ConditionalOnSystem("windows")
    public String windowsFileExtension() {
        return ".bat";
    }
}

测试一下:

public class SpringbootEnableAnnotationApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableAnnotationApplication.class, args);
        boolean b = context.containsBean("windowsFileExtension");
        System.out.println("windowsFileExtension : " + b);
    }
}

运行结果:

windowsFileExtension : true

如果 @ConditionalOnSystem(“windows”)的值不是Windows,就不会加载这个Bean了。

三、自定义基于接口驱动的@EnableXXX

以上,基于注解驱动的@EnableXXX装配模块感觉很方便了,但是总感觉不够灵活,那么接下来再实现一下,使用自定义基于接口驱动的@EnableXXX装配模块吧。

声明:以下工程和自定义基于注解驱动的@EnableXXX操作类型,重复地方,不再赘述。

  1. 创建一个spring boot项目,项目结构如下:

在这里插入图片描述
2. 创建WindowsSystemConfiguration配置类:

@Configuration
public class WindowsSystemConfiguration {
    @Bean
    public String fileExtension() {
        return ".bat";
    }
}
  1. 创建LinuxSystemConfiguration配置类:
@Configuration
public class LinuxSystemConfiguration {

    @Bean
    public String fileExtension() {
        return ".sh";
    }
}
  1. 编写自定义注解@EnableSystemSelector:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({SystemSelector.class})
public @interface EnableSystemSelector {
    // 加载配置时,判断是Windows系统还是Linux系统
    boolean isWindows() default true; //默认是Windows系统
}

可以看到,这里也是依赖@Import注解,上面已经分析过,自定义的SystemSelector类,是需要继承ImportSelector接口的。

  1. 实现自定义的SystemSelector类,处理@EnableSystemSelector注解元素数据:
public class SystemSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 获取@EnableSystemSelector注解的所有注解元素
        Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableSystemSelector.class.getName());
        //获取注解元素isWindows的属性值
        boolean isWindows = (boolean) annotationAttributes.get("isWindows");
        //判断加载Windows还是linux相关配置类
        return new String[]{isWindows ? WindowsSystemConfiguration.class.getName() : LinuxSystemConfiguration.class.getName()};
    }
}
  1. 测试:

到此,自定义基于接口驱动的@EnableSystemSelector 就写好了,下面测试一下:

  • 测试Windows配置
@SpringBootApplication
@EnableSystemSelector
public class SpringbootEnableInterfaceApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableInterfaceApplication.class, args);
        String fileExtension = context.getBean("fileExtension", String.class);
        System.out.println("fileExtension=" + fileExtension);
    }
}

运行结果:

fileExtension=.bat
  • 测试Linux配置
@SpringBootApplication
@EnableSystemSelector(isWindows = false) //把isWindows属性改成false
public class SpringbootEnableInterfaceApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableInterfaceApplication.class, args);
        String fileExtension = context.getBean("fileExtension", String.class);
        System.out.println("fileExtension=" + fileExtension);
    }
}

运行结果:

fileExtension=.sh

四、总结

到此,文章介绍了

  • 自定义基于注解驱动的@EnableXXX模块装配功能;
  • 自定义基于接口驱动的@EnableXXX模块装配功能。

两种实现方式,虽然演示的案例都十分简陋和不规范,但是核心实现代码还是可以参考一下的。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值