Spring Boot 自定义@Enable* 注解

  • @Enable*的实例@EnableAsync
  • 通过实现ImportSelector接口来实现bean的加载
  • 通过实现ImportBeanDefinitionRegistrar接口来实现bean的加载
  • 自定义Enable注解来实现对类加载的监听


@Enable*的实例@EnableAsync

在Spring Boot中,当我们想要以多线程的方式来运行某一段代码时,我们可以在方法上面使用注解@Async,但同时,我们有必须在Spring Boot的启动类中配置@EnableAsync来让注解@Async生效,究竟@Enable*都做了什么样的工作,来实现了我们所配置的注解生效呢?
我们来看一下@EnableAsync的源码

@Target({ElementType.TYPE})
	@Retention(RetentionPolicy.RUNTIME)
	@Documented
	@Import({AsyncConfigurationSelector.class})
	public @interface EnableAsync {

在注解定义中,除了正常使用的@Target@Retention@Documented之外,还有一个@Import的注解,在@Import中有一个AsyncConfigurationSelector的选择器我们再来看一下AsyncConfigurationSelector的源码

public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {

	private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME =
			"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";


	/**
	 * Returns {@link ProxyAsyncConfiguration} or {@code AspectJAsyncConfiguration}
	 * for {@code PROXY} and {@code ASPECTJ} values of {@link EnableAsync#mode()},
	 * respectively.
	 */
	@Override
	@Nullable
	public String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				return new String[] {ProxyAsyncConfiguration.class.getName()};
			case ASPECTJ:
				return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};
			default:
				return null;
		}
	}

}

可以看到AsyncConfigurationSelector继承了AdviceModeImportSelector,当我们再点进这个父类中看源码的时候可以发现AdviceModeImportSelector实现了ImportSelector这个接口,而ImportSelector这个接口中的方法(String[] selectImports(AnnotationMetadata importingClassMetadata);),通常会返回类实例名称。也就是说只要实现了这个方法,就可以根据不同的场景,返回不同的类实例。

通过实现ImportSelector接口来实现bean的加载

通常,我们加载一个类可以通过简单的@Component@Service@Controller等方式让Spring对类进行实例化托管,但我们也可以通过@Import的方式,来实现类的实例化。
首先我们定义一个等待被实例化的类Book

package com.boot.enable.bootenable;

public class Book {
}

实现ImportSelector接口的实现类BeanImportSelector

package com.boot.enable.bootenable;

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

public class BeanImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[] {"com.boot.enable.bootenable.Book"};
    }
}
启动类
@SpringBootApplication
// 使用Import方式装配
@Import(BeanImportSelector.class)
public class BootEnableApplication {

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

这个时候我们就能正常看到Book这个类已经被实例化了

通过实现ImportBeanDefinitionRegistrar接口来实现bean的加载

同样的,我们依旧是使用Book类来作为等待被加载的类
我们新建一个MyBeanDefinitionRegistrar类来实现ImportBeanDefinitionRegistrar接口

package com.boot.enable.bootenable;

import org.springframework.beans.factory.support.AbstractBeanDefinition;
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;

public class MyBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 创建构建器对象
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Book.class);
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        registry.registerBeanDefinition("book", beanDefinition);
    }
}
启动类
@SpringBootApplication
// 使用Import方式装配
@Import(MyBeanDefinitionRegistrar.class)
public class BootEnableApplication {

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

同样的,我们也能正常看到Book这个类已经被实例化了。

自定义Enable注解来实现对类加载的监听

我们已经知道了@Import可以帮我们对类进行加载,接下来就到本篇文章的重点了,我们该如何实现类似@Enable*的注解来实现对某些类加载的监听呢?
首先,我们需要一个@Enable的类,我们先创建一个注解@EnableScanner

package com.boot.enable.bootenable.sample;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(ScannerPackageRegistrar.class)
public @interface EnableScanner {
    String[] packages();
}

我们可以看到在@EnableScanner中,我们使用了@Import的注解导入了一个注册类ScannerPackageRegistrar 我们来看一下ScannerPackageRegistrar这个类

package com.boot.enable.bootenable.sample;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

import java.util.List;

public class MyBeanDefinitionProcessor implements BeanPostProcessor {

    private List<String> packages;

    public void setPackages(List<String> packages) {
        this.packages = packages;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        for (String pkg : packages) {
            if (bean.getClass().getName().contains(pkg)) {
                System.out.println("instance bean" + bean.getClass().getName());
            }
        }
        return bean;
    }
}

在这个类中,我们实现了BeanPostProcessor接口下的postProcessBeforeInitialization方法,这个方法可以帮我们在实例被加载之前,拿到相关bean的一些信息,同时packages中是我们保存需要被监听的文件的包信息。
但是,我们只有这些还不够,我们还需要将MyBeanDefinitionProcessor注册进注册器中,我们可以通过实现ImportBeanDefinitionRegistrar的方式来对我们自定义的监听器进行注册

package com.boot.enable.bootenable.sample;

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;

public class ScannerPackageRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        String[] attrs = (String[]) importingClassMetadata.getAnnotationAttributes(EnableScanner.class.getName()).get("packages");
        List<String> packages = Arrays.asList(attrs);
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(MyBeanDefinitionProcessor.class);
        builder.addPropertyValue("packages", packages);
        registry.registerBeanDefinition(MyBeanDefinitionProcessor.class.getName(), builder.getBeanDefinition());
    }
}

我们通过参数importingClassMetadata可以拿到注解的相关属性信息,同时将拿到的packages存放进我们的MyBeanDefinitionProcessor中,这样,我们就等于实现了对类加载的监听。
接下来,我们进行测试,测试中新写了一个Person类

package com.boot.enable.bootenable.sample.bean;

import org.springframework.stereotype.Component;

@Component
public class Person {
}

可以发现,现在的Person类通过@Component注解已经交予了Spring容器进行管理
启动类

package com.boot.enable.bootenable.sample;

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

@SpringBootApplication
@EnableScanner(packages = {"com.boot.enable.bootenable.sample.bean"})
public class ScannerPackageApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(ScannerPackageApplication.class, args);
        context.close();
    }

}

我们运行后就可以得到对类监听的结果了

com.boot.enable.bootenable.Book@11a82d0f