您现在的位置是:首页 > 文章详情

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

日期:2019-11-26点击:366

假设现在我们有一个已知的算法,我们需要写任意一个接口打上我们特有的标签,那么这个接口的方法都可以执行这个算法,好比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)

原文链接:https://my.oschina.net/u/3768341/blog/3134058
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章