使用动态代理只代理接口(非实现类)


声明:本文转载自https://my.oschina.net/u/3768341/blog/3134058,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

假设现在我们有一个已知的算法,我们需要写任意一个接口打上我们特有的标签,那么这个接口的方法都可以执行这个算法,好比Mybatis的Dao,或者Feign的接口。现在假设我们这个特有的标签如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ProxyVersion {
}

已知的算法为打印方法的所有参数。

@Override
public Object invoke(Object[] argv) throws Throwable {
    Stream.of(argv).sequential().forEach(System.out::println);
    return null;
}

-------------------------------------------------------------

现在需求清楚了,我们来随意写个接口,并打上该标签。

@ProxyVersion
public interface ProxyTest {
    String find(String a, Integer b);
}

先写一个动态代理类

@AllArgsConstructor
public class ProxyInvocationHandler implements InvocationHandler {
    private Map<Method,MethodHandler> dispatch;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return dispatch.get(method).invoke(args);
    }
}

其中MethodHandler为方法处理器接口,定义如下

public interface MethodHandler {
    Object invoke(Object[] argv) throws Throwable;
}

然后写一个方法处理器接口的实现类,它包含了我们固定要实现的算法。

public class DefaultMethodHandler implements MethodHandler {

    @Override
    public Object invoke(Object[] argv) throws Throwable {
        Stream.of(argv).sequential().forEach(System.out::println);
        return null;
    }
}

我们首先写一个目标类,因为我们不知道我们要代理的是啥接口,所以使用泛型,并且包含一个该泛型的Class属性type。

@Data
@AllArgsConstructor
public class Target<T> {
    private Class<T> type;
}

然后为来创建该目标类,写一个目标工厂类,从该目标工厂类去搜索包下的所有类,并获取带有@ProxyVersion标签的接口放入我们的目标对象属性中。这里我们做了简化处理,只取第一个接口。

public class TargetFactory {
    public static Target createTarget() {
        Set<Class<?>> classes = ClassUtil.getClassSet("com.guanjian.demo.proxytest");
        List<Class<?>> collect = classes.stream()
                .filter(Class::isInterface)
                .filter(clazz -> clazz.isAnnotationPresent(ProxyVersion.class))
                .collect(Collectors.toList());
        return new Target(collect.get(0));
    }
}

ClassUtil代码请参考@Compenent,@Autowired,@PostConstruct自实现

现在我们要调用动态代理类,这里我们也做了简化处理,只取第一个方法。最终返回我们代理的接口实例

public class ProxyBean {
    public Object proxyTest() {
        Map<Method,MethodHandler> methodToHandler = new HashMap<>();
        //获取目标对象
        Target target = TargetFactory.createTarget();
        //将目标对象的方法以及方法处理器(方法处理器包含我们需要的固定算法)放入映射中
        methodToHandler.put(target.getType().getMethods()[0],new DefaultMethodHandler());
        //构建动态代理对象
        InvocationHandler handler = new ProxyInvocationHandler(methodToHandler);
        //返回动态代理代理的接口实例
        return Proxy.newProxyInstance(target.getType().getClassLoader(), new Class[]{target.getType()}, handler);
    }
}

再加一个ProxyBean的工厂

public class ProxyBeanFactory {
    public static ProxyBean createProxyBean() {
        return new ProxyBean();
    }
}

最后写测试

public class ProxyMain {

    public static void main(String[] args) {
        ProxyTest test = (ProxyTest)ProxyBeanFactory.createProxyBean().proxyTest();
        test.find("aaa",233);
    }
}

运行结果

aaa
233

如果我们换一个接口替换ProxyTest也是一样,随意定义接口,都可以获取执行的结果。

由于我们在使用Mybatis或者Feign的时候都是使用依赖注入的,所以我们现在要将该例子修改为Spring依赖注入的形式。在此要感谢我的好兄弟雄爷(李少雄)提供的支持。

要实现Spring依赖注入,我们需要修改一部分代码。

首先要将TargetFactory修改如下,不再使用静态方法,而修改成单例模式,便于获取接口的类型。

public class TargetFactory {
    private Set<Class<?>> classes = ClassUtil.getClassSet("com.guanjian.demo.proxytest");

    private static final TargetFactory instance = new TargetFactory();

    public static TargetFactory getInstance(){
        return instance;
    }

    private TargetFactory() {}

    public Target createTarget() {
        return new Target(getNeedProxyClass());
    }
    
    public Class<?> getNeedProxyClass() {
        List<Class<?>> collect = classes.stream()
                .filter(Class::isInterface)
                .filter(clazz -> clazz.isAnnotationPresent(ProxyVersion.class))
                .collect(Collectors.toList());
        return collect.get(0);
    }
}

所以ProxyBean在获取target目标对象的时候需要由单例来获取,并且proxyTest方法返回改为泛型。

public class ProxyBean<T> {
    public T proxyTest() {
        Map<Method,MethodHandler> methodToHandler = new HashMap<>();
        //获取目标对象
        Target target = TargetFactory.getInstance().createTarget();
        //将目标对象的方法以及方法处理器(方法处理器包含我们需要的固定算法)放入映射中
        methodToHandler.put(target.getType().getMethods()[0],new DefaultMethodHandler());
        //构建动态代理对象
        InvocationHandler handler = new ProxyInvocationHandler(methodToHandler);
        //返回动态代理代理的实例
        return (T) Proxy.newProxyInstance(target.getType().getClassLoader(), new Class[]{target.getType()}, handler);
    }
}

