0
点赞
收藏
分享

微信扫一扫

springboot原理实战(8)--enable前缀注解之开启特性的原理和案例


文章目录

  • ​​目录​​
  • ​​一、前言​​
  • ​​二、作用和案例​​
  • ​​1.@EnableConfigurationProperties​​
  • ​​2.@EnableAync实现异步方法​​
  • ​​三、enable*等注解能够实现的原理​​
  • ​​1.使用import导入一个bean或者配置bean​​
  • ​​2.使用@import导入一个bean前加点自己的逻辑​​
  • ​​①.实现了ImportBeanDefinitionRegistrar接口的处理器​​
  • ​​②.实现了ImportSelector接口的处理器​​
  • ​​③案例:​​
  • ​​a.实现ImportSelector导入类并在导入类时一点逻辑​​
  • ​​b.实现ImportBeanDefinitionRegistrar导入类​​
  • ​​四、手动实现个enable*注解案例​​

目录

本篇文章主要讨论enable前缀的注解的使用方法和实现原理:

主要内容如下:

springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_ide

一、前言

在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();
}

运行结果:

springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_ide_02


改变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();
}
}

运行结果:也可以注入进来配置文件的属性!

springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_tomcat_03


改变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();
}
}

再次运行:

springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_spring_04


结果显示都可以将属性注入进来,说明一个问题,上面的注解都可以把​​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();
}

运行结果:

springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_spring boot_05


打印的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,方法,然后慢慢执行,说明是异步执行的。

springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_tomcat_06


enable*等注解既然这么好用,那么是如何实现的呢?接着分析:

三、enable*等注解能够实现的原理

我们看下@EnableAsync。

springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_spring boot_07


发现有个@Import注解。

@EnableConfigurationProperties注解:

springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_spring boot_08


也有个注解@Import。

再看下@Import,有个value属性,可以指定多个类或者配置类

springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_spring_09


springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_spring_10


也就是说明:能够自动启用的原因和这个​​@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();
}
}

看下执行结果:

springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_spring boot_11


全部打印出来了,说明import已经帮我们注入了。

2.使用@import导入一个bean前加点自己的逻辑

刚才我们实践了import导入类了,再进一步考虑下,能不能在导入类前加入自己的逻辑,以前学过直接实现beanDefination等接口,可是这样的话会影响所有的bean。能不能缩小点范围,只影响我们需要导入的类呢?
可以的。​​​思路就是可以定义一个注解,在需要加载的bean上添加注解就可以过滤了。​​​ 注解里面用@import导入一些加载bean的处理器,在处理器方法中进行我们自己的逻辑。
这里提到了2点:一个是注解,注解就是我们之前用的enable前缀作为开头的注解。一个处理器。我们看下springboot源码中:
有2类:

①.实现了ImportBeanDefinitionRegistrar接口的处理器

  • 看下@EnableConfigurationProperties注解
  • springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_spring boot_12


  • springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_ide_13

②.实现了ImportSelector接口的处理器

看下:@EnableAsync

springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_spring_14


springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_spring boot_15


主要是利用importSelector的selectImports方法:

springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_tomcat_16

③案例:

知道了这两类接口的原理。我们来几个案例:

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,并且打印了我们添加的业务代码:

springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_ide_17

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:

springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_spring_18

四、手动实现个enable*注解案例

案例:我们手动实现个注解,主要在某个包下,我们就打印包下的所有类的权限定名:

准备2个包,和2个bean

springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_spring boot_19

@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<String, Object> attr = importingClassMetadata.getAnnotationAttributes(EnableEcho.class.getName());
String[] packAttr = (String[]) attr.get("packages");
List<String> 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<String> 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<String> getPackages() {
return packages;
}

public void setPackages(List<String> 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();
}
}

运行结果:

springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_spring_20


结果显示如预期。

回顾下:本篇主要介绍了enable前缀注解的使用和原理,主要根据import导入,importSelector或者ImportBeanDefinitionRegistrar实现类为参数,结合我们可以根据enable注解上的参数来控制操作bean,或者在注入bean前加入我们自己的逻辑。

个人微信公号:

搜索: 怒放de每一天

不定时推送相关文章,期待和大家一起成长!!

springboot原理实战(8)--enable前缀注解之开启特性的原理和案例_spring boot_21


举报

相关推荐

0 条评论