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

in 编程
关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9

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

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

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

@OverridepublicObject invoke(Object[] argv) throwsThrowable {
    Stream.of(argv).sequential().forEach(System.out::println);return null;}

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

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

@ProxyVersionpublic interfaceProxyTest {
    String find(String a,Integer b);}

先写一个动态代理类

@AllArgsConstructorpublic classProxyInvocationHandler implementsInvocationHandler {
    privateMap<Method,MethodHandler> dispatch;
@OverridepublicObject invoke(Object proxy,Method method,Object[] args) throwsThrowable {
        returndispatch.get(method).invoke(args);}
}

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

public interfaceMethodHandler {
    Object invoke(Object[] argv) throwsThrowable;}

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

public classDefaultMethodHandler implementsMethodHandler {

    @OverridepublicObject invoke(Object[] argv) throwsThrowable {
        Stream.of(argv).sequential().forEach(System.out::println);return null;}
}

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

@Data@AllArgsConstructorpublic classTarget<T> {
    privateClass<T> type;}

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

public classTargetFactory {
    public staticTarget 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 newTarget(collect.get(0));}
}

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

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

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

再加一个ProxyBean的工厂

public classProxyBeanFactory {
    public staticProxyBean createProxyBean() {
        return newProxyBean();}
}

最后写测试

public classProxyMain {

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

运行结果

aaa
233

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

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

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

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

public classTargetFactory {
    privateSet<Class<?>> classes= ClassUtil.getClassSet("com.guanjian.demo.proxytest");
private static finalTargetFactory instance = newTargetFactory();
public staticTargetFactory getInstance(){
        returninstance;}

    privateTargetFactory() {}

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

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

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

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

@AllArgsConstructorpublic classProxyBeanFactory<T> implementsFactoryBean<T> {
    privateClass<T> interfaceType;
/**
     * 返回由FactoryBean创建的bean实例,如果isSingleton()返回true,则该实例会放到Spring容器中单实例缓存池中。
     * @return
     * @throws Exception
     */
    @OverridepublicTgetObject() throwsException {
        ProxyBean<T> proxyBean = newProxyBean<>();returnproxyBean.proxyTest();}

    /**
     * 返回FactoryBean创建的bean类型。
     * @return
     */
    @OverridepublicClass<T> getObjectType() {
        returninterfaceType;}

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

}

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

/**
 * 实现BeanDefinitionRegistryPostProcessor接口来动态生成bean
 */
@Componentpublic classDynamicProxyServiceBeanRegister implementsBeanDefinitionRegistryPostProcessor {

    @Overridepublic voidpostProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throwsBeansException {
        //获取我们需要的接口类型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);}

    @Overridepublic voidpostProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throwsBeansException {

    }

}

最后进行测试

@Componentpublic classProxyMain {
    @AutowiredprivateProxyTest proxyTest;
@PostConstructpublic voidaction() {
        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)

关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9
扫一扫关注公众号添加购物返利助手,领红包
Comments are closed.

推荐使用阿里云服务器

超多优惠券

服务器最低一折,一年不到100!

朕已阅去看看