Spring Boot 自定义@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