Java反射学习总结四(动态代理使用实例和内部原理解析)
通过上一篇文章介绍的静态代理Java反射学习总结三(静态代理)中,大家可以发现在静态代理中每一个代理类只能为一个接口服务,这样一来必然会产生过多的代理,而且对于每个实例,如果需要添加不同代理就要去添加相应的代理类。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能或者说去动态的生成这个代理类,那么此时就必须使用动态代理完成。
动态代理知识点:
Java动态代理类位于java.lang.reflect包下,主要有以下一个接口和一个类:
1.InvocationHandler接口: 该接口中仅有一个方法
public object invoke(Object obj, Method method, Object[] args)
在实际使用时,obj一般是指代理类,method是被代理的方法,args为该方法的参数数组。这个抽象的invoke方法在代理类中动态实现。
2.Proxy类: 该类即为动态代理类,这里只介绍一下newProxyInstance()这个方法
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
这个方法是最主要的方法,它会返回代理类的一个实例,返回后的代理类可以当做被代理类使用
实现动态代理需4步骤:
1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法。
2.通过Proxy的静态方法newProxyInstance创建一个代理
3.创建被代理的类以及接口
4.通过代理调用方法
下面看这个例子具体说明如何通过上面的4个步骤来建立一个动态代理:
步骤1和步骤2合并写在一个类中,命名为DynamicProxy
- public class DynamicProxy implements InvocationHandler {
- // 需要被代理类的引用
- private Object object;
- // 通过构造方法传入引用
- public DynamicProxy(Object object) {
- this.object = object;
- }
- // 定义一个工厂类,去生成动态代理
- public Object getProxy() {
- // 通过Proxy类的newProxyInstance方法动态的生成一个动态代理,并返回它
- return Proxy.newProxyInstance(object.getClass().getClassLoader(), object
- .getClass().getInterfaces(), this);
- }
- // 重写的invoke方法,这里处理真正的方法调用
- @Override
- public Object invoke(Object obj, Method method, Object[] args)
- throws Throwable {
- beforeDoing();
- Object invoke = method.invoke(object, args);
- afterDoing();
- return invoke;
- }
- public void beforeDoing() {
- System.out.println("before ............");
- }
- public void afterDoing() {
- System.out.println("after ............."+"\n");
- }
- }
该类实现了InvocationHandler接口,并且自定义了一个getProxy()方法去调用Proxy类的newProxyInstance()去生成一个动态代理。
步骤3:创建被代理的类以及接口
- //真实角色对象,继承自抽象角色,重写定义的方法。
- public class RealSubject implements Subject1,Subject2{
- //Subject1接口中的方法
- @Override
- public void request() {
- System.out.println("this is real subject");
- }
- //Subject1接口中的方法
- @Override
- public void ask() {
- System.out.println("this is real ask");
- }
- //Subject2接口中的方法
- @Override
- public void request2() {
- System.out.println("this is real subject2");
- }
- }
这个类就是我们需要被代理的类,他继承了两个接口分别是Subject1,Subject2
- interface Subject1 {
- public void request();
- public void ask();
- }
- interface Subject2 {
- public void request2();
- }
4.通过代理调用方法
接下来在main方法中通过动态生成的代理来调用方法
- public static void main(String[] args) {
- //需要被代理的类
- RealSubject realSubject = new RealSubject();
- //用于创建动态代理的类,将被代理类的引用传递进去
- DynamicProxy dynamicProxy = new DynamicProxy(realSubject);
- //通过getProxy方法动态的获取代理类,转换成需要调用的接口类型后调用方法
- Subject1 s1 = (Subject1) dynamicProxy.getProxy();
- s1.request();
- s1.ask();
- //通过getProxy方法动态的获取代理类,转换成需要调用的接口类型后调用方法
- Subject2 s2 = (Subject2) dynamicProxy.getProxy();
- s2.request2();
- }
最后打印:
before ............
this is real subject
after .............
before ............
this is real ask
after .............
before ............
this is real subject2
after .............
简单介绍动态代理内部实现原理:
例子看完了,肯定有如下疑问:
动态代理在哪里应用了反射机制?仅仅通过一个InvocationHandler接口和一个Proxy类的newProxyInstance方法是如何动态的生成代理?
下面就来简单的分析一下InvocationHandler,和Proxy的newProxyInstance方法是如何在运行时动态的生成代理的:
以下代码都是伪代码而且内容大部分参考马士兵动态代理的视频。如果感兴趣,建议找视频去学。
先看newProxyInstance是如何定义的
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
这里需要传入3个参数。先看第二个参数,传入一个接口类型的Class数组。
上面例子中传入的参数是object.getClass().getInterfaces()
object是被代理对象,这个参数就是通过反射拿到被代理对象的所有接口
在上面例子中就是我们定义的Subject1,Subject2接口了
有了接口数组,就可以通过类似下面的代码使用反射拿到接口中的所有方法
- for (interface infce : interfaces[]) {
- Method[] methods = infce.getMethods();
- for (Method m : method) {
- m.getName();
- }
- }
在正常情况下,知道了被代理的接口和接口里面的方法就可以去生成代理类了。
大概就是下面这种的一个简单的实现:一个很固定的套路,只要知道实现接口和方法就仿照写出。
- public class ProxySubject implements Subject{
- private RealSubject realSubject;
- @Override
- public void request() {
- realSubject.request();
- }
- }
动态代理还会在代理的方法中做一些其他的操作,如添加日志,时间,权限等操作。这时候就要靠InvocationHandler接口中的invoke方法。看看例子中如何实现的。
- @Override
- public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
- beforeDoing();
- Object invoke = method.invoke(object, args);
- afterDoing();
- return invoke;
- }
这些代码是我们自定义的,需要实现什么操作就写在里面。
这段代码存在于Invocationhandler对象中,这个对象会在调用Proxy的newProxyInstance的方法中传递进去。
这时候可以通过反射知道被调用方法的名字等信息,之后还是通过字符串的形式拼接处类似下面的动态代理类
- public class ProxySubject implements Subject{
- private RealSubject realSubject;
- @Override
- public void request() {
- Methond md = Subject.getMethod("methodName");
- handler.invoke(this, md);
- }
- }
这个大概就是根据传递的接口对象和InvocationHandler结合后应该生成的代理类。但现在的问题是如何去动态的生成上面这样的代理类。
答案是使用字符串拼接的方式。
从看上面的代码可以看出,除了接口和调用方法不同其他都相同。而且我们已经通过反射获得了方法和接口名字,这样就可以按着这个“套路”去用字符串拼接成这样的一类。
大概就是下面这种代码:
- String source = "package com.gxy.proxy;" + rt
- + "public class "+ClassName+"implements "+InterfaceName+ rt
- + "{" + rt
- + "private "+ ClassName + ClassName.toLowerCase()+" ; " + rt
- + "@Override"
- + "public Void "+InterfaceName+ "()" + rt + " {"
- + "Method md = "+InterfaceName+".getMethod("+ methodName+");" +rt
- + "hander.invoke(this, md);" + rt
- + "}" + rt
- + "}";
用反射生成的出来类名,接口名,方法名去动态的创建这样一个类的字符串。
之后就特定的方法去将这个字符串生成成类。在用反射把这个类取出来。这样就有了这个“动态”生成的代理类了。
就简单介绍到这吧。。。
最后大家可以发现例子中的动态代理里都是通过接口来实现的,如果对于不能实现接口的类就不能用JDK的动态代理了。如果想用就需要使用cglib了,因为cglib是针对类来实现的。
关于动态代理我研究了一个多礼拜,觉得理解起来还是比较困难的,勉勉强强的知道了个大概。
以后有时间我会继续深入的学习动态代理,但暂时还是以反射为主。下一篇准备写一下反射与注解的内容,希望大家多多支持。
Java反射学习总结四(动态代理使用实例和内部原理解析)的更多相关文章
- java反射机制应用之动态代理
1.静态代理类和动态代理类区别 静态代理:要求被代理类和代理类同时实现相应的一套接口:通过代理类的对象调用重写接口的方法时,实际上执行的是被代理类的同样的 方法的调用. 动态代理:在程序运行时,根据被 ...
- java 反射之静态and动态代理
首先说一下我们什么情况下使用代理? (1)设计模式中有一个设计原则是开闭原则,是说对修改关闭对扩展开放,我们在工作中有时会接手很多前人的代码,里面代码逻辑让人摸不着头脑(sometimes the c ...
- 深入分析Java反射(四)-动态代理
动态代理的简介 Java动态代理机制的出现,使得Java开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类.代理类会负责将所有的方法调用分派到委托对象上反射执行,在分 ...
- Java学习笔记--JDK动态代理
1.JDK动态代理 JDK1.3之后,Java提供了动态代理的技术,允许开发者在运行期创建接口的代理实例.JDK的动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和 ...
- 分享非常有用的Java程序 (关键代码)(四)---动态改变数组的大小
原文:分享非常有用的Java程序 (关键代码)(四)---动态改变数组的大小 /** * Reallocates an array with a new size, and copies the co ...
- Java多线程学习(四)等待/通知(wait/notify)机制
转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79690279 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...
- Java反射学习系列-绪论
Java反射学习系列-绪论 https://blog.csdn.net/hanchao5272/article/details/79358924
- 零拷贝详解 Java NIO学习笔记四(零拷贝详解)
转 https://blog.csdn.net/u013096088/article/details/79122671 Java NIO学习笔记四(零拷贝详解) 2018年01月21日 20:20:5 ...
- Java反射学习总结终(使用反射和注解模拟JUnit单元测试框架)
转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 本文是Java反射学习总结系列的最后一篇了,这里贴出之前文章的链接,有兴趣的可以打开看看. ...
随机推荐
- IOS 一句代码搞定启动引导页
前言引导页,一个酷炫的页面,自从微博用了之后一下就火起来了,对于现在来说一个app如果没有引导页似乎总显那么不接地气,那么为了让我们的app也“高大上”一次,我写了一个demo来实现启动引导页的实现, ...
- poweroff---关闭计算机操作系统并且切断系统电源。
poweroff命令用来关闭计算机操作系统并且切断系统电源. 语法 poweroff(选项) 选项 -n:关闭操作系统时不执行sync操作: -w:不真正关闭操作系统,仅在日志文件“/var/log/ ...
- wmic linux python
sudo aptitude install wmi-client Example of usage is; wmic -U DOMAIN/administrator%password //10.99. ...
- 洛谷 P1217 [USACO1.5]回文质数 Prime Palindromes
P1217 [USACO1.5]回文质数 Prime Palindromes 题目描述 因为151既是一个质数又是一个回文数(从左到右和从右到左是看一样的),所以 151 是回文质数. 写一个程序来找 ...
- Harry Potter and the Goblet of Fire
书名:Harry Potter and the Goblet of Fire 作者:J.K. Rowling 篇幅: 752页 蓝思值:880L 用时: 17天 工具: 有道词典 [透析成果 ...
- PHP中字符串比较的常用方法
PHP中字符串比较的常用方法 一.总结 1.其实应该是直接等于号就可以了的 2.也可用strcmp,注意返回值 二.PHP中字符串比较的常用方法 1.按字节比较 按字节比较字符串是最常用的方法.其中可 ...
- ELK之日志查询、收集与分析系统
项目由来 (1)开发人员不能登录线上服务器查看详细日志,经过运维周转费时费力 (2)日志数据分散在多个系统,难以查找与整合 (3)日志数据量巨大,查询速度太慢,无法满足需求 (4)无法全局掌控项目运行 ...
- Android 多线程断点续传同时下载多个大文件
最近学习在Android环境中一些网络请求方面的知识,其中有一部分是关于网络下载方面的知识.在这里解析一下自己写的demo,总结一下自己所学的知识.下图为demo的效果图,仿照一些应用下载商城在Lis ...
- golang round
func Round(f float64, n int) float64 {pow10_n := math.Pow10(n)return math.Trunc((f+0.5/pow10_n)*pow1 ...
- 界面实例--旋转的3d立方体
好吧,这里直接编辑源码并不允许设置css和js……毕竟会有危险……那直接放代码吧 <!DOCTYPE html> <html> <head> <meta ch ...