使用FactoryBean和JDK代理实现动态注册接口Bean

发布时间 2023-05-17 00:02:17作者: linmt

一、介绍

本文将介绍如何通过FactoryBean和JDK动态代理实现动态注册接口Bean,做到无具体实现的类也能调用方法,类似openFeign中的接口调用和mybatis中的Mapper,下面将使用openFeign的示例讲解实现过程。

二、步骤

  • 创建注解类

    EnableFeignClients.java

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Import(FeignClientsRegistrar.class)
    public @interface EnableFeignClients {
        String[] basePackages() default {};
    }
    

    FeignClient.java

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    public @interface FeignClient {
        String value();
    }
    

    测试接口类RemoteUserService.java

    @FeignClient(value = "user-server")
    public interface RemoteUserService {
        @GetMapping("/getUser")
        Object getUser(@RequestParam String id);
    }
    
  • 创建FeignClientFactoryBean.java,通过jdk动态代理获取代理对象

    public class FeignClientFactoryBean implements FactoryBean<Object> {
        private Class<?> type;
    
        public FeignClientFactoryBean(Class<?> type) {
            this.type = type;
        }
    
        @Override
        public Object getObject() {
            return this.getTarget();
        }
    
        /**
         * 通过动态代理获取代理对象(重要)
         * @param <T>
         * @return
         */
        <T> T getTarget() {
            return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, new FeignProxy());
        }
    
        @Override
        public Class<?> getObjectType() {
            return type;
        }
    }
    
  • 创建代理类FeignProxy.java,具体的方法执行都在这个类

    public class FeignProxy implements InvocationHandler {
        private static final Logger log = LoggerFactory.getLogger(FeignProxy.class);
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            log.info("类{},执行了{}方法,参数={}", method.getDeclaringClass().getName(), method.getName(),args);
            return "张三";
        }
    }
    
  • 创建注册所有的FeignClient的类FeignClientsRegistrar.java

    伪代码:

    • 获取所有的包路径,从EnableFeignClients注解上的属性以及启动类所在的包获得
    • 通过ClassPathScanningCandidateComponentProvider类扫码所有带FeignClient注解的类
    • 遍历所有获取到带FeignClient类并进行通过BeanDefinitionRegistry进行bean注册
    • 注册这类的bean是通过FactoryBean去创建对象
    @Configuration
    public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
        private ResourceLoader resourceLoader;
        private Environment environment;
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet();
            Set<String> basePackages = getBasePackage(metadata);
    
            for (String basePackage : basePackages) {
                ClassPathScanningCandidateComponentProvider scanner = this.createScanner();
                scanner.setResourceLoader(this.resourceLoader);
                scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
    
                // 添加所有扫码到的类
                candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
            }
    
            for (BeanDefinition beanDefinition : candidateComponents) {
                if (beanDefinition instanceof AnnotatedBeanDefinition) {
                    AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition;
                    AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
                    // 注册bean
                    registerFeignClient(registry, annotationMetadata);
                }
            }
        }
    
        /**
         * 注册bean
         *
         * @param registry
         * @param annotationMetadata
         */
        private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata) {
            // 获取class名称
            String className = annotationMetadata.getClassName();
            Class<?> clazz = null;
            try {
                clazz = Class.forName(className);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    
            FeignClientFactoryBean factoryBean = new FeignClientFactoryBean(clazz);
            BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, new Supplier() {
                @Override
                public Object get() {
                    return factoryBean.getObject();
                }
            });
            registry.registerBeanDefinition(clazz.getName(), definition.getBeanDefinition());
        }
    
        /**
         * 创建类路径扫描器
         *
         * @return
         */
        private ClassPathScanningCandidateComponentProvider createScanner() {
            return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
                protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                    boolean isCandidate = false;
                    // isIndependent是顶级接口,isAnnotation是注解类
                    if (beanDefinition.getMetadata().isIndependent() && !beanDefinition.getMetadata().isAnnotation()) {
                        isCandidate = true;
                    }
    
                    return isCandidate;
                }
            };
        }
    
    
        /**
         * 获取
         *
         * @param metadata
         * @return
         */
        private Set<String> getBasePackage(AnnotationMetadata metadata) {
            Set<String> basePackage = new HashSet<>();
    
            // 获取启动类的包路径
            String bootstrapClassName = metadata.getClassName();
            String bootstrapClassPackage = bootstrapClassName.substring(0, bootstrapClassName.lastIndexOf("."));
            basePackage.add(bootstrapClassPackage);
    
            // 获取注解上面的包路径
            Map<String, Object> attributes = metadata.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());
            String[] basePackages = (String[]) attributes.get("basePackages");
            basePackage.addAll(Arrays.asList(basePackages));
    
            return basePackage;
        }
    
        @Override
        public void setEnvironment(Environment environment) {
            this.environment = environment;
        }
    
        @Override
        public void setResourceLoader(ResourceLoader resourceLoader) {
            this.resourceLoader = resourceLoader;
        }
    }
    

三、实现效果

通过下图发现RemoteUserService类是可以正常注入,执行getUser方法真正会执行到FeignProxy.java,同时可以获取到源类、方法、参数。

四、源码

https://github.com/1277463718lmt/interface-bean-registry-demo.git