SpringBoot动态代理接口

需求:在类似数据库等一些场景下,我们根据一定规则去操作数据,但是操作内容大部分内容是相同的,如果为每一个操作都写代码实现,会造成大量冗余。此时就可以对共性抽取,并通过动态代理的方式进行代码简化,比如Mybatis。

需要将接口动态代理,同时注入到IOC容器。

准备工作

  1. 编写注解,用来识别哪些接口需要被动态代理(类似于@Repository

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @ClassName NeedProxy
     * @Description TODO
     * @Author Silwings
     * @Date 2021/3/7 15:57
     * @Version V1.0
     **/
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface NeedProxy {
        String value() default "";
    }
    
  2. 编写公共接口(类似于@Mapper)

    /**
     * @ClassName Repository
     * @Description 泛型用来声明实体类类型(参考MyBatis)
     * @Author Silwings
     * @Date 2021/3/7 15:58
     * @Version V1.0
     **/
    public interface Repository<T> {
    	// 示例方法.
       String  print();
    }
    
  3. 编写公共接口的默认实现

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;
    
    /**
     * @ClassName DefaultRepository
     * @Description TODO
     * @Author Silwings
     * @Date 2021/3/7 16:00
     * @Version V1.0
     **/
    public class DefaultRepository<T> implements Repository<T> , InvocationHandler {
    	// 这里声明一个Class,用来接收接口声明的泛型实际类型的class,T是声明的实体类类型
        private Class<T> clazz;
    
        public DefaultRepository(Class<T> interfaceType) {
            // 获取当前类上的泛型类型
            ParameterizedType parameterizedType = (ParameterizedType) interfaceType.getGenericInterfaces()[0];
            // 获取泛型对应的真实类型(泛型真实类型在很多场合需要使用)
            Type[] actualType = parameterizedType.getActualTypeArguments();
            // 取数组的第一个,肯定是T的类型,即实体类类型(如果有多个,递增角标即可)
            this.clazz = (Class<T>) actualType[0];
        }
    
        @Override
        public String print() {
            // 示例方法的默认实现
            System.out.println(clazz);
            return clazz.getName();
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // Object 方法,走原生方法,比如hashCode()
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this,args);
            }
            // 其它走本地代理
            return method.invoke(this, args);
        }
    }
    
  4. 默认实现完成后,需要使用FactoryBean来构建它.

    import org.springframework.beans.factory.FactoryBean;
    import java.lang.reflect.Proxy;
    
    /**
     * @ClassName RepositoryFactory
     * @Description FactoryBean是一种特殊的Bean,其返回的对象不是指定类的一个实例,其返回的是该工厂Bean的getObject方法所返回的对象
     * @Author Silwings
     * @Date 2021/3/7 16:01
     * @Version V1.0
     **/
    public class RepositoryFactory<T> implements FactoryBean<T> {
    
        /**
         * 构建DefaultRepository需要使用的参数
         */
        private Class<T> interfaceType;
    
        public RepositoryFactory(Class<T> interfaceType) {
            this.interfaceType = interfaceType;
        }
    
        @Override
        public T getObject() throws Exception {
            // 因为DefaultRepository需要Class<T>作为参数,所以该类包含一个Claa<T>的成员,通过构造函数初始化
            return (T) Proxy.newProxyInstance(interfaceType.getClassLoader(), new Class[]{interfaceType},
                    new DefaultRepository<>(interfaceType));
        }
    
        @Override
        public Class<?> getObjectType() {
            // 该方法返回的getObject()方法返回对象的类型,这里是基于interfaceType生成的代理对象,所以类型就是interfaceType
            return interfaceType;
        }
    }
    
  5. 至此准备工作完成.我们完成了标识需要代理接口的注解和动态代理的代码.接下来的问题是,如何获取到添加了@NeedProxy注解的接口,以及如何将其实例化并注入到IOC容器

  6. 注意事项: @NeedProxy注解并不是必须的,可以直接通过是否继承了Repository接口来判断.这里为了演示的更全面,使用了标识注解,在 一些业务情况下,也是需要添加一个注解来传递一些信息的.

