例子讲解Springboot注解@EnableXXX如何工作

本文通过实例展示了如何自定义@Enable注解,并将其应用于Spring Boot项目中,注册自定义组件为Spring容器bean,同时介绍了注解属性如何影响组件注册。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

Springboot 提供了很多@EnableXXX这样的注解,通过使用这些注解,我们能够很方便地启用某些功能。那么这些@EnableXXX注解又是怎么工作的呢?简单来讲,就是它主要利用了另外一个注解@Import,而关于@Import如何使用以及背后的工作原理,可以参考我的另外一篇文章 :Spring @Import 的使用及其工作原理分析。本文不再重点讲解@EnableXXX,@Import的工作原理,而是通过一个例子项目讲解注解@EnableXXX如何定义及其工作效果。

自定义@EnableXXX注解的例子项目

演示目的

这个例子项目中,我们通过普通Java类方式定义了一组服务组件,也就是说没有使用Spring标准的bean定义方式将它们定义为可被Spring框架直接识别的bean;然后我们自定义了一个@EnableXXX注解,通过使用这个自定义的注解,我们能够把这些通过普通Java类方式定义的组件注册为Spring容器bean,从而使他们变得跟其他Spring标准方式定义的bean一样被使用 。

例子项目的源代码

下面,我们来看一下这个项目的源代码。这是一个基于maven的项目。

项目文件结构

/
│    
│  pom.xml
│
└─src
    └─main
        └─java
            ├─app
            │      Application.java
            │
            ├─config
            │      EnableMyOwnBeanDefinitions.java
            │      LogServiceImportSelector.java
            │      OrderServiceBeanDefinitionRegistrar.java
            │      UserBeansConfig.java
            │
            └─services
                    AdminService.java
                    CustomerService.java
                    LogService.java
                    OrderChangeRecordService.java
                    OrderService.java
                    SettingService.java

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>app</groupId>
    <artifactId>zero</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.1.0.RELEASE</version>
        </dependency>
    </dependencies>
</project>

模拟的服务组件类

在这个例子项目中,我们模拟了以下服务组件类,因为只是出于演示的目的,所以每个服务类组件并没有提供任何具体的实现。通过类名称和类上的注解,你应该能理解每个服务组件类想表达的意义。这里需要指出的是,这些服务组件类都是普通Java类,没有使用任何Spring标准bean定义方式将它们定义为bean,比如使用XML bean定义方式,或者使用类似@Service,@Component,Bean这样注解定义的方式。

你可能会问,既然我们能通过@Service,@Component,Bean这样标准的Spring bean定义方式定义bean,那么这里又何苦使用普通Java类方式定义服务组件类呢 ?
这里有两个目的。
一,本例子中这样定义的目的是为了演示一个@Enable注解如何工作;
二,想象一下一个通过其他渠道获取的java服务包,你无法在源代码层面对其服务组件类进行修改,但你又想把它们引入Springboot应用体系中,那该怎么办呢?等你读完了本文,再想一下,这里是不是就提供了一种不错的解决办法?

package services;

/**
 * 管理员管理服务:管理员账号增删改查等
 */
public class AdminService {
}

package services;

/**
 * 客户管理服务:客户账号,基础信息增删改查等
 */
public class CustomerService {
}
package services;

/**
 * 日志服务
 */
public class LogService {
}

package services;

/**
 * 订单变更跟踪记录,仅用在 开发模式下
 */
public class OrderChangeRecordService {
}

package services;

/**
 * 订单服务:订单的增删改查等
 */
public class OrderService {
}

package services;

/**
 * 配置服务:系统配置参数的增删改查等
 */
public class SettingService {
}

自定义@EnableMyOwnBeanDefinitions

准备一个@Configuration配置类
package config;

import services.AdminService;
import services.CustomerService;
import org.springframework.context.annotation.Bean;

/**
 * 一个 Spring 配置类, 注解方式注册bean的典型用法 :
 *
 * @Configuration 定义配置类 + @Bean 注解配置类方法注册bean
 */
//@Configuration
public class UserBeansConfig {
    /**
     * 注册管理员管理服务组件bean
     * AdminService 是一个普通Java类
     *
     * @return
     */
    @Bean
    public AdminService adminService() {
        return new AdminService();
    }

    /**
     * 注册客户管理服务组件bean
     * CustomerService 是一个普通Java类
     *
     * @return
     */
    @Bean
    public CustomerService customerService() {
        return new CustomerService();
    }
}

准备一个ImportSelector
package config;

import services.LogService;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

/**
 * 一个 ImportSelector 实现类
 * 用于演示通过 ImportSelector ,给定一组要注册为bean组件的普通Java类的名称,
 * 然后通过利用 @Import(LogServiceImportSelector.class)即可注册相应的bean
 */
public class LogServiceImportSelector implements ImportSelector {

    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 返回要注册成为 bean 的类的全名称的数组
        return new String[]{LogService.class.getName()};
    }
}

准备一个ImportBeanDefinitionRegistrar
package config;

import services.OrderChangeRecordService;
import services.OrderService;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.StringUtils;

/**
 * 一个接口ImportBeanDefinitionRegistrar的实现类,
 * 用于演示 , 演示点:
 * 1. 程序化将普通Java类作为bean注册到Spring IoC容器;
 * 2. 使用注解元数据属性动态决定bean的注册;
 */
