10分钟让你彻底了解Java动态代理的实现与原理

目录:

1、什么是代理?

2、静态代理的实现方式

3、什么是动态代理?动态代理的实现方式?

4、为什么JDK动态代理必须需要实现一个接口?

5、怎样模仿JDK动态代理实现自己的动态代理?

6、动态代理有哪些使用场景?

7、总结

什么是代理模式?

定义:为其他对象提供一种代理以控制对这个对象的访问。

那我们形象化的说明一下:

比如我们找女朋友,自己身边没有资源,那么就会找婚介所、找朋友介绍、找媒婆、参加相亲大会。

代理模式的结构图如下:

结构说明:

(1) Proxy: 代理对象

实现与具体的目标对象一样的接口,这样就可以使用代理来代替具体的目标对象。保存一个指向具体目标对象的引用,可以在需要的时候调用具体的目标对象。可以控制对具体目标对象的访问,并可以负责创建和删除它。

(2) Subject: 目标接口

定义代理和具体目标对象的接口,这样就可以在任何使用具体目标对象的地方使用代理对象。

(3) RealSubject

具体的目标对象,真正实现目标接口要求的功能。

静态代理的实现方式

(1) 目标接口的定义

/** * 代理对象 */ public class Proxy implements Subject{ /** * 持有被 代理的具体的目标对象 */ private Subject realSubject = null; public Proxy(RealSubject realSubject){ this.realSubject = realSubject; } /** * */ @Override public void request( ) { //在 转调具体的目标对象前,可以执行一些功能处理 //转调具体的目标对象的方法 realSubject.request(); // 在 转调 具体的目标对象后,可以执行一些功能处理 } }

(2) 具体目标的实现

/** * 代理对象 */ public class Proxy implements Subject{ /** * 持有被 代理的具体的目标对象 */ private Subject realSubject = null; public Proxy(RealSubject realSubject){ this.realSubject = realSubject; } /** * */ @Override public void request( ) { //在 转调具体的目标对象前,可以执行一些功能处理 //转调具体的目标对象的方法 realSubject.request(); // 在 转调 具体的目标对象后,可以执行一些功能处理 } }

(3)代理类的实现

/** * 代理对象 */ public class Proxy implements Subject{ /** * 持有被 代理的具体的目标对象 */ private Subject realSubject = null; public Proxy(RealSubject realSubject){ this.realSubject = realSubject; } /** * */ @Override public void request( ) { //在 转调具体的目标对象前,可以执行一些功能处理 //转调具体的目标对象的方法 realSubject.request(); // 在 转调 具体的目标对象后,可以执行一些功能处理 } }

什么是java动态代理?动态代理的实现方式?

在引出动态代理之前,我们先说一下静态代理的缺点?

(1) 静态代理的时候,如果在接口上定义了很多方法,代理类里面也要写很多方法;而动态代理实现的时候,虽然接口上定义了很多方法,但是动态代理类始终只有一个invoke方法。这样,当需要代理的接口发生变化的时候,动态代理的接口发生变化的时候,动态代理的接口就不需要跟着变化了。

(2) 在静态代理中,我们需要在代理类中,将原始类的所有的方法,都重新实现一遍,并且为每个方法都附加相似的代码逻辑。如果要添加的附加功能的类不止一个,我们需要针对每个类都创建一个代理类。如果有100类要添加附加的功能的原始类,我们就要创建100个对应的代理类。这将导致项目中的类迅速增多,大大增加了维护的成本。并且,每个代理类中的代码都有很多重复的。也增加了不必要的开发成本。那么这个问题怎么解决呢?

我们可以使用动态代理来解决这个问题。那么什么动态代理呢?

动态代理就是我们不事先为每个需要代理的来写代理类,而是在运行的时候,动态地创建对应的代理类,然后在系统中用代理类替换被代理的类。那么如何实现呢?

1、Java动态代理

Java 对代理模式提供了内建的支持,在java.lang.reflect包下面,提供了一个Proxy类和一个InvocationHandler的接口。

动态代理的实现步骤如下:

(1) 要实现InvocationHandler接口。

(2) 需要提供一个方法来实现:把具体的目标对象和动态代理绑定起来,并在绑定好后,返回被代理的目标对象的接口,以利于客户端的操作。

(3) 需要实现invoke方法。在这个方法中,具体判断当前是在调用什么方法,需要如何处理?

示例代码如下:

public class UserInvocationHandlerimplements InvocationHandler{ /** * * 被代理的对象 */ private Object target; public Object newInstance(Object target) throws Exception{ this.target = target; Class<?> clazz = target.getClass(); return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { System.out.println("开始事务...."); method.invoke(this.target,args); }catch (Exception e){ System.out.println("回滚事务...."); }finally { System.out.println("提交事务...."); } returnnull; } }

客户端如何使用这个动态代理呢。代码如下:

IUserService userService = new UserServiceImpl(); IUserService userServiceProxy = (IUserService) Proxy. newProxyInstance(userService.getClass().getClassLoader(),userService.getClass().getInterfaces(), new UserInvocationHandler()); userServiceProxy.save();

SpringAOP 底层的实现原理就是基于动态代理的。首先用户需要配置哪些类需要代理?哪些方法需要代理,并定义好在业务代码前后的附加功能。SpringAOP 实现方式是在Bean初始化完成后,执行动态代理,然后把动态代理类写入Map中,比如有一个UserService,Spring底层是这样存储的的

Map<String,Object> map = new ConcurentHashMap<>();

map.put("userService",userService);

当执行动态代理后,就变成了下面的形式

map.put("userService",userServiceProxy);

这样代码中每次获取UserService都是代理类.

2、CGLIB 动态代理

Java动态代理的类必须实现接口,有很强的局限性,而CGLIB 为目标对象转件代理对象时,目标对象可以不实现接口。在CGLIB底层,其实是使用ASM这个非常强大的Java字节码生成框架。

那么怎样使用呢?

public class UserServiceCglib implements MethodInterceptor{ public Object getInstance(Class<?> clazz) throwsException{ Enhancer enhancer = new Enhancer(); //要把哪个设置为即将生成的新类父类 enhancer.setSuperclass(clazz); enhancer.setCallback(this); returnenhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //业务的增强 try{ System.out.println("开始事务"); methodProxy.invokeSuper(o,objects); }catch(Exception e){ System.out.println("回滚事务 "); }finally{ System.out.println("提交事务 "); } return null; } }

最后我们对比一下JDK动态代理和CGLIB代理

(1) JDK代理要求被代理的类必须实现接口。

(2) CGLIB没有强制被代理的类必须试下接口,可以实现,也可以不实现。

为什么JDK动态代理必须需要实现一个接口?

我们生成的代理类来分析JDK动态代理为什么必须实现一个接口。

怎么查看呢?可以使用下面的代码生成代理类

IUserService userService = new UserServiceImpl(); byte[] proxyClassFile = ProxyGenerator.generateProxyClass("proy0", new Class[] { userService.getClass() }); Path path = new File(System.getProperty("user.dir") + "/target/proxy0.class").toPath(); Files.write(path, proxyClassFile);

生成的类如下:

public final class proy0 extends Proxy implements IUserService { private static Method m3; public proy0(InvocationHandler var1) throws{ super(var1); } public final void save() throws{ try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } }

从上面代码我们分析一下生成的原理

生成的代理类继承了Proxy类,实现了IUserService接口,因为Java是单继承的,所以不能再继承其他类,那么怎么获取代理类中的方法 生成代理类,必须实现需要代理的接口。

怎样模仿JDK动态代理实现自己的动态代理?

下面我们模仿JDK动态代理,实现自己的一个代理。有以下几个步骤:

1、实现一个 YJInvocationHandler 接口

public interface YJInvocationHandler { public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable; }

2、自定义类加载器

怎样自定义类加载器呢?我们需要自定义类加载器,然后从本地盘中加载生成的class文件。

/** * 自定义类加载器 */ public class YJClassLoader extends ClassLoader{ private File classPathFile; public YJClassLoader(){ String classPath = YJClassLoader.class.getResource("").getPath(); this.classPathFile = new File(classPath); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String className = YJClassLoader.class.getPackage().getName() + "." + name; if(classPathFile != null){ File classFile = new File(classPathFile,name.replaceAll("\\.","/") + ".class"); if(classFile.exists()){ FileInputStream in = null; ByteArrayOutputStream out = null; try{ in = new FileInputStream(classFile); out = new ByteArrayOutputStream(); byte [] buff = new byte[1024]; int len; while ((len = in.read(buff)) != -1){ out.write(buff,0,len); } returndefineClass(className,out.toByteArray(),0,out.size()); }catch (Exception e){ e.printStackTrace(); }finally { if(null != in){ try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if(out != null){ try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } } return null; }

ClassLoader 类有以下两个关键方法:

loadClass(String name, boolean resolve):该方法为ClassLoader的入口点,根据指定的二进制名称来加载类,系统就是调用ClassLoader的该方法来获取指定类对应的Class对象。findClass(String name):根据二进制名称来查找类。

如果需要实现自定义的ClassLoader,可以通过重写以上两个方法来实现,建议使用findClass()方法,而不是重写loadClass()方法。

3、YjProxy 代理类,和JDK中的Proxy类似

怎样实现YjProxy代理类呢?

(1) 动态生成源代码.java文件

实现一个代理类,实现需要代理的接口,通过构造方法把YJInvocationHandler实现类传进去,然后循环接口的每个方法,然后调用YJInvocationHandler实现类中invoke方法。最终生成一个Java文件。

public class UserInvocationHandlerimplements InvocationHandler{ /** * * 被代理的对象 */ private Object target; public Object newInstance(Object target) throws Exception{ this.target = target; Class<?> clazz = target.getClass(); return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { System.out.println("开始事务...."); method.invoke(this.target,args); }catch (Exception e){ System.out.println("回滚事务...."); }finally { System.out.println("提交事务...."); } returnnull; } }

(2) 将Java文件输出磁盘

(3) 把生成的.java文件编译成.class文件

(4) 使用我们的自定义类加载器,把生成的 class文件加载到JVM中。

IUserService userService = new UserServiceImpl(); IUserService userServiceProxy = (IUserService) Proxy. newProxyInstance(userService.getClass().getClassLoader(),userService.getClass().getInterfaces(), new UserInvocationHandler()); userServiceProxy.save();

代理类实现完成后,我们要写一个测试类来测试一下,我们使用保存用户的事务来演示:

用户服务接口如下:

public interface IUserService { public void save(); }

实现类:

public class UserServiceImpl implements IUserService { @Overridepublic void save( ) { System.out.println("保存用户................"); } }

代理类:

public class UserServiceProxy implements YJInvocationHandler { /** * * 被代理的对象 */ private Object target; public Object newInstance(Object target) throws Exception{ this.target = target; Class<?> clazz = target.getClass(); return YjProxy.newProxyInstance(new YJClassLoader(),clazz.getInterfaces(),this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { System.out.println("开始事务...."); method.invoke(this.target,args); }catch (Exception e){ System.out.println("回滚事务...."); }finally { System.out.println("提交事务...."); } returnnull; } }

测试类:

public static void main(String[] args) throws Exception { IUserService userService = new UserServiceImpl(); IUserService proxyUserService = (IUserService) newUserServiceProxy().newInstance(userService); proxyUserService.save(); }

测试结果:

那么有还没有其他的实现方式呢?答:有

可以使用javassist技术,Javassist是一个开源的分析、编辑和创建Java字节码的类库。其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成。

Javassist有什么作用呢?

a. 运行时监控插桩埋点

b. AOP动态代理实现(性能上比Cglib生成的要慢)

c. 获取访问类结构信息:如获取参数名称信息

public class UserServiceCglib implements MethodInterceptor{ public Object getInstance(Class<?> clazz) throwsException{ Enhancer enhancer = new Enhancer(); //要把哪个设置为即将生成的新类父类 enhancer.setSuperclass(clazz); enhancer.setCallback(this); returnenhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //业务的增强 try{ System.out.println("开始事务"); methodProxy.invokeSuper(o,objects); }catch(Exception e){ System.out.println("回滚事务 "); }finally{ System.out.println("提交事务 "); } return null; } }

动态代理有哪些使用场景?

代理模式在我们平时开发的应用系统中场景还是很多的。不是让你拿着代理模式去死搬硬套的去使用,要学会举一反三,Java中的设计模式应用到项目中或者框架中,都是经过变种的,或者是几个设计模式融合在一起的。

1、业务系统的非功能性需求开发

我们开发应用时,经常会涉及到一些非业务需求,比如:监控、统计、鉴权、限流、事务、日志等,以前我们处理可能是在每个模块中单独写,自从有了动态代理,我们可以将这些附加功能和业务功能解耦,放到动态代理中统一处理,让程序员只关注业务的开发。

比如Spring AOP , 就是在切面中实现日志的记录和事务的处理。

2、动态代理在MyBatis中的使用

大家使用Mybatis的时候,很多都是使用分页框架 pagehelper,有了pagehelper我们再也不用关注分页的查询了,那么你知道pagehelper的原理吗?

pagehelper是基于Mybatis 插件实现的。完全可以实现无侵入实现sql的分页 ,其底部通过ThreadLocal来存放分页信息。通过动态代理拦截org.apache.ibatis.executor.Executor的query方法,然后拦截你发送的SQL。根据不同的数据库来生成不同的分页sql。

3、代理模式在 RPC、缓存中的应用

实际上,RPC也是一种代理模式,相当于远程代理,用来在不同的地址空间上代表同一个对象,这个不同的地址空间可以在本机,也可以在其他机器上。在Java里最典型的就是RMI技术。RPC 包含了服务发现、负载、容错、网络传输、序列化等组件,其中RPC协议就指明了程序如何进行网络传输和序列化。

通过远程代理,将通信、编解码 和序列化 等细节隐藏 起来,使用过dubbo 和 Spring Cloud的都知道,我们调用远程服务和调用本地服务一样,不需要了解通信细节、编解码、序列化等繁琐的细节。