Java静态代理与动态代理基础
在学习一些重要框架之前,有一个很重要的知识点必须了解,了解后对于Spring AOP和集成Mybatis原理会非常容易理解,这个前提技术就是代理。代理可以分为静态代理和动态代理,本篇会简单举例静态代理以及分享动态代理的方式(只以JDK自带动态代理和CGLib为例,CGLib另外一篇文章学习),同时通过demo来认识静态代理和动态代理的优缺点。
在学习之前先了解什么是代理(代理模式)?代理是指通过一个代理对象,将需要被代理的对象(我们称之为目标对象)包装起来,然后用代理对象来替代目标对象执行各种代理方法(这些代理方法可以间接调用目标对象的方法),也可以决定是否执行,怎么执行目标对象方法以及何时调用目标对象方法。用一个生活中的例子举例说明:
以上例子相当于把各个具体的目标对象,通过一个类似统一的代理对象进行代理,对于外部(车站)来说只有一个代理者(代售点)与之交互,实际上交互的事件(也就是代理方法)都仅限于代理者对外的代理事件,而这些交互的代理事件内部本身可以有被代理者的事件(买票),也可以根据需要自行穿插其它并不属于被代理者的事件,也可以忽视不执行被代理者的事件等等,这种模式就称为代理模式。代理模式同时又分为静态代理和动态代理。
一.静态代理
什么是静态代理?
静态代理可以简单理解为代理类通过包装被代理类,对被代理类相关方法进行增强,在增强方法中可以加入一些非业务性代码,比如事务、日志等操作,常见的静态代理实现方式有集成和聚合。静态代理是在编译期就已经确定代理类和目标类的关系并生成了代理类。下面以一个简单的demo为例,简单创建两个目标类(被代理者TicketDao和UserDao)和两个代理类(StaticProxy1和StaticProxy2),分别采用继承和聚合的方式实现静态代理。
继承方式:提供代理方法调用被代理者方法以及添加自己的逻辑用于增强方法。
聚合方式:提供代理方法调用被代理者方法以及添加自己的逻辑用于增强方法。
以上两种方式实现简单,但是也带来一个问题。那就是当代理逻辑复杂多变时,例如在getName代理方法中还要添加其他逻辑,那么直接修改已有的方法内逻辑是不合适且有风险的,因为有多个不同地方调用时统一修改原有代理方法内的逻辑,可能会导致所有使用代理方法的地方业务都被影响。因此需要不断新增新的代理类文件再通过集成或聚合的方式继续增强代理方法,这就使得后续维护成本变得很大。那么有没有一种方式,可以不用一直新增代理类文件,就可以为所有的目标对象进行代理呢?那么动态代理便产生了。
二.动态代理
什么是动态代理?动态代理是在运行期利用JVM的反射机制生成代理类,过程是动态构建生成类字节码,再由类加载器载入JVM中执行生成代理类对象。动态代理的实现方式有很多如JDK,CGLIB,AspectJ(修改目标类的字节码植入代码字节,编译期时会改变源class文件并没有生成新的文件)等,目前主要介绍两种:一种是JDK自带的动态代理(代理接口),另一种是CGLIB(代理类),这两种是构建生成一个全新字节码文件的方式,本篇将先从JDK动态代理的实现学习其代理过程,并尝试通过代码自己模拟实现这个过程。
常见工具或框架操作字节码所用的技术如上图
动态代理有什么常见应用场景:
a.日志集中打印
b.事务
c.权限控制
d.AOP,RPC调用使用代理来屏蔽底层协议等
(1)JDK动态代理
由描述便知,JDK动态代理是对接口的代理,也就是基于一组定义好的接口,对这些接口的实现进行代理,如下:
其中Proxy就是JDK自带的代理工具类,Proxy通过传入的Interface接口产生出这个接口对应的代理对象$Proxy0(源码中定义类名的生成规则是前缀"$Proxy"加上一个序列数),既然这个代理对象$Proxy0实现了接口但是要怎么实现这些接口的方法逻辑呢,InvocationHandler接口的作用就在这里。$Proxy0对外只提供一个构造函数,这个构造函数接受一个InvocationHandler实例h,在$Proxy0构造函数里面将参数h直接传入父类的构造函数执行,最终把所有的方法实现都分派到InvocationHandler实例的invoke方法上。因此JDK动态代理的接口方法实现逻辑是由InvocationHandler实例的invoke方法决定的。
JDK动态代理的一般实现步骤如下:
(1)创建一个实现InvocationHandler接口的类,它必须实现invoke方法
(2)创建被代理的类以及接口
(3)调用Proxy的静态方法newProxyInstance,创建一个代理类
(4)通过代理调用方法
还是使用上面的目标类UserDao,为这个目标类创建一个代理类,通过一段简单的demo代码:
package com.proxy;
import com.proxy.dao.UserDao;
import com.proxy.proxyutil.IProxyService;
import com.proxy.proxyutil.MyInvocationHandler;
import sun.misc.ProxyGenerator;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
public class TestProxy {
public static void main(String[] args) {
//1.使用JDK自带的动态代理
//System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
/**
* Returns an instance of a proxy class for the specified interfaces
* @param loader 代理类的类加载器,可与传入的目标类相同加载器
* @param interfaces 目标类的接口接口
* @param InvocationHandler 代理类转入到目标类的方法,放参数要传入目标类
* @return a proxy instance with the specified invocation handler of a
*/
IProxyService UserDaoProxy = (IProxyService) Proxy.newProxyInstance(
IProxyService.class.getClassLoader(),
new Class[]{IProxyService.class}, new MyInvocationHandler(new UserD