引入代理模式

代理模式是框架中经常使用的一种模式,动态代理是AOP(面向切面编程)思想的一种重要的实现方式,在我们常用的框架中也经常遇见代理模式的身影,例如在Spring中事务管理就运用了动态代理,它将Service层原先应该进行的事务管理交给了Spring框架,大大简化了开发流程。在Hibernate中对象的懒加载模式,也运用了JDK的动态代理以及cglib代理。

静态代理

在说动态代理之前,我们需要先了解一下静态代理

静态代理通常用于对原有业务逻辑的扩充。比如持有第三方jar包中的某个类,并调用了其中的某些方法。然后出于某种原因,比如记录日志、打印方法执行时间,但是又不好将这些逻辑写入第三方jar包的方法里。所以可以创建一个代理类实现和这个类方法相同的方法,通过让代理类持有真实对象,然后在原代码中调用代理类方法,来达到添加我们需要业务逻辑的目的。

这其实也就是代理模式的一种实现,通过对真实对象的封装,来实现扩展性。

满足代理模式应用场景的三要素:

1. 两个角色:被代理者、执行者

2. 注重过程:被代理对象不想做,但是必须要做时使用代理模式

3. 代理对象:执行者必须持有被代理对象的引用

这里我们提出一个需求:我们在编写的Service类的每个方法中没有进行事务处理,但根据需要我们必须进行实物处理,那此时我们进行静态代理。

ServiceInterface接口:

public interface ServiceInterface {
void a();
void b();
}

接口的实现类:

public class ServiceImpl implements ServiceInterface{

	@Override
public void a() {
System.out.println("执行a方法");
} @Override
public void b() {
System.out.println("执行b方法");
}
}

代理类:

public class ServiceImplProxy implements ServiceInterface{
private ServiceInterface service;
public ServiceImplProxy(ServiceInterface service) {
super();
this.service = service;
} @Override
public void a() {
System.out.println("开启事务");
service.a();
System.out.println("提交事务");
} @Override
public void b() {
System.out.println("开启事务");
service.b();
System.out.println("提交事务");
}
}

测试:

public class TestProxy {
public static void main(String[] args) {
//创建目标对象
ServiceInterface service=new ServiceImpl();
//创建代理对象,将目标对象传入代理对象
ServiceInterface serviceProxy =new ServiceImplProxy(service); serviceProxy.a();
serviceProxy.b();
}
}

静态代理的优点及缺点

上面演示了静态代理的完整过程,现在我们来看看静态代理的优缺点

优点:扩展原功能,不侵入原代码。

缺点:

1. 如果我们只需要调用代理对象的某一个方法,换句话说我们只需要代理对象代理委托类的某一个方法时,我们仍然需要实现接口中所有的方法,这样显得很浪费。当然在这里我们可以通过继承来实现静态代理,使用继承时,我们只需要覆写需要代理的方法即可。

2. 如果在接口中定义了很多方法,而这些方法都需要被代理,并且代理的逻辑都是相同的,比如说在WEB开发中Service层所有方法执行之前都需要打开事务,结束后关闭事务。那么此时我们使用静态代理,会导致大量代码重复(想想如果有50个方法,你去一个一个手敲吧)。

既然静态代理这么多缺点,那JDK一定会帮我们解决这个问题,现在我们就来看看JDK自带的动态代理。

动态代理

动态代理的原理:

动态代理由程序在内存中动态生成一个对象,不需要我们手写代理对象,我们只需要指定代理方法的模板即可。

JDK自带动态代理的核心类:

java.lang.reflect.Proxy类

该类中的newInstance的静态方法,用于创建动态代理对象,该类也是所有生成的动态代理对象的父类。

    方法:

      Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

  参数:

      ClassLoader:传入被代理类的类加载器,实际上只需要传入类加载器即可,不一定必须传入“被代理类”的类加载器,也                              可以传入别的实例的类加载器,因为java中类加载器是一个对象。这个类加载器用于将生成的字节码文件加载进方法区,                             并生成字节码对象。

Class<?>[] interfaces:这个参数是指定代理对象实现的接口,可以实现多个接口,这些接口中的所有方法都会按照invoke模板                                                        中的代码进行加强。

InvocationHandler h:传入InvocationHandler接口的实现类,该类中的invoke方法是代理对象中所有方法的逻辑处理模板。

java.lang.reflect.InvocationHandler接口:

该接口有一个invoke方法,我们需要实现invoke方法,这个方法就是代理方法的模板。

  接口中需要实现的方法:

       invoke(Object proxy, Method method, Object[] args)

  参数:

      Object proxy:代表代理对象本身,可以它调用代理对象的其他方法

      Method method:代表“被代理对象”对应方法的字节码对象

Object[] args:传入“代理对象”对应方法的参数数组

动态代理的示例代码:

