h5网站制作案例分析,android studio教程,怎么更改网站里的tdk,搭建wordpress靶机Java动态代理实现与原理详细分析
关于Java中的动态代理#xff0c;我们首先需要了解的是一种常用的设计模式–代理模式#xff0c;而对于代理#xff0c;根据创建代理类的
时间点#xff0c;又可以分为静态代理和动态代理。
1、代理模式
代理模式是常用的java设计模式我们首先需要了解的是一种常用的设计模式–代理模式而对于代理根据创建代理类的
时间点又可以分为静态代理和动态代理。
1、代理模式
代理模式是常用的java设计模式他的特征是代理类与委托类有同样的接口代理类主要负责为委托类预处理消
息、过滤消息、把消息转发给委托类以及事后处理消息等。代理类与委托类之间通常会存在关联关系一个代理
类的对象与一个委托类的对象关联代理类的对象本身并不真正实现服务而是通过调用委托类的对象的相关方
法来提供特定的服务。简单的说就是我们在访问实际对象时是通过代理对象来访问的代理模式就是在访问
实际对象时引入一定程度的间接性因为这种间接性可以附加多种用途。在后面我会解释这种间接性带来的好
处。代理模式结构图图片来自《大话设计模式》 2、静态代理
2.1 静态代理
静态代理由程序员创建或特定工具自动生成源代码也就是在编译时就已经将接口被代理类代理类等确定下
来。在程序运行之前代理类的.class文件就已经生成。
2.2 静态代理简单实现
根据上面代理模式的类图来写一个简单的静态代理的例子。我这儿举一个比较粗糙的例子假如一个班的同学要
向老师交班费但是都是通过班长把自己的钱转交给老师。这里班长就是代理学生上交班费班长就是学生的代
理。
首先我们创建一个Person接口。这个接口就是学生被代理类和班长代理类的公共接口他们都有上
交班费的行为。这样学生上交班费就可以让班长来代理执行。
package com.proxy.staticproxy;/*** 创建Person接口*/
public interface Person {//上交班费void giveMoney();
}Student类实现Person接口Student可以具体实施上交班费的动作。
package com.proxy.staticproxy;public class Student implements Person {private String name;public Student(String name) {this.name name;}Overridepublic void giveMoney() {System.out.println(name 上交班费50元);}
}StudentsProxy类这个类也实现了Person接口但是还另外持有一个学生类对象由于实现了Person接口同
时持有一个学生对象那么他可以代理学生类对象执行上交班费执行giveMoney()方法行为。
package com.proxy.staticproxy;/*** 学生代理类也实现了Person接口保存一个学生实体这样既可以代理学生产生行为*/
public class StudentsProxy implements Person {//被代理的学生Student stu;public StudentsProxy(Person stu) {// 只代理学生对象if (stu.getClass() Student.class) {this.stu (Student) stu;}}//代理上交班费调用被代理学生的上交班费行为public void giveMoney() {stu.giveMoney();}
}下面测试一下看如何使用代理模式
package com.proxy.staticproxy;public class StaticProxyTest {public static void main(String[] args) {//被代理的学生张三他的班费上交有代理对象monitor班长完成Person zhangsan new Student(张三);//生成代理对象并将张三传给代理对象Person monitor new StudentsProxy(zhangsan);//班长代理上交班费monitor.giveMoney();}
}运行结果
# 程序输出
张三上交班费50元这里并没有直接通过张三被代理对象来执行上交班费的行为而是通过班长代理对象来代理执行了。这就
是代理模式。
代理模式最主要的就是有一个公共接口Person一个具体的类Student一个代理类StudentsProxy,
代理类持有具体类的实例代为执行具体类实例方法。上面说到代理模式就是在访问实际对象时引入一定程度的
间接性因为这种间接性可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法那么我们在代理
过程中就可以加上一些其他用途。就这个例子来说加入班长在帮张三上交班费之前想要先反映一下张三最近学习
有很大进步通过代理模式很轻松就能办到
package com.proxy.staticproxy;/*** author zhangshixing* date 2021年11月02日 19:13*//*** 学生代理类也实现了Person接口保存一个学生实体这样既可以代理学生产生行为** author Gonjan*/
public class StudentsProxy implements Person {//被代理的学生Student stu;public StudentsProxy(Person stu) {// 只代理学生对象if (stu.getClass() Student.class) {this.stu (Student) stu;}}//代理上交班费调用被代理学生的上交班费行为public void giveMoney() {System.out.println(张三最近学习有进步);stu.giveMoney();}
}运行结果
张三最近学习有进步
张三上交班费50元可以看到只需要在代理类中帮张三上交班费之前执行其他操作就可以了。这种操作也是使用代理模式的一个
很大的优点。最直白的就是在Spring中的面向切面编程AOP我们能在一个切点之前执行一些操作在一个切
点之后执行一些操作这个切点就是一个个方法。这些方法所在类肯定就是被代理了在代理过程中切入了一些其
他操作。
3、动态代理
3.1 动态代理
代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中代理类(studentProxy)是自
己定义好的在程序运行之前就已经编译完成。然而动态代理代理类并不是在Java代码中定义的而是在运行时
根据我们在Java代码中的“指示”动态生成的。相比于静态代理 动态代理的优势在于可以很方便的对代理类的函数
进行统一的处理而不用修改每个代理类中的方法。 比如说想要在每个代理的方法前都加上一个处理方法
public void giveMoney() {//调用被代理方法前加入处理方法beforeMethod();stu.giveMoney();
}这里只有一个giveMoney方法就写一次beforeMethod方法但是如果出了giveMonney还有很多其他的方法
那就需要写很多次beforeMethod方法麻烦。那看看下面动态代理如何实现。
3.2 动态代理简单实现
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口通过这个类和这个接口可以生
成JDK动态代理类和动态代理对象。
创建一个动态代理对象步骤具体代码见后面
创建一个InvocationHandler对象
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler new MyInvocationHandlerPerson(stu);使用Proxy类的getProxyClass静态方法生成一个动态代理类stuProxyClass
Class? stuProxyClass Proxy.getProxyClass(Person.class.getClassLoader(), new Class?[] {Person.class});获得stuProxyClass 中一个带InvocationHandler参数的构造器constructor
Constructor? constructor PersonProxy.getConstructor(InvocationHandler.class);通过构造器constructor来创建一个动态实例stuProxy
Person stuProxy (Person) cons.newInstance(stuHandler);就此一个动态代理对象就创建完毕当然上面四个步骤可以通过Proxy类的newProxyInstances方法来简化
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler new MyInvocationHandlerPerson(stu);
//创建一个代理对象stuProxy代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Person stuProxy (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class?[]{Person.class}, stuHandler);到这里肯定都会很疑惑这动态代理到底是如何执行的是如何通过代理对象来执行被代理对象的方法的先不
急我们先看看一个简单的完整的动态代理的例子。还是上面静态代理的例子班长需要帮学生代交班费。
首先是定义一个Person接口:
package com.proxy.dynamicproxy;/*** 创建Person接口*/
public interface Person {//上交班费void giveMoney();
}创建需要被代理的实际类
package com.proxy.dynamicproxy;public class Student implements Person {private String name;public Student(String name) {this.name name;}Overridepublic void giveMoney() {try {//假设数钱花了一秒时间Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(name 上交班费50元);}
}再定义一个检测方法执行时间的工具类在任何方法执行前先调用start方法执行后调用finish方法就可以计算
出该方法的运行时间这也是一个最简单的方法执行时间检测工具。
package com.proxy.dynamicproxy;public class MonitorUtil {private static ThreadLocalLong tl new ThreadLocal();public static void start() {tl.set(System.currentTimeMillis());}//结束时打印耗时public static void finish(String methodName) {long finishTime System.currentTimeMillis();System.out.println(methodName 方法耗时 (finishTime - tl.get()) ms);}
}创建StuInvocationHandler类实现InvocationHandler接口这个类中持有一个被代理对象的实例target。
InvocationHandler中有一个invoke方法所有执行代理对象的方法都会被替换成执行invoke方法。
再再invoke方法中执行被代理对象target的相应方法。当然在代理过程中我们在真正执行被代理对象的方法前
加入自己其他处理。这也是Spring中的AOP实现的主要原理这里还涉及到一个很重要的关于java反射方面的基础
知识。
package com.proxy.dynamicproxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class StuInvocationHandlerT implements InvocationHandler {//invocationHandler持有的被代理对象T target;public StuInvocationHandler(T target) {this.target target;}/*** proxy:代表动态代理对象* method代表正在执行的方法* args代表调用目标方法时传入的实参*/Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(代理执行 method.getName() 方法);//代理过程中插入监测方法,计算该方法耗时MonitorUtil.start();Object result method.invoke(target, args);MonitorUtil.finish(method.getName());return result;}
}做完上面的工作后我们就可以具体来创建动态代理对象了上面简单介绍了如何创建动态代理对象我们使用简
化的方式创建动态代理对象
package com.proxy.dynamicproxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;public class ProxyTest {public static void main(String[] args) {//创建一个实例对象这个对象是被代理的对象Person zhangsan new Student(张三);//创建一个与代理对象相关联的InvocationHandlerInvocationHandler stuHandler new StuInvocationHandler(zhangsan);//创建一个代理对象stuProxy来代理zhangsan代理对象的每个执行方法都会替换执行Invocation中的invoke方法Person stuProxy (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class?[]{Person.class}, stuHandler);//代理执行上交班费的方法stuProxy.giveMoney();}
}我们执行这个ProxyTest类先想一下我们创建了一个需要被代理的学生张三将zhangsan对象传给了
stuHandler中我们在创建代理对象stuProxy时将stuHandler作为参数了的上面也有说到所有执行代理对象
的方法都会被替换成执行invoke方法也就是说最后执行的是StuInvocationHandler中的invoke方法。所以在
看到下面的运行结果也就理所当然了。
运行结果
# 程序输出
代理执行giveMoney方法
张三上交班费50元
giveMoney方法耗时1014ms上面说到动态代理的优势在于可以很方便的对代理类的函数进行统一的处理而不用修改每个代理类中的方法。
是因为所有被代理执行的方法都是通过在InvocationHandler中的invoke方法调用的所以我们只要在invoke方
法中统一处理就可以对所有被代理的方法进行相同的操作了。例如这里的方法计时所有的被代理对象执行的
方法都会被计时然而我只做了很少的代码量。
动态代理的过程代理对象和被代理对象的关系不像静态代理那样一目了然清晰明了。因为动态代理的过程中
我们并没有实际看到代理类也没有很清晰地的看到代理类的具体样子而且动态代理中被代理对象和代理对象是
通过InvocationHandler来完成的代理过程的其中具体是怎样操作的为什么代理对象执行的方法都会通过
InvocationHandler中的invoke方法来执行。带着这些问题我们就需要对java动态代理的源码进行简要的分析
弄清楚其中缘由。
4、动态代理原理分析
4.1 Java动态代理创建出来的动态代理类
上面我们利用Proxy类的newProxyInstance方法创建了一个动态代理对象查看该方法的源码发现它只是封装
了创建动态代理类的步骤
public static Object newProxyInstance(ClassLoader loader,Class?[] interfaces,InvocationHandler h)throws IllegalArgumentException
{Objects.requireNonNull(h);final Class?[] intfs interfaces.clone();final SecurityManager sm System.getSecurityManager();if (sm ! null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}/** Look up or generate the designated proxy class.*/Class? cl getProxyClass0(loader, intfs);/** Invoke its constructor with the designated invocation handler.*/try {if (sm ! null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}final Constructor? cons cl.getConstructor(constructorParams);final InvocationHandler ih h;if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedActionVoid() {public Void run() {cons.setAccessible(true);return null;}});}return cons.newInstance(new Object[]{h});} catch (IllegalAccessException|InstantiationException e) {throw new InternalError(e.toString(), e);} catch (InvocationTargetException e) {Throwable t e.getCause();if (t instanceof RuntimeException) {throw (RuntimeException) t;} else {throw new InternalError(t.toString(), t);}} catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);}
}其实我们最应该关注的是 Class? cl getProxyClass0(loader, intfs);这句这里产生了代理类后面代码中的
构造器也是通过这里产生的类来获得可以看出这个类的产生就是整个动态代理的关键由于是动态生成的类文
件我这里不具体进入分析如何产生的这个类文件只需要知道这个类文件时缓存在java虚拟机中的我们可以通
过下面的方法将其打印到文件里面一睹真容
package com.proxy.dynamicproxy;import sun.misc.ProxyGenerator;import java.io.FileOutputStream;public class ProxyGeneratorTest {public static void main(String[] args) {byte[] classFile ProxyGenerator.generateProxyClass($Proxy0, Student.class.getInterfaces());String path ./StuProxy.class;try(FileOutputStream fos new FileOutputStream(path)) {fos.write(classFile);fos.flush();System.out.println(代理类class文件写入成功);} catch (Exception e) {System.out.println(写文件错误);}}
}对这个class文件进行反编译我们看看jdk为我们生成了什么样的内容
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//import com.proxy.dynamicproxy.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy0 extends Proxy implements Person {private static Method m1;private static Method m2;private static Method m3;private static Method m0;public $Proxy0(InvocationHandler var1) throws {super(var1);}public final boolean equals(Object var1) throws {try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final String toString() throws {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final void giveMoney() throws {try {super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final int hashCode() throws {try {return (Integer)super.h.invoke(this, m0, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}static {try {m1 Class.forName(java.lang.Object).getMethod(equals, Class.forName(java.lang.Object));m2 Class.forName(java.lang.Object).getMethod(toString);m3 Class.forName(com.proxy.dynamicproxy.Person).getMethod(giveMoney);m0 Class.forName(java.lang.Object).getMethod(hashCode);} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}jdk为我们的生成了一个叫$Proxy0这个名字后面的0是编号有多个代理类会一次递增的代理类这个类文件
时放在内存中的我们在创建代理对象时就是通过反射获得这个类的构造方法然后创建的代理实例。通过对这
个生成的代理类源码的查看我们很容易能看出动态代理实现的具体过程。
我们可以对InvocationHandler看做一个中介类中介类持有一个被代理对象在invoke方法中调用了被代理对象
的相应方法。通过聚合方式持有被代理对象的引用把外部对invoke的调用最终都转为对被代理对象的调用。
代理类调用自己方法时通过自身持有的中介类对象来调用中介类对象的invoke方法从而达到代理执行被代理
对象的方法。也就是说动态代理通过中介类实现了具体的代理功能。
5、总结
生成的代理类$Proxy0 extends Proxy implements Person我们看到代理类继承了Proxy类所以也就决定了
java动态代理只能对接口进行代理Java的继承机制注定了这些动态代理类们无法实现对class的动态代理。
上面的动态代理的例子其实就是AOP的一个简单实现了在目标对象的方法执行之前和执行之后进行了处理对
方法耗时统计。Spring的AOP实现其实也是用了Proxy和InvocationHandler这两个东西的。