使用动态代理只代理接口(非实现类)
假设现在我们有一个已知的算法,我们需要写任意一个接口打上我们特有的标签,那么这个接口的方法都可以执行这个算法,好比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)
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
保护企业免受网络攻击简单的方法
互联网在完成各种任务(如销售网上商品,转移资金)方面的重要作用值得考虑。由于其在加强各类业务运营方面的主要作用,网络犯罪分子已开始将其用于黑客目的。 一份报告声称,2016年,由于网络犯罪,英国有近300万家企业受到影响,其成本约为291亿英镑。这引发了全球各组织之间的紧张关系,他们寻找可以保护其资产的潜在网络安全解决方案。 因此,他们招募网络安全专家或掌握网络安全培训,以获得实际的提示和技巧对抗攻击。本文涵盖的安全措施,可以由任何类型的企业选择,无论域名,规模和政策如何。 增强内部数据安全措施 改进内部安全程序有助于保护企业免受网络攻击。更改密码策略!这包括在定期重置密码时使用强大且唯一的密码。 Thales e-Security和分析公司451的2017年数据威胁报告显示,约有52%的商家面临数据泄露。数据泄露事件增加的原因是,企业仍然使用过去已经显示结果的安全解决方案,但现代数据泄露已经过时。 报告称,如今,企业领导者正在大力投资网络安全措施,而没有为他们分配适当的预算。 该报告还提到,95%的企业在云,物联网等高度受保护的环境中使用敏感数据,并且在缺乏安全协议的渠道中使用相同的...
- 下一篇
《吐血整理》-顶级程序员工具集
你知道的越多,你不知道的越多 点赞再看,养成习惯 GitHub上已经开源 https://github.com/JavaFamily 有一线大厂面试点脑图、个人联系方式和人才交流群,欢迎Star和指教 前言 这期是被人才群交流里,还有很多之前网友评论强行顶出来的一期,就是让我介绍自己常用的一些工具给他们安利一下,我一听很高兴呀,帅丙我这么乐于奉献的人是吧。 主要是能水一篇文章就很开心,不过写下来发现花的时间完全不比写技术少,点赞!!! 千万不要白嫖,真香警告⚠️。 但是我在构思这篇文章的时候发现我贴个标题,然后发下软件信息会不会太乏味了,于是创作鬼才我呀,准备用一个产品的研发流程,是的就是用这样的一个思路去写这个工具集的介绍文章。 因为读者很多还是学生,还有很多应届生,对一个需求的研发流程都不是很熟悉,还有可能对于以后自己需要使用到的工具都不是很熟悉,那我就一一罗列一下,帅丙我作为一个还算有点小经验的程序员都使用哪些工具呢? 那下面就跟随暖男的脚步,走进顶级程序员的百宝箱吧(我所有的标题都是噱头就为了夸大其词,我是低级程序员,大家看了也不能吊打面试官,笑笑就好了)。 注意:下面的软件我...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS6,CentOS7官方镜像安装Oracle11G
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- 设置Eclipse缩进为4个空格,增强代码规范
- Mario游戏-低调大师作品
- MySQL8.0.19开启GTID主从同步CentOS8
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16