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

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

目录


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

主要内容如下:

1dc618a0ed9580ce8bfa6facb208c08f.png


一、前言


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


运行结果:


5d4c6812c8535adbb050f4ddf2e1bce8.png


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


再次运行:


46a9d80a6e05e4e3b19d57a0ee70bcdf.png

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


运行结果:

1dc618a0ed9580ce8bfa6facb208c08f.png

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

1dc618a0ed9580ce8bfa6facb208c08f.png

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


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


我们看下@EnableAsync。

1dc618a0ed9580ce8bfa6facb208c08f.png

发现有个@Import注解。


@EnableConfigurationProperties注解:

5d4c6812c8535adbb050f4ddf2e1bce8.png

也有个注解@Import。

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

46a9d80a6e05e4e3b19d57a0ee70bcdf.png66ba272a0bfc97be54a5fa679e3d5482.png


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


看下执行结果:

1dc618a0ed9580ce8bfa6facb208c08f.png

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


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


刚才我们实践了import导入类了,再进一步考虑下,能不能在导入类前加入自己的逻辑,以前学过直接实现beanDefination等接口,可是这样的话会影响所有的bean。能不能缩小点范围,只影响我们需要导入的类呢?


可以的。思路就是可以定义一个注解,在需要加载的bean上添加注解就可以过滤了。

注解里面用@import导入一些加载bean的处理器,在处理器方法中进行我们自己的逻辑。


这里提到了2点:一个是注解,注解就是我们之前用的enable前缀作为开头的注解。一个处理器。我们看下springboot源码中:

有2类:


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


看下@EnableConfigurationProperties注解

5d4c6812c8535adbb050f4ddf2e1bce8.png46a9d80a6e05e4e3b19d57a0ee70bcdf.png


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


看下:@EnableAsync

1dc618a0ed9580ce8bfa6facb208c08f.png5d4c6812c8535adbb050f4ddf2e1bce8.png


主要是利用importSelector的selectImports方法:

46a9d80a6e05e4e3b19d57a0ee70bcdf.png


③案例:


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


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


1dc618a0ed9580ce8bfa6facb208c08f.png


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:

5d4c6812c8535adbb050f4ddf2e1bce8.png


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


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

准备2个包,和2个bean

1dc618a0ed9580ce8bfa6facb208c08f.png


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


运行结果:


5d4c6812c8535adbb050f4ddf2e1bce8.png

结果显示如预期。


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


相关文章
|
6天前
|
Java 开发者 Spring
【SpringBoot 异步魔法】@Async 注解:揭秘 SpringBoot 中异步方法的终极奥秘!
【8月更文挑战第25天】异步编程对于提升软件应用的性能至关重要,尤其是在高并发环境下。Spring Boot 通过 `@Async` 注解简化了异步方法的实现。本文详细介绍了 `@Async` 的基本用法及配置步骤,并提供了示例代码展示如何在 Spring Boot 项目中创建与管理异步任务,包括自定义线程池、使用 `CompletableFuture` 处理结果及异常情况,帮助开发者更好地理解和运用这一关键特性。
50 1
|
15天前
|
XML Java 测试技术
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
这篇文章介绍了Spring5框架的三个新特性:支持@Nullable注解以明确方法返回、参数和属性值可以为空;引入函数式风格的GenericApplicationContext进行对象注册和管理;以及如何整合JUnit5进行单元测试,同时讨论了JUnit4与JUnit5的整合方法,并提出了关于配置文件加载的疑问。
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
|
2天前
|
缓存 Java 数据库连接
Spring Boot奇迹时刻:@PostConstruct注解如何成为应用初始化的关键先生?
【8月更文挑战第29天】作为一名Java开发工程师,我一直对Spring Boot的便捷性和灵活性着迷。本文将深入探讨@PostConstruct注解在Spring Boot中的应用场景,展示其在资源加载、数据初始化及第三方库初始化等方面的作用。
13 0
|
13天前
|
NoSQL Java Redis
Redis6入门到实战------ 八、Redis与Spring Boot整合
这篇文章详细介绍了如何在Spring Boot项目中整合Redis,包括在`pom.xml`中添加依赖、配置`application.properties`文件、创建配置类以及编写测试类来验证Redis的连接和基本操作。
Redis6入门到实战------ 八、Redis与Spring Boot整合
|
2天前
|
Java API UED
【实战秘籍】Spring Boot开发者的福音:掌握网络防抖动,告别无效请求,提升用户体验!
【8月更文挑战第29天】网络防抖动技术能有效处理频繁触发的事件或请求,避免资源浪费,提升系统响应速度与用户体验。本文介绍如何在Spring Boot中实现防抖动,并提供代码示例。通过使用ScheduledExecutorService,可轻松实现延迟执行功能,确保仅在用户停止输入后才触发操作,大幅减少服务器负载。此外,还可利用`@Async`注解简化异步处理逻辑。防抖动是优化应用性能的关键策略,有助于打造高效稳定的软件系统。
12 2
|
15天前
|
Java 数据安全/隐私保护 Spring
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
|
15天前
|
XML Java 数据库
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
这篇文章是Spring5框架的实战教程,详细介绍了事务的概念、ACID特性、事务操作的场景,并通过实际的银行转账示例,演示了Spring框架中声明式事务管理的实现,包括使用注解和XML配置两种方式,以及如何配置事务参数来控制事务的行为。
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
|
15天前
|
XML 数据库 数据格式
Spring5入门到实战------14、完全注解开发形式 ----JdbcTemplate操作数据库(增删改查、批量增删改)。具体代码+讲解 【终结篇】
这篇文章是Spring5框架的实战教程的终结篇,介绍了如何使用注解而非XML配置文件来实现JdbcTemplate的数据库操作,包括增删改查和批量操作,通过创建配置类来注入数据库连接池和JdbcTemplate对象,并展示了完全注解开发形式的项目结构和代码实现。
Spring5入门到实战------14、完全注解开发形式 ----JdbcTemplate操作数据库(增删改查、批量增删改)。具体代码+讲解 【终结篇】
|
2天前
|
JSON Java API
解码Spring Boot与JSON的完美融合:提升你的Web开发效率,实战技巧大公开!
【8月更文挑战第29天】Spring Boot作为Java开发的轻量级框架,通过`jackson`库提供了强大的JSON处理功能,简化了Web服务和数据交互的实现。本文通过代码示例介绍如何在Spring Boot中进行JSON序列化和反序列化操作,并展示了处理复杂JSON数据及创建RESTful API的方法,帮助开发者提高效率和应用性能。
12 0
|
2天前
|
Java 开发者 Spring
Spring Boot实战宝典:揭秘定时任务的幕后英雄,让业务处理如流水般顺畅,轻松驾驭时间管理艺术!
【8月更文挑战第29天】在现代应用开发中,定时任务如数据备份、报告生成等至关重要。Spring Boot作为流行的Java框架,凭借其强大的集成能力和简洁的配置方式,为开发者提供了高效的定时任务解决方案。本文详细介绍了如何在Spring Boot项目中启用定时任务支持、编写定时任务方法,并通过实战案例展示了其在业务场景中的应用,同时提供了注意事项以确保任务的正确执行。
10 0
下一篇
云函数