自定义注解扫描BEAN
为什么要自定义注解扫描生成bean呢? 这个需求类似Mybatis的Mapper扫描,在项目中有一些代码逻辑是固定的,仅仅是些运行时需要的值不一样,这时候如果将这些逻辑通过一点点的代码就生成多好呀
我在项目中的需求:一些接口需要对接其他系统的接口或者直接访问数据 ,但是这些系统的接口可能会随时更改或者同时可用,或者一会用新版本的一会用旧版本的,我们想在尽量不更改我方代码的情况下对接这些系统,并且可以通过更新数据的方式控制调用的接口版本
思路:核心代码中不直接对接第三方系统,对应他们每一个版本的接口增加一个中间层服务,核心代码中调用这些中间层,每个中间层接口有自己的接口ID更新ID对应的使用地址来达到上面的要求
名词
BeanDefinition
BeanDefinition 是对一个类的描述,spring 会根据这些描述来生成一个具体的对象, 他会描述哪些内容呢?类的基本属性,比如Class,spring字义的一些属性,比较scope,lazy,initMethod,destoryMethod等等,这些大都有相对应的定义Bean方法时会用到的注解(@IsLazy,@Scope,@DepenseOn等等)
FactoryBean
FactoryBean是一个比较特殊的Bean,当spring发现你要生成的Bean是一个FactoryBean 时,他不会使用FactoryBean对应的类来生成一个对象,而是会调用这个类的getObject()
方法来得到这个类,当程序运行时的某个bean 需要这个BeanFactory生成的bean时,可以通过 ApplicationContent.getBean(Class)
方法来获得这个类,注意这个方法的参数是 FactoryBean 对象的 getObjectType()
方法返回值一样的Class,也就是说这是另一种生成bean的方式
ClassPathBeanDefinitionScanner
这个类是用于扫描类路径下的类的;(具体他怎么扫的,我也不知道,可难了,涉及到类加载,文件二进制解析等等一系列问题才能理解到能说个大概的水平),我们可以以这个类作为模板定义自己的扫描器
ImportBeanDefinitionRegistrar
当你用@Import注解引入一个类时,如果这个类实现了ImportBeanDefinitionRegistrar ,那么spring 会调用这个类的registerBeanDefinitions(AnnotationMetadata,BeanDefinitionRegistry)方法, 可以通过这个方法向容器添加一些bean
代码
-
第一个,我们要一种方式设置扫描哪些包,扫描什么样的类,我这里使用的是一个自定义注解
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(RemoteServiceBeanScanRegistrar.class) // 请看第三步 public @interface RemoteServiceBeanScan { /** * package name that be supposed to scan * * @return package names */ String[] basePackage() default {}; }
这里我只只定义了要扫描的包 ,你还可以定义其他信息;比如类或接口要使用什么样的接口修饰;
-
第二个,我们要使用一个注解标记目标的类,比如Mybatis 的MapperScan注解里可以设置一个特定的注解来标记你要用作Mapper的接口
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface RemoteServiceBean { String serviceName(); String defaultService(); ServiceType serviceType() default ServiceType.REMOTE; enum ServiceType { BEAN, REMOTE } }
-
第三个,知道要扫描哪里了,知道扫描什么样的类了,下面是怎么扫,什么时候触发扫描的动作呢?
先解决什么时机扫描的问题,应该会有很多方法,只要你能获得向容器注册bean 的对象(
BeanDefinitionRegistry
)就可以,我copy了Mybatis的方式,使用@Import注解和ImportBeanDefinitionRegistrar接口public class RemoteServiceBeanScanRegistrar implements ImportBeanDefinitionRegistrar { private final Logger logger = LoggerFactory.getLogger(getClass()); @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 获取RemoteServiceScan注解上的属性 AnnotationAttributes mapperScanAttrs = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(RemoteServiceBeanScan.class.getName())); String[] packages; if (mapperScanAttrs != null) { packages = getPackageArr(mapperScanAttrs, importingClassMetadata); } else { packages = defaultPackageArr(importingClassMetadata.getClassName()); } logger.debug("scan package : {}", Arrays.toString(packages)); // 向容器 中注册 ClassPathRemoteServiceBeanScanner BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ClassPathRemoteServiceBeanScanner.class); // 设置构造方法的参数 builder.addConstructorArgValue(registry); builder.addConstructorArgValue(packages); // 设置bean的名字 String beanName = importingClassMetadata.getClassName() + "#" + getClass().getSimpleName() + "#" + 0; logger.debug("scanner's bean name is {}",beanName); builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); } private String[] getPackageArr(AnnotationAttributes attributes, AnnotationMetadata metadata) { String[] packages = attributes.getStringArray("basePackage"); if (packages.length == 0) { packages = defaultPackageArr(metadata.getClassName()); } return packages; } private String[] defaultPackageArr(String className) { return new String[]{ClassUtils.getPackageName(className)}; } }
到这先回头缕一下,有个关键的地方,
@RemoteServiceBeanScan
注解中出现了@Import(RemoteServiceBeanScanRegistrar)
,为什么要这样写呢?- 因为只要使用了@RemoteServiceBeanScan 就一定要启动RemoteServiceBeanScanRegistrar ,
- 这样可以保证@RemoteServiceBeanScan 注解修饰的类和@Import()修饰的类是同一个因为@Import出现的地方会影响AnnotationMetadata 一些方法的返回值
-
结过第三步,我们只是将Scanner 注册到了容器中,但是还没有开始扫描,什么时候开始呢? 要说明这个问题,需要先了解一些 bean的后置处理器的相关问题,因为我将这个Scanner 实现了 BeanDefinitionRegistryPostProcessor,这个接口,所以spring 窗口在处理到一定的时机时会调用这个接口的postProcessBeanDefinitionRegistry方法,这个时候我们就可以进行扫描了
public class ClassPathRemoteServiceBeanScanner extends ClassPathBeanDefinitionScanner implements BeanDefinitionRegistryPostProcessor { private final Logger logger = LoggerFactory.getLogger(getClass()); private final BeanDefinitionRegistry beanDefinitionRegistry; private final String basePackage; public ClassPathRemoteServiceBeanScanner(BeanDefinitionRegistry beanDefinitionRegistry,String[] packages) { super(beanDefinitionRegistry); this.beanDefinitionRegistry = beanDefinitionRegistry; this.basePackage = StringUtils.collectionToCommaDelimitedString(Arrays.stream(packages).filter(StringUtils::hasText) .collect(Collectors.toList())); // 设置扫描哪个注解修饰的类 addIncludeFilter(new AnnotationTypeFilter(RemoteServiceBean.class)); // 排除 package-info.java addExcludeFilter((metadataReader, metadataReaderFactory) -> { String className = metadataReader.getClassMetadata().getClassName(); return className.endsWith("package-info"); }); } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { this.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } @Override public Set<BeanDefinitionHolder> doScan(String... basePackages) { // 扫描结果 Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { logger.warn( "No remote bean was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); } else { processBeanDefinitions(beanDefinitions); } return beanDefinitions; } private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions ){ GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); // 扫描到的类的名字 String beanClassName = definition.getBeanClassName(); logger.debug("Creating RemoteServiceFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' Remote service interface"); // 设置生成bean 的具体的类 definition.setBeanClass(RemoteServiceFactoryBean.class); definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // 这个地方没有明具体原理,他是在设置调用构造方法时的参数,但构造方法需要一个Class类型的参数,这里确传入了这个类的全名的字符串 // 将环境中的 remoteServiceBeanOperator bean 注入 definition.getPropertyValues().add("remoteServiceBeanOperator", new RuntimeBeanReference("remoteServiceBeanOperator")); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); definition.setLazyInit(true); } } /** * {@inheritDoc} */ @Override protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent(); }
这个Scanner 主要作了3件事
- 扫描指定的包中的类,不是所以类他都会关注的,注意构造方法中,指明了需要关注哪些类,不关注哪些类 同时 isCandidateComponent这个方法也在扫描过程中对扫描到的类进行了一定的判定
- 扫描到你想要的包之后 会将这类的信息包装成GenericBeanDefinition ,可以通过这些GenericBeanDefinition的信息对想要生成的类进行改造等一些特殊处理
- processBeanDefinitions方法中对扫描到的BeanDefinition 进行了下面的处理:
- 将生成bean的类型由扫描到的类改为一个FactoryBean 类型
- 设置生成Bean时调用构造方法需要的参数
- 设置一些需要的属性,类似于我们写 @Autoware属性
-
第五步,使用FactoryBean 生成 代理对象 在上一步里,我们将BeanDefinition的Class对象设置成了FactoryBean.Class,他的代码长成下面这个样子
public class RemoteServiceFactoryBean<T> implements FactoryBean<T> { private RedisTemplate<String, String> remoteServiceBeanOperator; private final Class<T> tClass; public RemoteServiceFactoryBean(Class<T> tClass) { this.tClass = tClass; } /** * 生成代理对象,完成大部分相同的业务代码 */ @SuppressWarnings("unchecked") @Override public T getObject() throws Exception { return (T) Proxy.newProxyInstance(tClass.getClassLoader(), new Class[] { tClass } , new RemoteServiceProxy(remoteServiceBeanOperator)); } /** * 因为生成的是一个代理对象,他即是InvocationHandler,也是你扫描到的接口类的子类,那么容器中认为他到底是哪个类呢,由这个方法的返回值确定 */ @Override public Class<T> getObjectType() { return tClass; } public RedisTemplate<String, String> getRemoteServiceBeanOperator() { return remoteServiceBeanOperator; } public void setRemoteServiceBeanOperator(RedisTemplate<String, String> remoteServiceBeanOperator) { this.remoteServiceBeanOperator = remoteServiceBeanOperator; } }
-
第6步 ,也就是最终处理业务逻辑的部分
public class RemoteServiceProxy implements InvocationHandler, Serializable { private final Logger logger = LoggerFactory.getLogger(getClass()); private final RedisTemplate<String, String> remoteServiceBeanOperator; public RemoteServiceProxy(RedisTemplate<String, String> remoteServiceBeanOperator) { if (remoteServiceBeanOperator == null) { logger.error("remoteServiceBeanOperator should not be null"); throw new RuntimeException("remoteServiceBeanOperator should not be null"); } this.remoteServiceBeanOperator = remoteServiceBeanOperator; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { // 调用远程方法 System.out.println("进入代理方法 !!!!"); System.out.println(method.getName()); System.out.println(Arrays.toString(args)); System.out.println("remoteServiceBeanOperator:" + this.remoteServiceBeanOperator); return null; } } }
-
如何使用呢?
/** * @author */ @RestController @RequestMapping("file") public class FileController { private final Logger logger = LoggerFactory.getLogger(getClass()); private InterfaceOne interfaceOne; private InterfaceTwo interfaceTwo; private InterfaceThree interfaceThree; public FileController(InterfaceOne interfaceOne, InterfaceTwo interfaceTwo, InterfaceThree interfaceThree) { this.interfaceOne = interfaceOne; this.interfaceTwo = interfaceTwo; this.interfaceThree = interfaceThree; } @RequestMapping("/testBean") public Map<String, String> testBean() { logger.debug(this.interfaceOne.getClass().getName()); logger.debug(this.interfaceTwo.getClass().getName()); logger.debug(this.interfaceThree.getClass().getName()); Map<String, String> result = new HashMap<>(); result.put("interfaceOne", interfaceOne.getClass().getName()); result.put("interfaceTwo", interfaceTwo.getClass().getName()); result.put("interfaceThree", interfaceThree.getClass().getName()); logger.debug(interfaceOne.toString()); logger.debug(interfaceOne.getName()); logger.debug(interfaceTwo.twoMethod("p1", 3)); logger.debug(interfaceThree.threeMethod()); return result; } }
和使用@Component,@Service等注解修饰的类一样使用(idea) 会标红,但是运行不会报错
代码地址 https://gitee.com/Mzoro/demos/tree/master/spring-boot-feature
来源:oschina
链接:https://my.oschina.net/Mzoro/blog/4334037