Java设计模式之深入浅出动态代理设计模式
yuyutoo 2024-10-20 13:08 1 浏览 0 评论
来源:升学就业帮讲师——肖云锐
一、代理模式
在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成。
代理模式的定义与特点
代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
代理模式的主要优点有:
1. 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
2. 代理对象可以扩展目标对象的功能;
3.原来客户端直接依赖目标对象,通过代理模式后,客户端直接依赖代理对象,从而在一定程度上降低了系统的耦合度,简化了目标对象的代码,降低了目标对象的依赖关系,能将客户端与目标对象分离。
其主要缺点是:
1. 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
2. 增加了系统的复杂度;
代理模式的结构与实现
代理模式的结构比较简单,主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问,下面来分析其基本结构和实现方法。
模式的结构
代理模式的主要角色如下。
1. 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
2. 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
3. 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
其结构图如图 1 所示:
关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式--代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理。
二、静态代理
1、静态代理
静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
2、静态代理简单实现
根据上面代理模式的类图,来写一个简单的静态代理的例子。我这儿举一个比较粗糙的例子,假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长就是代理学生上交班费,班长就是学生的代理。
首先,我们创建一个IPerson接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为。这样,学生上交班费就可以让班长来代理执行。
/**
* 学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为
* @author 肖云锐
*/
public interface IPerson {
void payMoney();
}
Student类实现IPerson接口。Student可以具体实施上交班费的动作。
/**
* Student类实现Person接口。Student可以具体实施上交班费的动作
* @author 肖云锐
*/
public class Student implements IPerson{
private String name;
public Student(String name) {
this.name = name;
}
public void payMoney() {
System.out.println(name + "缴费3000元");
}
}
StudentsProxy类,这个类也实现了IPerson接口,但是还另外持有一个学生类对象,由于实现了IPeson接口,同时持有一个学生对象,那么他可以代理学生类对象执行上交班费(执行giveMoney()方法)行为。
/**
* 学生代理类,也实现了IPerson接口,依赖学生实体,这样就可以代理学生产生行为
* @author 肖云锐
*/ public class StudentProxy implements IPerson{
//被代理的学生
Student stu;
public StudentProxy(IPerson stu) {
// 只代理学生对象
if(stu.getClass() == Student.class) {
this.stu = (Student)stu;
}
}
//代理上交班费,调用被代理学生的上交班费行为
public void payMoney() {
stu.payMoney();
}
}
下面测试一下,看如何使用代理模式:
/**
* 静态代理测试类
* @author 肖云锐
*/
public class StaticProxyTest {
public static void main(String[] args) {
// 被代理的学生张三,
他的班费上交由代理对象monitor(班长)完成
IPerson xyr = new Student("老肖");
// 生成代理对象,并将张三传给代理对象
IPerson monitor = new StudentProxy(xyr);
// 班长代理上交班费
monitor.payMoney();
}
}
运行程序,执行结果如下图所示:
这里并没有直接通过老肖(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。
代理模式最主要的就是有一个公共接口(IPerson),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。就这个例子来说,假如班长在帮老肖上交班费之前想要先反映一下老肖最近学习有很大进步,通过代理模式很轻松就能办到,并且不需要老肖自己去反映,完全不需要修改Student类的源代码。
/**
* 学生代理类,也实现了IPerson接口,依赖学生实体,这样就可以代理学生产生行为
* @author Gonjan
*/
public class StudentProxy implements IPerson{
//被代理的学生
Student stu;
public StudentProxy(IPerson stu) {
// 只代理学生对象
if(stu.getClass() == Student.class) {
this.stu = (Student)stu;
}
}
//代理上交班费,调用被代理学生的上交班费行为
public void payMoney() {
System.out.println("老肖最近学习有进步!");
stu.payMoney();
}
}
运行程序,执行结果如下图所示:
可以看到,只需要在代理类中帮张三上交班费之前,执行其他操作就可以了。这种操作,也是使用代理模式的一个很大的优点。最直白的就是在Spring中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。
三、动态代理
1.动态代理
代理类在程序运行时创建的代理方式被称为动态代理。 我们上面静态代理的例子中,代理类(StudentProxy)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。 比如说,想要在每个代理的方法前都加上一个处理方法:
public void payMoney() {
//调用被代理方法前加入处理方法
beforeMethod();
stu.payMoney();
}
这里只有一个payMoney方法,就写一次beforeMethod方法,但是如果除了payMoney还有很多其他的方法,那就需要写很多次beforeMethod方法,麻烦。那看看下面动态代理如何实现。
2、动态代理简单实现
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
创建一个动态代理对象步骤,具体代码见后面:
1.创建一个InvocationHandler对象
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new MyInvocationHandler<IPerson>(xyr);
2.使用Proxy类的getProxyClass静态方法生成一个动态代理类stuProxyClass
Class<?> stuProxyClass = Proxy.getProxyClass(IPerson.class.getClassLoader(), new Class<?>[] {IPerson.class});
3.获得stuProxyClass 中一个带InvocationHandler参数的构造器constructor
Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);
4.通过构造器constructor来创建一个动态实例stuProxy
IPerson stuProxy = (Person) constructor.newInstance(stuHandler);
就此,一个动态代理对象就创建完毕,当然,上面四个步骤可以通过Proxy类的newProxyInstances方法来简化:
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new MyInvocationHandler<Person>(xyr);
//创建一个代理对象stuProxy,代理对象的每个执行方法都会替换执行Invocation中的invok方法
IPerson stuProxy= (Person) Proxy.newProxyInstance(IPerson.class.getClassLoader(), new Class<?>[]{IPerson.class}, stuHandler);
到这里肯定都会很疑惑,这动态代理到底是如何执行的,是如何通过代理对象来执行被代理对象的方法的,先不急,我们先看看一个简单的完整的动态代理的例子。还是上面静态代理的例子,班长需要帮学生代交班费。
首先是定义一个IPerson接口:
public interface IPerson {
void payMoney();
}
创建需要被代理的实际类:
public class Student implements IPerson{
private String name; public Student(String name) {
this.name = name; } public void payMoney() {
try {
//假设数钱花了一秒时间
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
} System.out.println(name + "上缴班费3000元");
}
}
再定义一个检测方法执行时间的工具类,在任何方法执行前先调用start方法,执行后调用finsh方法,就可以计算出该方法的运行时间,这也是一个最简单的方法执行时间检测工具。
public class MonitorUtil {
private static ThreadLocal<Long> tl = new ThreadLocal<Long>();
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反射方面的基础知识。
public class StuInvocationHandler<T> implements InvocationHandler {
//invocationHandler持有的被代理对象
T target;
public StuInvocationHandler(T target) {
this.target = target;
}
/**
* proxy:代表动态代理对象
* method:代表正在执行的方法
* args:代表调用目标方法时传入的实参
*/
@Override
public 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;
}
}
做完上面的工作后,我们就可以具体来创建动态代理对象了,上面简单介绍了如何创建动态代理对象,我们使用简化的方式创建动态代理对象:
public class ProxyTest {
public static void main(String[] args) {
//创建一个实例对象,这个对象是被代理的对象
IPerson xyr = new Student("老肖");
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new StuInvocationHandler<IPerson>(xyr);
//创建一个代理对象stuProxy来代理xyr,代理对象的每个执行方法都会替换执行Invocation 中的invoke方法
IPerson stuProxy = (IPerson) Proxy.newProxyInstance(IPerson.class. getClassLoader(),
new Class<?>[]{IPerson.class}, stuHandler);
//代理执行上交班费的方法
stuProxy.payMoney();
}
}
我们执行这个ProxyTest类,先想一下,我们创建了一个需要被代理的学生张三,将xyr对象传给了stuHandler中,我们在创建代理对象stuProxy时,将stuHandler作为参数了的,上面也有说到所有执行代理对象的方法都会被替换成执行invoke方法,也就是说,最后执行的是StuInvocationHandler中的invoke方法。所以在看到下面的运行结果也就理所当然了。
运行结果如下图所示:
上面说到,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。例如,这里的方法计时,所有的被代理对象执行的方法都会被计时,然而我只做了很少的代码量。
动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然,清晰明了。因为动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地地看到代理类的具体样子,而且动态代理中被代理对象和代理对象是通过InvocationHandler来完成的代理过程的,其中具体是怎样操作的,为什么代理对象执行的方法都会通过InvocationHandler中的invoke方法来执行。带着这些问题,我们就需要对java动态代理的源码进行简要的分析,弄清楚其中缘由。
四、动态代理原理分析
1、Java动态代理创建出来的动态代理类
上面我们利用Proxy类的newProxyInstance方法创建了一个动态代理对象,查看该方法的源码,发现它只是封装了创建动态代理类的步骤:
class Proxy {
private static final Class<?>[] constructorParams =
{ InvocationHandler.class };
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
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);
}
// 通过代理类字节码对象获取带InvocationHandler参数的构造方法对象
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() { cons.setAccessible(true); return null;
}
});
}
// 通过反射创建对象,并且传入InvocationHandler对象
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虚拟机中的,我们可以通过下面的方法将其打印到文件里面,一睹真容:
import com.xyr.dynamicproxy.IPerson;
import java.lang.reflect.*;
class Proxy {
private static final Class<?>[] constructorParams =
{ InvocationHandler.class };
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
// ...此处代码省略
}
public final class $Proxy0 extends Proxy
implements IPerson
{
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals",
new Class[] {
Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.xyr.dynamicproxy.IPerson").getMethod("payMoney", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
}
catch (NoSuchMethodException nosuchmethodexception)
{
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
}
catch (ClassNotFoundException classnotfoundexception)
{
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
/*
* 代理对象调用payMoney方法
* 内部会调用传入的InvocationHandler的invoke方法。
* 并且会把代理对象,所调用的方法,方法参数传递过去
* 如果方法有返回值,也会返回给调用者
* 如果没有,不返回,即return;
* 每个方法都是这样处理的
*/
public final void payMoney()
{
try
{
super.h.invoke(this, m3, null); return;
}
catch (Error ) { }
catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final boolean equals(Object obj)
{
try
{
return ((Boolean)super.h.invoke(this, m1, new Object[] {
obj
})).booleanValue();
}
catch (Error e) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString( )
{
try
{
return (String)super.h.invoke(this, m2, null);
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode()
{
try
{
return ((Integer)super.h.invoke(this, m0, null)).intValue();
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
}
jdk为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件是放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。通过对这个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。
我们可以对InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。
代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。
生成的代理类:$Proxy0 extends Proxy implements Person,我们看到代理类继承了Proxy类,所以也就决定了java动态代理只能对接口进行代理,Java的继承机制注定了这些动态代理类们无法实现对class的动态代理。
上面的动态代理的例子,其实就是AOP的一个简单实现了,在目标对象的方法执行之前和执行之后进行了处理,对方法耗时统计。Spring的AOP实现其实也是用了Proxy和InvocationHandler这两个东西的。
相关推荐
- jQuery VS AngularJS 你更钟爱哪个?
-
在这一次的Web开发教程中,我会尽力解答有关于jQuery和AngularJS的两个非常常见的问题,即jQuery和AngularJS之间的区别是什么?也就是说jQueryVSAngularJS?...
- Jquery实时校验,指定长度的「负小数」,小数位未满末尾补0
-
在可以输入【负小数】的输入框获取到焦点时,移除千位分隔符,在输入数据时,实时校验输入内容是否正确,失去焦点后,添加千位分隔符格式化数字。同时小数位未满时末尾补0。HTML代码...
- 如何在pbootCMS前台调用自定义表单?pbootCMS自定义调用代码示例
-
要在pbootCMS前台调用自定义表单,您需要在后台创建表单并为其添加字段,然后在前台模板文件中添加相关代码,如提交按钮和表单验证代码。您还可以自定义表单数据的存储位置、添加文件上传字段、日期选择器、...
- 编程技巧:Jquery实时验证,指定长度的「负小数」
-
为了保障【负小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【负小数】的方法。HTML代码<inputtype="text"class="forc...
- 一篇文章带你用jquery mobile设计颜色拾取器
-
【一、项目背景】现实生活中,我们经常会遇到配色的问题,这个时候去百度一下RGB表。而RGB表只提供相对于的颜色的RGB值而没有可以验证的模块。我们可以通过jquerymobile去设计颜色的拾取器...
- 编程技巧:Jquery实时验证,指定长度的「正小数」
-
为了保障【正小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【正小数】的方法。HTML做成方法<inputtype="text"class="fo...
- jquery.validate检查数组全部验证
-
问题:html中有多个name[],每个参数都要进行验证是否为空,这个时候直接用required:true话,不能全部验证,只要这个数组中有一个有值就可以通过的。解决方法使用addmethod...
- Vue进阶(幺叁肆):npm查看包版本信息
-
第一种方式npmviewjqueryversions这种方式可以查看npm服务器上所有的...
- layui中使用lay-verify进行条件校验
-
一、layui的校验很简单,主要有以下步骤:1.在form表单内加上class="layui-form"2.在提交按钮上加上lay-submit3.在想要校验的标签,加上lay-...
- jQuery是什么?如何使用? jquery是什么功能组件
-
jQuery于2006年1月由JohnResig在BarCampNYC首次发布。它目前由TimmyWilson领导,并由一组开发人员维护。jQuery是一个JavaScript库,它简化了客户...
- django框架的表单form的理解和用法-9
-
表单呈现...
- jquery对上传文件的检测判断 jquery实现文件上传
-
总体思路:在前端使用jquery对上传文件做部分初步的判断,验证通过的文件利用ajaxFileUpload上传到服务器端,并将文件的存储路径保存到数据库。<asp:FileUploadI...
- Nodejs之MEAN栈开发(四)-- form验证及图片上传
-
这一节增加推荐图书的提交和删除功能,来学习node的form提交以及node的图片上传功能。开始之前需要源码同学可以先在git上fork:https://github.com/stoneniqiu/R...
- 大数据开发基础之JAVA jquery 大数据java实战
-
上一篇我们讲解了JAVAscript的基础知识、特点及基本语法以及组成及基本用途,本期就给大家带来了JAVAweb的第二个知识点jquery,大数据开发基础之JAVAjquery,这是本篇文章的主要...
- 推荐四个开源的jQuery可视化表单设计器
-
jquery开源在线表单拖拉设计器formBuilder(推荐)jQueryformBuilder是一个开源的WEB在线html表单设计器,开发人员可以通过拖拉实现一个可视化的表单。支持表单常用控件...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)