核心工作

  1. 要完成接下来的工作需要实现三个接口BeanDefinitionRegistryPostProcessor,ResourceLoaderAware,ApplicationContextAware

    1. BeanDefinitionRegistryPostProcessor
      1. 该接口是实现将代理类注入到容器最关键的部分.
    2. ResourceLoaderAware和ApplicationContextAware
      1. Spring中,以Aware结尾的接口都是感知接口,实现了这些接口的bean在满足条件时可以感知到自身的一些属性,比如ApplicationContextAware,体现在代码上就是在合适的时候Spring会调用所有实现了ApplicationContextAware接口的bean的setApplicationContext(ApplicationContext applicationContext)方法,并将ApplicationContext 作为参数传递过来,那么使用者就可以在这个地方将ApplicationContext 进行存储.
      2. 在我们的实现中,需要借助ResourceLoader和ApplicationContext,所以这里需要实现这两个接口,让我们方便的获取到这两个类的实例
  2. 编写实现类RepositoryScanner

    import com.silwings.demo.anno.NeedProxy;
    import com.silwings.demo.repository.RepositoryFactory;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.beans.factory.support.BeanDefinitionBuilder;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
    import org.springframework.beans.factory.support.GenericBeanDefinition;
    import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.ResourceLoaderAware;
    import org.springframework.core.annotation.AnnotatedElementUtils;
    import org.springframework.core.io.Resource;
    import org.springframework.core.io.ResourceLoader;
    import org.springframework.core.io.support.ResourcePatternResolver;
    import org.springframework.core.io.support.ResourcePatternUtils;
    import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
    import org.springframework.core.type.classreading.MetadataReader;
    import org.springframework.core.type.classreading.MetadataReaderFactory;
    import org.springframework.util.ClassUtils;
    
    import java.io.IOException;
    import java.util.LinkedHashSet;
    import java.util.List;
    import java.util.Set;
    
    /**
     * @ClassName RepositoryScanner
     * @Description 扫描类并将其实例化注入IOC容器
     * @Author Silwings
     * @Date 2021/3/7 16:09
     * @Version V1.0
     **/
    public class RepositoryScanner implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware, ApplicationContextAware {
        private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
    
        private MetadataReaderFactory metadataReaderFactory;
        private ResourcePatternResolver resourcePatternResolver;
        private ApplicationContext applicationContext;
    
        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
            // 获取启动类所在包
            List<String> packages = AutoConfigurationPackages.get(applicationContext);
            // 开始扫描包,获取字节码
            Set<Class<?>> beanClazzSet = scannerPackages(packages.get(0));
            for (Class beanClazz : beanClazzSet) {
                // 判断是否是需要被代理的接口
                if (isNotNeedProxy(beanClazz)) {
                    continue;
                }
                // BeanDefinition构建器
                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz);
                GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();
    
                //在这里,我们可以给该对象的属性注入对应的实例。
                definition.getConstructorArgumentValues()
                                    .addGenericArgumentValue(beanClazz);
                // 如果构造函数中不止一个值,可以使用这个api,指定是第几个参数
                //   definition.getConstructorArgumentValues()
                //					.addIndexedArgumentValue(1,null);
                
                // 定义Bean工程(最终会用上面add的构造函数参数值作为参数调用RepositoryFactory的构造方法)
                definition.setBeanClass(RepositoryFactory.class);
                //这里采用的是byType方式注入,类似的还有byName等
                definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
                String simpleName = beanClazz.getSimpleName();
                // 首字母小写注入容器
                simpleName = simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1);
                beanDefinitionRegistry.registerBeanDefinition(simpleName, definition);
            }
        }
    
        /**
         * description: 是否是需要被代理的接口
         * version: 1.0
         * date: 2021/3/7 17:35
         * author: Silwings
         *
         * @param beanClazz 类对象
         * @return boolean 如果不是需要被代理的接口返回true
         */
        private boolean isNotNeedProxy(Class beanClazz) {
            // 如果不是接口,或者其实现的接口小于等于0,或者其实现的第一个接口不是Repository,或者没有添加@NeedProxy注解,则说明不是需要被代理的接口
            return !beanClazz.isInterface() || beanClazz.getInterfaces().length <= 0 || beanClazz.getInterfaces()[0] != Repository.class || null == AnnotatedElementUtils.findMergedAnnotation(beanClazz, NeedProxy.class);
        }
    
        /**
         * description: 根据包路径获取包及子包下的所有类
         * version: 1.0
         * date: 2021/3/7 17:34
         * author: Silwings
         *
         * @param basePackage 需要扫描的包
         * @return java.util.Set<java.lang.Class < ?>>
         */
        private Set<Class<?>> scannerPackages(String basePackage) {
            Set<Class<?>> set = new LinkedHashSet<>();
            // 此处固定写法即可,含义就是包及子包下的所有类
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    resolveBasePackage(basePackage) + '/' + DEFAULT_RESOURCE_PATTERN;
            try {
                Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
                for (Resource resource : resources) {
                    if (resource.isReadable()) {
                        MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                        String className = metadataReader.getClassMetadata().getClassName();
                        Class<?> clazz;
                        try {
                            clazz = Class.forName(className);
                            set.add(clazz);
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return set;
        }
    
        /**
         * description: 解析包名
         * version: 1.0
         * date: 2021/3/7 17:31
         * author: Silwings
         *
         * @param basePackage 需要解析的路径
         * @return java.lang.String 解析后的路径
         */
        private String resolveBasePackage(String basePackage) {
            // 将类名转换为资源路径
            return ClassUtils.convertClassNameToResourcePath(
                    // 解析占位符
                    this.applicationContext.getEnvironment().resolveRequiredPlaceholders(basePackage));
        }
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
            // 该方法空实现即可,用不到
        }
    
        @Override
        public void setResourceLoader(ResourceLoader resourceLoader) {
            this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
            this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    
    }
    
  3. 如此,工作就全部完成.

测试

  1. 此时,如果我们编写一个接口,继承Repository接口,并添加@NeedProxy注解,不用编写实现类,按理说就可以直接在Controller中注入并使用了.

  2. 编写接口继承Repository

    @NeedProxy
    public interface Test02 extends Repository<String> {
    }
    
  3. 编写controller

    @RestController
    @RequestMapping("/my")
    public class TestController {
    
        private Test02 test02;
    
        @Autowired
        public TestController(Test02 test02) {
            this.test02 = test02;
        }
    
        @GetMapping("/test01")
        public String test01() {
            Objects.requireNonNull(test02, "你的代码怎么又报错啦!");
            System.out.println("测试 getClass() = " + test02.getClass());
            System.out.println("测试 hashCode() = " + test02.hashCode());
            return test02.print();
        }
    
    }
    
  4. 请求localhost:8080/my/test01.

    1. 控制台打印

      测试 getClass() = class com.sun.proxy.$Proxy50
      测试 hashCode() = 208067420
      class java.lang.String
      
    2. 请求响应结果

      java.lang.String
      

      说明已经成功对接口进行代理并注入到了Spring容器,同时像hashCode()这种Object的方法也可以正常执行.

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