首页 文章 精选 留言 我的

精选列表

搜索[非结构化元数据],共10000篇文章
优秀的个人博客,低调大师

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

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

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册