编写InvocationHandler实现类

public class ServiceInvocationHandler implements InvocationHandler {
private Object instance;//这是目标对象(被代理对象) public Object getInstance() {
return instance;
} public void setInstance(Object instance) {
this.instance = instance;
} /***
* proxy:代表代理对象本身,可以通过它调用代理对象的其他方法
* method:代表目标对象对应方法的字节码对象
* args:代表目标对象相应的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开启事务");
method.invoke(instance,args);
System.out.println("提交事务");
return null;
}
}

通过Proxy类创建动态代理

public class TestMyProxy {
public static void main(String[] args) throws Exception{
//创建目标对象
ServiceInterface sale = new ServiceImpl();
//创建模板对象
ServiceInvocationHandler invocationHandler = new ServiceInvocationHandler();
//将目标对象注入模板对象中
invocationHandler.setInstance(sale); Class serviceClazz = sale.getClass();
ServiceInterface proxy = (ServiceInterface) Proxy.newProxyInstance(serviceClazz.getClassLoader(), serviceClazz.getInterfaces(), invocationHandler); proxy.a();
proxy.b();
}
}

通过测试,动态代理对象与前面的静态代理结果相同

生成代理对象的字节码文件

看到这里肯定很多人都会另一头雾水,动态代理底层到底是怎么实现的,我们想办法通过程序将代理对象字节码文件输出到磁盘上,然后通过jdgui反编译工具,查看动态代理对象的源码结构。

public class TestMyProxy {
public static void main(String[] args) throws Exception{
byte[] buffer = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{ServiceInterface.class});
try {
FileOutputStream output = new FileOutputStream("C:/Users/Lenovo/Desktop/$Proxy0.class");
output.write(buffer);
} catch (Exception e) {
e.printStackTrace();
}
}
}

注:使用ProxyGenerator类需要将JRE中lib目录下的一个jar包导入到项目中

通过反编译工具可以看到代理对象源码结构如下(下列代码对源码进行了部分删减,如果有需要可以自行生成源代码查看):

public final class $Proxy0
extends Proxy
implements ServiceInterface
{
private static Method m1;
private static Method m4;
private static Method m3;
private static Method m2;
private static Method m0; public $Proxy0(InvocationHandler paramInvocationHandler)
{
super(paramInvocationHandler);
} public final void b()
{
try
{
this.h.invoke(this, m4, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
} public final void a()
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
} static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m4 = Class.forName("com.proxy.ServiceInterface").getMethod("b", new Class[0]);
m3 = Class.forName("com.proxy.ServiceInterface").getMethod("a", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}

我们分析源码可知,代理对象中的代理方法,都执行了this.h.invoke()方法,this.h实际上是代理类的父类中(Proxy类)中的属性,它保存了在调用Proxy.newInstance()方法时传入的InvocationHandler实例,而InvocationHandler中的invoke方法就是,代理类的代理逻辑。

也就是说在生成的一个动态代理对象中,对目标对象中所有方法都进行了相同的前处理和后处理过程,因为都执行相同的invoke“模板”方法,也就是说如果我们想对目标对象中每个方法进行不同的前处理和后处理,我们需要编写不同的InvocationHandler实现类,然后创建不同的动态代理对象才能实现这一需求。

动态代理思想的总结:

动态代理实际上就是在内存中直接生成字节码文件(通常程序员写的java文件编译后生成的字节码文件都在硬盘中),然后将字节码加载进方法区生成字节码对象,生成字节码对象后通过反射就可以创建代理对象的实例了。生成字节码文件就必定需要生成这个代理类的方法,那程序是怎么知道这个代理对象需要实现哪些方法呢,这就是为什么在Proxy.newProxyInstance()需要传入接口的数组,传入几个接口,这个代理就会实现这些接口,程序自然就知道它需要实现哪些方法了。此时就会开始循环生成代理类中的方法,那这个方法的具体实现代码又是什么呢?由于生成的代理类继承了Proxy类,在Proxy类中有一个h字段,保存的是一个InvocationHandler实现类的对象,而我们通过Proxy.newInstance()方法时,传入了一个InvocationHandler实例,所以在代理对象中存放着一个InvocationHandler对象,代理对象的每个方法都会调用父类中存放的InvocationHandler实例中的invoke方法。

走过路过不要错过,原创不易,帮忙点个赞撒!!!!

JDK动态代理的深入理解的更多相关文章

  1. JDK 动态代理的简单理解

    动态代理 代理模式是 Java 中的常用设计模式,代理类通过调用被代理类的相关方法,提供预处理.过滤.事后处理等服务,动态代理及通过反射机制动态实现代理机制.JDK 中的 java.lang.refl ...

  2. JDK动态代理的简单理解

    转载:http://www.cnblogs.com/luotaoyeah/p/3778183.html 动态代理 代理模式是 Java 中的常用设计模式,代理类通过调用被代理类的相关方法,提供预处理. ...

  3. jdk动态代理:由浅入深理解mybatis底层

    什么是代理 代理模式,目的就是为其他对象提供一个代理以控制对某个对象的访问,代理类为被代理者处理过滤消息,说白了就是对被代理者的方法进行增强. 看到这里,有没有感觉很熟悉?AOP,我们熟知的面向切面编 ...

  4. JDK动态代理深入理解分析并手写简易JDK动态代理(下)

    原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-05/27.html 作者:夜月归途 出处:http://www.guitu ...

  5. JDK动态代理深入理解分析并手写简易JDK动态代理(上)

    原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-03/27.html 作者:夜月归途 出处:http://www.guitu ...

  6. 从Mybatis源码理解jdk动态代理默认调用invoke方法

    一.背景最近在工作之余,把开mybatis的源码看了下,决定自己手写个简单版的.实现核心的功能即可.写完之后,执行了一下,正巧在mybatis对Mapper接口的动态代理这个核心代码这边发现一个问题. ...

  7. 举例理解JDK动态代理

    JDK动态代理 说到java自带的动态代理api,肯定离不开反射.JDK的Proxy类实现动态代理最核心的方法: public static Object newProxyInstance(Class ...

  8. JDK动态代理

    一.基本概念 1.什么是代理? 在阐述JDK动态代理之前,我们很有必要先来弄明白代理的概念.代理这个词本身并不是计算机专用术语,它是生活中一个常用的概念.这里引用维基百科上的一句话对代理进行定义: A ...

  9. 静态代理和利用反射形成的动态代理(JDK动态代理)

    代理模式 代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 静态代理 1.新建 ...

  10. JDK动态代理的实现原理

    学习JDK动态代理,从源码层次来理解其实现原理参考:http://blog.csdn.net/jiankunking/article/details/52143504

随机推荐

  1. HarmonyOS振动效果开发指导

      Vibrator开发概述 振动器模块服务最大化开放硬工最新马达器件能力,通过拓展原生马达服务实现振动与交互融合设计,打造细腻精致的一体化振动体验和差异化体验,提升用户交互效率和易用性.提升用户体验 ...

  2. Windows Server 2008 R2修复永恒之蓝漏洞

    一.情况描述 服务器安装的Windows Server 2008 R2 standard系统,通过扫描发现系统存在永恒之蓝漏洞MS17-010(CVE-2017-0143.CVE-2017-0144. ...

  3. Java构建工具:Maven与Gradle的对比

    在Java码农的世界里,构建工具一直是一个不可或缺的元素.一开始,世上是只有一个构建工具的那就是Make后来发展为GNU Make.但是由于需求的不断涌现,这个小圈子里又逐渐衍生出其他千奇百怪的构建工 ...

  4. CentOS-6.5快速搭建HTTP服务器和仅供授权用户登陆的FTP服务器

    CentOS-6.5快速搭建HTTP服务器和仅供授权用户登陆的FTP服务器 (2014-01-09 21:29:31) 转载▼ 标签: linux centos 服务器 http vsftp 分类:& ...

  5. c# vs 中如何修改类模板

    背景 在一些应用中,我们需要去修改我们的类模板,作为标记. 步骤 在这个目录下就是我们的模板: C:\Program Files\Microsoft Visual Studio 10.0\Common ...

  6. D365调试进入系统类方法

    在生成InventDIMID时,虽然设置了InventDIMID的Number sequence为自定义的Format,但是总是不按指定的Number sequence产生InventDIMID Di ...

  7. 力扣578(MySQL)-查询回答率最高的问题(中等)

    题目: 从 survey_log 表中获得回答率最高的问题,survey_log 表包含这些列:id, action, question_id, answer_id, q_num, timestamp ...

  8. 【pytorch学习】之自动微分

    5 自动微分 求导是几乎所有深度学习优化算法的关键步骤.虽然求导的计算很简单,只需要一些基本的微积分.但对于复杂的模型,手工进行更新是一件很痛苦的事情(而且经常容易出错).深度学习框架通过自动计算导数 ...

  9. Serverless JOB | 传统任务新变革

    简介: SAE Job 重点解决了用户的效率和成本问题,在兼具传统任务使用体验和功能的同时按需使用,按量计费,做到低门槛任务上云,节省闲置资源成本. Job 作为一种运完即停的负载类型,在企业级开发中 ...

  10. Quick BI:降低使用门槛,大东鞋业8000家门店的数据导航

    简介: 通过引入MaxCompute和Quick BI,大东解决了以往数据查询即刻导致数据库闪崩的状况,还搭建起完善的报表体系,稳定应对高频.高并发的数据分析. 大东鞋业一季大约有500款的新品.大区 ...