public class OrderServiceBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     * @param importingClassMetadata 注解元数据
     * @param registry               Spring IoC 容器
     */
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, 
    BeanDefinitionRegistry registry) {
        AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(
        	importingClassMetadata.getAnnotationAttributes(
        							EnableMyOwnBeanDefinitions.class.getName()));

        {// 注册类型为 OrderService 的订单服务组件 bean
            Class beanClass = OrderService.class;
            RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
            String beanName = StringUtils.uncapitalize(beanClass.getSimpleName());
            registry.registerBeanDefinition(beanName, beanDefinition);
        }

        {// 看情况决定是否注册类型为 OrderChangeRecordService 的订单变更记录服务组件 bean
            boolean trackOrderChange = annotationAttributes.getBoolean("trackOrderChange");
            if (trackOrderChange) {
                Class beanClass = OrderChangeRecordService.class;
                RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
                String beanName = StringUtils.uncapitalize(beanClass.getSimpleName());
                registry.registerBeanDefinition(beanName, beanDefinition);
            }
        }
    }
}

定义注解 @EnableMyOwnBeanDefinitions

现在基于上面定义的配置类UserBeansConfig(@Configuration配置类),LogServiceImportSelector(ImportSelector),OrderServiceBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar),我们自定义一个注解@EnableMyOwnBeanDefinitions

这个注解同时定义了一个属性trackOrderChange,用来表明是否要开启订单变动跟踪,这个语义在上面的OrderServiceBeanDefinitionRegistrar中被具体实现:如果trackOrderChangefalse,服务组件OrderChangeRecordService并不会被注册到Spring容器。

package config;

import services.SettingService;
import org.springframework.context.annotation.Import;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 模仿 Spring 框架的 @EnableXXX 注解自定义的一个 @Enable 注解,
 * 此类注解一般使用 @Import 通过以下四种方式进行 bean 定义:
 * 1. @Configuration 注解的专门用于bean定义的类,一般通过@Bean注解的方法注册bean ;
 * 2. ImportSelector 给出某些要注册为bean的普通类的类名,将它们注册为 bean ;
 * 3. ImportBeanDefinitionRegistrar 直接基于某些普通类创建 BeanDefinition 并注册相应的 bean ,可以有较复杂的逻辑;
 * 4. 直接将某个普通类作为一个bean注册到容器。
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({
		UserBeansConfig.class, 
		LogServiceImportSelector.class, 
		OrderServiceBeanDefinitionRegistrar.class, 
		SettingService.class})
public @interface EnableMyOwnBeanDefinitions {
    /**
     * 注解属性:是否记录订单变更,语义 :
     * false -- 不注册订单变更记录服务组件bean
     * true -- 注册订单变更记录服务组件bean
     *
     * @return
     */
    boolean trackOrderChange() default false;
}

使用自定义注解@EnableMyOwnBeanDefinitions

package app;

import config.EnableMyOwnBeanDefinitions;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * 演示自定义注解@EnableMyOwnBeanDefinitions的使用和效果
 */

@EnableMyOwnBeanDefinitions(trackOrderChange = true)
//@EnableMyOwnBeanDefinitions(trackOrderChange = false)
@SpringBootApplication
public class Application {


    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args);
        dumpBeansToConsole(applicationContext);
    }


    /**
     * 往控制台上输出容器中注册的各个bean的名称
     *
     * @param applicationContext
     */
    private static void dumpBeansToConsole(ConfigurableApplicationContext applicationContext) {
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            Object bean = applicationContext.getBean(name);
            System.out.printf("%s[%s]\n", name, bean.getClass().getName());
        }
    }
}

现在,当我们运行上面的应用时,控制台上会输出我们通过Java普通类所定义的服务组件bean,如下所示:

config.UserBeansConfig[config.UserBeansConfig]
adminService[services.AdminService]
customerService[services.CustomerService]
services.LogService[services.LogService]
services.SettingService[services.SettingService]
orderService[services.OrderService]
orderChangeRecordService[services.OrderChangeRecordService]

把注解@EnableMyOwnBeanDefinitions的属性trackOrderChange设置为false,再运行上面的应用,控制台上的输出会变成这个样子:

config.UserBeansConfig[config.UserBeansConfig]
adminService[services.AdminService]
customerService[services.CustomerService]
services.LogService[services.LogService]
services.SettingService[services.SettingService]
orderService[services.OrderService]

跟上面属性trackOrderChange设置为true时的输出相比,可以看到orderChangeRecordService[services.OrderChangeRecordService]不见了,这说明开关属性trackOrderChange 在起到相应的作用。

总结

本文通过一个实际的例子项目演示了如何定义一个@EnableXXX注解并应用在Springboot项目中。通过这个例子,你可以看到@EnableXXX是怎样被定义的以及起到了什么样的作用。关于@EnableXXX背后的工作原理,基本上也就是注解@Import的工作原理,你可以参考我的另外一篇文章 :Spring @Import 的使用及其工作原理分析。另外,通过这个例子,你也可以针对某些第三方提供的java服务包提供一个自定义的@EnableXXX注解将这些服务注册到Spring的应用环境中来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值