引入代理模式

代理模式是框架中经常使用的一种模式,动态代理是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音视频开发概述

      在音视频开发指导中,将介绍各种涉及音频.视频播放或录制功能场景的开发方式,指导开发者如何使用系统提供的音视频API实现对应功能.比如使用TonePlayer实现简单的提示音,当设备接收到新消息时, ...

  2. Windows Server 2008 R2之升级IE8

    前言 先需求将Windows Server 2008 R2的IE8升级至IE9,需要安装系统补丁. 安装补丁 补丁包版本 KB2454826 下载地址 https://www.catalog.upda ...

  3. Excel 分组后计算

    分组后的计算都类似,仍然采用 groups 函数,分组并同时计算出各洲的 GDP 总量,然后再求一遍各洲的 GDP 总量占全球 GDP 的百分比值. SPL 代码如下:   A B 1 =clipbo ...

  4. IIS applicationHost.config 查找历史

    背景 iis 有时候需要修改配置,一般来说,我们会去修改applicationHost.config配置,当然,很多时候我们都需要去备份一个配置文件,但是可能忘记了,那么是否有补救的方式? 补救方式 ...

  5. MMDeploy部署实战系列【第三章】:MMdeploy pytorch模型转换onnx,tensorrt

    MMDeploy部署实战系列[第三章]:MMdeploy pytorch模型转换onnx,tensorrt 这个系列是一个随笔,是我走过的一些路,有些地方可能不太完善.如果有那个地方没看懂,评论区问就 ...

  6. springboot多模块项目启动经历

    springboot多模块使用 @ 目录 springboot多模块使用 前言 大佬把项目权限给我了,我就先下下来看看学习一下 一.识别 二.maven配置 1.安装maven 三.加载刷新 总结 前 ...

  7. .Net core 3.0 SignalR+Vue 实现简单的IM(无jq依赖)

    .Net core 中的SignalR JavaScript客户端已经不需要依赖Jquery了 一.服务端 1.nuget安装 Microsoft.AspNetCore.SignalR2.在start ...

  8. Oracle SQL 创建一个简单的存储过程procedure

    Oracle 简单的创建一个存储过程procedure 如果学过别的语言,例如java,c这些,那么其实很好理解,其实就是面向数据库的操作 简单的例子如下: --创建或者重写存储过程 create o ...

  9. HarmonyOS NEXT应用开发——Navigation开发 页面切换场景范例

    简介 在应用开发时,我们常常遇到,需要在应用内多页面跳转场景时中使用Navigation导航组件做统一的页面跳转管理,它提供了一系列属性方法来设置页面的标题栏.工具栏以及菜单栏的各种展示样式.除此之外 ...

  10. 3 种发布策略,解决 K8s 中快速交付应用的难题

    作者 | 郝树伟(流生)阿里云高级研发工程师 前言 软件技术更新换代很快,但我们追求的目标是一直不变的,那就是在安全稳定的前提下,增加应用的部署频率,缩短产品功能的迭代周期,这样的好处就是企业可以在更 ...