自定义注解扫描BEAN

纵然是瞬间 提交于 2020-08-12 19:16:56

自定义注解扫描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

代码

  1. 第一个,我们要一种方式设置扫描哪些包,扫描什么样的类,我这里使用的是一个自定义注解

        @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 {};
        }
    

    这里我只只定义了要扫描的包 ,你还可以定义其他信息;比如类或接口要使用什么样的接口修饰;

  2. 第二个,我们要使用一个注解标记目标的类,比如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
             }
         }
    
    
  3. 第三个,知道要扫描哪里了,知道扫描什么样的类了,下面是怎么扫,什么时候触发扫描的动作呢?

    先解决什么时机扫描的问题,应该会有很多方法,只要你能获得向容器注册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) ,为什么要这样写呢?

    1. 因为只要使用了@RemoteServiceBeanScan 就一定要启动RemoteServiceBeanScanRegistrar ,
    2. 这样可以保证@RemoteServiceBeanScan 注解修饰的类和@Import()修饰的类是同一个因为@Import出现的地方会影响AnnotationMetadata 一些方法的返回值
  4. 结过第三步,我们只是将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件事

    1. 扫描指定的包中的类,不是所以类他都会关注的,注意构造方法中,指明了需要关注哪些类,不关注哪些类 同时 isCandidateComponent这个方法也在扫描过程中对扫描到的类进行了一定的判定
    2. 扫描到你想要的包之后 会将这类的信息包装成GenericBeanDefinition ,可以通过这些GenericBeanDefinition的信息对想要生成的类进行改造等一些特殊处理
    3. processBeanDefinitions方法中对扫描到的BeanDefinition 进行了下面的处理:
      • 将生成bean的类型由扫描到的类改为一个FactoryBean 类型
      • 设置生成Bean时调用构造方法需要的参数
      • 设置一些需要的属性,类似于我们写 @Autoware属性
  5. 第五步,使用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. 第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;
                }
            }
    
        }
    
  7. 如何使用呢?

    
     /**
      * @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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!