Spring 扫描自定义注解的接口并且为其创建并注册代理 bean
Spring 扫描自定义注解的接口并且为其创建并注册代理 bean
总览
2024/02/05 - 建议使用这种方法来实现同样的效果。
准备
- 创建自定义的注解
- 创建自定义注解的 ImportDefinitionRegistrar 实现类用于扫描并注册含有注解的接口
- 使用 ClassPathBeanDefinitionScanner 扫描符合条件的接口
- 创建接口的代理对象
- 注册代理对象到 Spring 容器
使用
- 使用 @Import 引入自定义的注册类
- 在需要被扫描并注册的接口上使用自定义注解
创建目标注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Meow {
}
实现 ImportDefinitionRegistrar
public class ImportMeowRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
private DefaultListableBeanFactory beanFactory;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//在这里开始处理
}
}
扫描所有含有 @Meow 注解的接口
public class ImportMeowRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
Set<String> set = new LinkedHashSet<>();
scanner.addIncludeFilter((r, f) -> {
ClassMetadata classMetadata = r.getClassMetadata();
AnnotationMetadata annotationMetadata = r.getAnnotationMetadata();
if (classMetadata.isInterface()){
if (annotationMetadata.hasAnnotation(Meow.class.getName())) {
set.add(annotationMetadata.getClassName());
return true;
}
}
return false;
});
scanner.findCandidateComponents("cn.linkot");
}
//... 省略 setBeanFactory
}
ClassPathBeanDefinitionScanner
源码 JavaDoc A bean definition scanner that detects bean candidates on the classpath, registering corresponding bean definitions with a given registry (BeanFactory or ApplicationContext). Candidate classes are detected through configurable type filters. The default filters include classes that are annotated with Spring's @Component, @Repository, @Service, or @Controller stereotype.
ClassPathBeanDefinitionScanner 为 ClassPathScanningCandidateComponentProvider 的派生类,会通过定义的类型过滤器(type filters)扫描并注册 bean definition。 默认也会扫描 @Component 这些 bean,不过我这里只需要自定义注解的 bean,所以构造器 ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters)
的 useDefaultFilters 为 false。
为什么不用findCandidateComponents(String basePackage)
的返回值?
findCandidateComponents 中会调用以下方法
/**
* Determine whether the given bean definition qualifies as candidate.
* <p>The default implementation checks whether the class is not an interface
* and not dependent on an enclosing class.
* <p>Can be overridden in subclasses.
* @param beanDefinition the bean definition to check
* @return whether the bean definition qualifies as a candidate component
*/
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}
需要为 true 才会加入 set 中
也就是说,无论上面定义的 includeFilter 返回值是否为 true,只要我需要扫描的类为接口且没有 Lookup 的注解在方法上则不会被 include。 所以这里声明了一个 set,通过 type filter 找到符合条件的类并手动添加。(并没有用上 beanDefinition)
为扫描的类创建代理
private Object createProxy(String c) throws ClassNotFoundException {
Class<?> clz = Class.forName(c);
return Proxy.newProxyInstance(clz.getClassLoader(), new Class[]{clz}, (proxy, method, args) -> {
String name = method.getName();
System.out.printf("%s: meow.", name);
return null;
});
}
代理这块以后有机会再写一篇
注册代理对象到容器
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
Set<String> set = new LinkedHashSet<>();
scanner.addIncludeFilter((r, f) -> {
ClassMetadata classMetadata = r.getClassMetadata();
AnnotationMetadata annotationMetadata = r.getAnnotationMetadata();
if (classMetadata.isInterface()){
if (annotationMetadata.hasAnnotation(Meow.class.getName())) {
set.add(annotationMetadata.getClassName());
return true;
}
}
return false;
});
scanner.findCandidateComponents("cn.linkot");
//注册到容器
for (String c : set) {
try {
beanFactory.registerSingleton(c, createProxy(c));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
全部代码
ImportMeowRegistrar
package cn.linkot.meow;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import java.lang.reflect.Proxy;
import java.util.LinkedHashSet;
import java.util.Set;
public class ImportMeowRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
private DefaultListableBeanFactory beanFactory;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
Set<String> set = new LinkedHashSet<>();
scanner.addIncludeFilter((r, f) -> {
ClassMetadata classMetadata = r.getClassMetadata();
AnnotationMetadata annotationMetadata = r.getAnnotationMetadata();
if (classMetadata.isInterface()){
if (annotationMetadata.hasAnnotation(Meow.class.getName())) {
set.add(annotationMetadata.getClassName());
return true;
}
}
return false;
});
scanner.findCandidateComponents("cn.linkot");
for (String c : set) {
try {
beanFactory.registerSingleton(c, createProxy(c));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
private Object createProxy(String c) throws ClassNotFoundException {
Class<?> clz = Class.forName(c);
return Proxy.newProxyInstance(clz.getClassLoader(), new Class[]{clz}, (proxy, method, args) -> {
String name = method.getName();
System.out.printf("%s: meow.", name);
return null;
});
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (DefaultListableBeanFactory) beanFactory;
}
}
使用
使用
需要代理的接口
package cn.linkot.test;
import cn.linkot.meow.Meow;
@Meow
public interface TestItf {
void say();
}
测试 controller
@RestController
@AllArgsConstructor
public class TestController {
private TestItf testItf;
@GetMapping("meow")
public Object meow() {
testItf.say();
return "OK";
}
}
访问
say: meow.