ProxyBean工厂需要实现FactoryBean接口,该接口为Spring环境的接口(org.springframework.beans.factory.FactoryBean)

@AllArgsConstructor
public class ProxyBeanFactory<T> implements FactoryBean<T> {
    private Class<T> interfaceType;

    /**
     * 返回由FactoryBean创建的bean实例,如果isSingleton()返回true,则该实例会放到Spring容器中单实例缓存池中。
     * @return
     * @throws Exception
     */
    @Override
    public T getObject() throws Exception {
        ProxyBean<T> proxyBean = new ProxyBean<>();
        return proxyBean.proxyTest();
    }

    /**
     * 返回FactoryBean创建的bean类型。
     * @return
     */
    @Override
    public Class<T> getObjectType() {
        return interfaceType;
    }

    /**
     * 返回由FactoryBean创建的bean实例的作用域是singleton还是prototype。
     * @return
     */
    @Override
    public boolean isSingleton() {
        return true;
    }

}

然后我们需要对接口以及动态代理代理的接口实例在Spring环境中进行bean的动态注册,其实无论是Mybatis的Dao还是Feign的接口都是这么处理的。

/**
 * 实现BeanDefinitionRegistryPostProcessor接口来动态生成bean
 */
@Component
public class DynamicProxyServiceBeanRegister implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        //获取我们需要的接口类型
        Class<?> beanClazz = TargetFactory.getInstance().getNeedProxyClass();
        //根据接口类生成一个bean的建造器
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz);
        //根据该建造起获取bean的定义器
        GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();
        //定义该接口类型为该bean的匹配类型
        //如使用SpringbootApplication.getBean(匹配类型.class)
        definition.getConstructorArgumentValues().addGenericArgumentValue(beanClazz);
        //定义bean工厂中的动态代理代理的接口实例为bean的对象
        //如 bean对象=SpringbootApplication.getBean(匹配类型.class)
        definition.setBeanClass(ProxyBeanFactory.class);
        //定义@Autowired的装配方式,这里用默认装配方式即可
//        definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
        //注册该定义器,并把接口名做为注册的标识
        registry.registerBeanDefinition(beanClazz.getSimpleName(), definition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

}

最后进行测试

@Component
public class ProxyMain {
    @Autowired
    private ProxyTest proxyTest;

    @PostConstruct
    public void action() {
        proxyTest.find("aaa",233);
    }
}

启动Springboot,运行结果如下

2019-11-27 22:48:09.419  INFO 535 --- [           main] com.guanjian.demo.DyimportApplication    : Starting DyimportApplication on admindeMacBook-Pro.local with PID 535 (/Users/admin/Downloads/dyimport/target/classes started by admin in /Users/admin/Downloads/dyimport)
2019-11-27 22:48:09.422  INFO 535 --- [           main] com.guanjian.demo.DyimportApplication    : No active profile set, falling back to default profiles: default
aaa
233
2019-11-27 22:48:10.025  INFO 535 --- [           main] com.guanjian.demo.DyimportApplication    : Started DyimportApplication in 15.783 seconds (JVM running for 26.124)

本文发表于2019年11月28日 09:00
(c)注:本文转载自https://my.oschina.net/u/3768341/blog/3134058,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 131 讨论 0 喜欢 0

讨论

周娱

君子和而不同
按照自己的方式,去度过人生

8027 3840246
抢先体验

扫码体验
趣味小程序
文字表情生成器

加入组织

扫码添加周娱微信
备注“加入组织”
邀请进开发群

闪念胶囊

人活一辈子,不是一年两年。时间是有连续性的,做抉择的时候要多看几步。保持警惕,大丈夫有所为,有所不为。

跟人接触,不要想:我能从你身上得到什么,要想:我能给你什么。 想通了,内核就稳了。

这个世界上,别人只会看你现在的样子而不是以后的样子。你以后的样子只有自己才相信。如果没有执行力,一切都是虚妄。

对普通人来说,人和人相处其实最重要的是感觉。感觉不好,你说什么都没用,怎么解释都没用,越说越错,反正最后不好的锅都往你身上扣。所谓“说你行你就行,不行也行。说你不行,你就不行,行也不行”就是这个意思。狼要吃人根本不需要理由,你也同样叫不醒装睡的人。遇到这种情况,早点闪人才是上策。不过大部分人的问题是没有闪人的心态,能力,和资源。

考985不牛逼,考上才牛逼。创业不牛逼,创业成功才牛逼。这个社会上很多人把目标当成牛逼的资本,牛逼哄哄的,死活不听劝,然后做的一塌糊涂,给别人添麻烦,让别人帮他料理后事,对此只能呵呵。

当你尝到用生气解决问题的甜头后,你就懒得再用其他方式了。你却忽略了,生气是鸩毒啊,剂量用够了,你的关系也玩完了。

年轻的时候你只搞事业不谈恋爱,等你事业有成了,钱相对自由了,你可能已经没有荷尔蒙了。

如果你经常雇佣比你矮小的人,将来我们就会变成矮人国,变成一家侏儒公司。相反,如果你每次都雇用比你高大的人,日后我们必能成为一家巨人公司。

如果一个人有充裕的时间去完成一项工作,那么他就会放慢节奏或者增加其他不必要的工作,直到花光所有的时间。

Copyright © 2016 - 2020 Cion.
All Rights Reserved.
备案:鲁ICP备19012333号-4.