目录
本篇文章主要讨论enable前缀的注解的使用方法和实现原理:
主要内容如下:
一、前言
在springboot中很多enable开头的注解,如@EnableConfigurationProperties,@EnableAsync等,这些注解的作用就是:启动一个新特性,现在演示2个例子。
二、作用和案例
1.@EnableConfigurationProperties
入口函数上的注解,默认使用的是@SpringBootConfiguration,其实我们也可以使用@EnableAutoConfiguration + @ComponentScan,甚至是@EnableConfigurationProperties +@ComponentScan来注入bean,详细我们可以举个例子,看下:
application.properties
tomcat.host=192.168.157.1 tomcat.port=8080
TomcatProperties 配置类
@SpringBootConfiguration @ConfigurationProperties(prefix = "tomcat") public class TomcatProperties { private String host; private Integer port; @Override public String toString() { return "TomcatProperties{" + "tomcat.host='" + host + '\'' + ", tomcat.port=" + port + '}'; } public String getHost() { return host; } public void setHost(String host) { this.host = host; } public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; } }
测试类:
@SpringBootApplication public class Demo5Application { //案例1; public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(Demo5Application.class, args); System.out.println(context.getBean(TomcatProperties.class)); context.close(); }
运行结果:
改变1:@SpringBootApplication改成 @ComponentScan和@EnableAutoConfiguration看下效果:
@EnableAutoConfiguration @ComponentScan public class Demo5Application { //案例1; public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(Demo5Application.class, args); System.out.println(context.getBean(TomcatProperties.class)); context.close(); } }
运行结果:也可以注入进来配置文件的属性!
改变2:上面的注解再次把@EnableAutoConfiguration换成
@EnableConfigurationProperties @EnableConfigurationProperties @ComponentScan public class Demo5Application { //案例1; public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(Demo5Application.class, args); System.out.println(context.getBean(TomcatProperties.class)); context.close(); } }
再次运行:
结果显示都可以将属性注入进来,说明一个问题,上面的注解都可以把TomcatProperties这样的配置类对应的配置属性注入进来。
这几个注解都具有这个特性:可以把配置文件的属性注入到bean里面去。
注意的是:需要配合配置类上的@ConfigurationProperties使用。
2.@EnableAync实现异步方法
@EnableAsync 作用: 启用异步方法,一般是和@Async一起使用.
举例说明:
@Component public class Jeep implements Runnable { public void run() { try { for (int i = 1; i <= 10; i++) { System.out.println("======" + i); TimeUnit.SECONDS.sleep(1); } } catch (Exception e) { e.printStackTrace(); } } }
@ComponentScan public class Demo5Application { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(Demo5Application.class, args); context.getBean(Runnable.class).run(); System.out.println("-------end-------------"); context.close(); }
运行结果:
打印的end是main里面的方法说明是同步执行的,我们来修改下代码:
@Component public class Jeep implements Runnable { @Async public void run() { try { for (int i = 1; i <= 10; i++) { System.out.println("======" + i); TimeUnit.SECONDS.sleep(1); } } catch (Exception e) { e.printStackTrace(); } } }
@ComponentScan @EnableAsync public class Demo5Application { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(Demo5Application.class, args); context.getBean(Runnable.class).run(); System.out.println("-------end-------------"); context.close(); }
运行结果,先执行了main,方法,然后慢慢执行,说明是异步执行的。
enable*等注解既然这么好用,那么是如何实现的呢?接着分析:
三、enable*等注解能够实现的原理
我们看下@EnableAsync。
发现有个@Import注解。
@EnableConfigurationProperties注解:
也有个注解@Import。
再看下@Import,有个value属性,可以指定多个类或者配置类
也就是说明:能够自动启用的原因和这个@Import的注解和它里面的value属性有关。
@Import的注解可以导入某个bean(在value属性中指定)或配置类(在value属性中指定)到容器中,而且value属性指定的类有某些特定的功能,我们只要用@import导入就可了。
1.使用import导入一个bean或者配置bean
我们就试试这个import的能不能帮我们导入一个bean或者配置类:
首先定义2个bean和一个配置bean,都没加@Component:
public class User { }
public class Role { }
public class MyConfiguration { @Bean public Runnable createRunnable(){ return () -> {}; } @Bean public Runnable createRunnable2(){ return () -> {}; } }
入口中导入这3个bean:
@ComponentScan @Import({User.class, Role.class, MyConfiguration.class}) public class App { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(App.class, args); System.out.println(context.getBean(User.class)); System.out.println(context.getBean(Role.class)); System.out.println(context.getBeansOfType(Runnable.class)); context.close(); } }
看下执行结果:
全部打印出来了,说明import已经帮我们注入了。
2.使用@import导入一个bean前加点自己的逻辑
刚才我们实践了import导入类了,再进一步考虑下,能不能在导入类前加入自己的逻辑,以前学过直接实现beanDefination等接口,可是这样的话会影响所有的bean。能不能缩小点范围,只影响我们需要导入的类呢?
可以的。思路就是可以定义一个注解,在需要加载的bean上添加注解就可以过滤了。
注解里面用@import导入一些加载bean的处理器,在处理器方法中进行我们自己的逻辑。
这里提到了2点:一个是注解,注解就是我们之前用的enable前缀作为开头的注解。一个处理器。我们看下springboot源码中:
有2类:
①.实现了ImportBeanDefinitionRegistrar接口的处理器
看下@EnableConfigurationProperties注解
②.实现了ImportSelector接口的处理器
看下:@EnableAsync
主要是利用importSelector的selectImports方法:
③案例:
知道了这两类接口的原理。我们来几个案例:
a.实现ImportSelector导入类并在导入类时一点逻辑
MyImportSelector
selectImports方法的返回值,必须是一个class(全称),该class会被spring容器所托管起来
//selectImports方法的返回值,必须是一个class(全称),该class会被spring容器所托管起来 public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { System.out.println(importingClassMetadata.getAnnotationAttributes(EnableLog.class.getName())); /** * 这里可以获取直接的详细信息,然后根据信息去动态的返回需要被Spring容器管理的bean */ return new String[]{"com.springboot.demo5.User",Role.class.getName(),MyConfiguration.class.getName()}; } }
这个处理器,加入了3个类,并在这3个类注入的时候加点逻辑。
自定义注解@EnableLog
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import({MyImportSelector.class}) public @interface EnableLog { String name() default ""; }
测试入口类:
@ComponentScan @EnableLog(name="my springboot") public class App2 { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(App2.class, args); System.out.println(context.getBean(User.class)); System.out.println(context.getBean(Role.class)); System.out.println(context.getBeansOfType(Runnable.class)); context.close(); } }
可以看到通过@EnableLog注解,我们注入了bean,并且打印了我们添加的业务代码:
b.实现ImportBeanDefinitionRegistrar导入类
/** * registerBeanDefinitions方法的参数有一个BeanDefinitionRegistry, * BeanDefinitionRegistry 可以用来往spring容器中注入bean * 如此,我们就可以在registerBeanDefinitions方法里面动态的注入bean */ public class MyImportBeanDefinitionRegister implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition(User.class); registry.registerBeanDefinition("user", bdb.getBeanDefinition()); BeanDefinitionBuilder bdb2 = BeanDefinitionBuilder.rootBeanDefinition(Role.class); registry.registerBeanDefinition("role", bdb2.getBeanDefinition()); BeanDefinitionBuilder bdb3 = BeanDefinitionBuilder.rootBeanDefinition(MyConfiguration.class); registry.registerBeanDefinition(MyConfiguration.class.getName(), bdb3.getBeanDefinition()); } }
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import({MyImportBeanDefinitionRegister.class}) public @interface EnableLog { String name() default ""; }
测试下:
@ComponentScan @EnableLog(name="my springboot") public class App2 { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(App2.class, args); System.out.println(context.getBean(User.class)); System.out.println(context.getBean(Role.class)); System.out.println(context.getBeansOfType(Runnable.class)); context.close(); } }
看下结果,同样注入了bean:
四、手动实现个enable*注解案例
案例:我们手动实现个注解,主要在某个包下,我们就打印包下的所有类的权限定名:
准备2个包,和2个bean
@Component public class Cat { }
@Component public class Dog { }
EchoImportBeanDefinitionRegistrar import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata; import java.util.Arrays; import java.util.List; import java.util.Map; public class EchoImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { Map attr = importingClassMetadata.getAnnotationAttributes(EnableEcho.class.getName()); String[] packAttr = (String[]) attr.get("packages"); List packages = Arrays.asList(packAttr); System.out.println("packages: "+ packages); BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition(EchoBeanPostProcessor.class); bdb.addPropertyValue("packages",packages); registry.registerBeanDefinition(EchoBeanPostProcessor.class.getName(), bdb.getBeanDefinition()); } }
EchoBeanPostProcessor
public class EchoBeanPostProcessor implements BeanPostProcessor { private List packages; @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { for (String pack: packages){ if (bean.getClass().getName().startsWith(pack)){ System.out.println("echo bean : " + bean.getClass().getName()); } } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } public List getPackages() { return packages; } public void setPackages(List packages) { this.packages = packages; } }
@EnableEcho
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import({EchoImportBeanDefinitionRegistrar.class}) public @interface EnableEcho { String[] packages() ; }
测试:
@ComponentScan @EnableLog(name="my springboot") @EnableEcho(packages = {"com.springboot.demo5.bean","com.springboot.demo5.vo"}) public class App3 { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(App3.class, args); context.close(); } }
运行结果:
结果显示如预期。
回顾下:本篇主要介绍了enable前缀注解的使用和原理,主要根据import导入,importSelector或者ImportBeanDefinitionRegistrar实现类为参数,结合我们可以根据enable注解上的参数来控制操作bean,或者在注入bean前加入我们自己的逻辑。