微信原文:Java 动态代理详解
博客原文:Java 动态代理详解php
动态代理在Java中有着普遍的应用,好比Spring AOP、Hibernate数据查询、测试框架的后端mock、RPC远程调用、Java注解对象获取、日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等。html
本文主要介绍Java中两种常见的动态代理方式:JDK原生动态代理和CGLIB动态代理。java
因为Java动态代理与java反射机制关系紧密,请读者确保已经了解了Java反射机制,可参考上一篇文章《Java反射机制详解》面试
本文将介绍的Java动态代理与设计模式中的代理模式有关,什么是代理模式呢?数据库
代理模式:给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。代理模式是一种结构型设计模式。segmentfault
代理模式角色分为 3 种:后端
Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;设计模式
RealSubject(真实主题角色):真正实现业务逻辑的类;api
Proxy(代理主题角色):用来代理和封装真实主题;数组
代理模式的结构比较简单,其核心是代理类,为了让客户端可以一致性地对待真实对象和代理对象,在代理模式中引入了抽象层
代理模式按照职责(使用场景)来分类,至少能够分为如下几类:一、远程代理。 二、虚拟代理。 三、Copy-on-Write 代理。 四、保护(Protect or Access)代理。 五、Cache代理。 六、防火墙(Firewall)代理。 七、同步化(Synchronization)代理。 八、智能引用(Smart Reference)代理等等。
若是根据字节码的建立时机来分类,能够分为静态代理和动态代理:
咱们先经过实例来学习静态代理,而后理解静态代理的缺点,再来学习本文的主角:动态代理
编写一个接口 UserService ,以及该接口的一个实现类 UserServiceImpl
public interface UserService {
public void select();
public void update();
}
public class UserServiceImpl implements UserService {
public void select() {
System.out.println("查询 selectById");
}
public void update() {
System.out.println("更新 update");
}
}
复制代码
咱们将经过静态代理对 UserServiceImpl 进行功能加强,在调用 select
和 update
以前记录一些日志。写一个代理类 UserServiceProxy,代理类须要实现 UserService
public class UserServiceProxy implements UserService {
private UserService target; // 被代理的对象
public UserServiceProxy(UserService target) {
this.target = target;
}
public void select() {
before();
target.select(); // 这里才实际调用真实主题角色的方法
after();
}
public void update() {
before();
target.update(); // 这里才实际调用真实主题角色的方法
after();
}
private void before() { // 在执行方法以前执行
System.out.println(String.format("log start time [%s] ", new Date()));
}
private void after() { // 在执行方法以后执行
System.out.println(String.format("log end time [%s] ", new Date()));
}
}
复制代码
客户端测试
public class Client1 {
public static void main(String[] args) {
UserService userServiceImpl = new UserServiceImpl();
UserService proxy = new UserServiceProxy(userServiceImpl);
proxy.select();
proxy.update();
}
}
复制代码
输出
log start time [Thu Dec 20 14:13:25 CST 2018]
查询 selectById
log end time [Thu Dec 20 14:13:25 CST 2018]
log start time [Thu Dec 20 14:13:25 CST 2018]
更新 update
log end time [Thu Dec 20 14:13:25 CST 2018]
复制代码
经过静态代理,咱们达到了功能加强的目的,并且没有侵入原代码,这是静态代理的一个优势。
虽然静态代理实现简单,且不侵入原代码,可是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。
一、 当须要代理多个类的时候,因为代理对象要实现与目标对象一致的接口,有两种方式:
二、 当接口须要增长、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。
固然是让代理类动态的生成啦,也就是动态代理。
为何类能够动态的生成?
这就涉及到Java虚拟机的类加载机制了,推荐翻看《深刻理解Java虚拟机》7.3节 类加载的过程。
Java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段须要完成如下3件事情:
java.lang.Class
对象,做为方法区这个类的各类数据访问入口因为虚拟机规范对这3点要求并不具体,因此实际的实现是很是灵活的,关于第1点,获取类的二进制字节流(class字节码)就有不少途径:
*$Proxy
的代理类的二进制字节流因此,动态代理就是想办法,根据接口或目标对象,计算出代理类的字节码,而后再加载到JVM中使用。可是如何计算?如何生成?状况也许比想象的复杂得多,咱们须要借助现有的方案。
这里有一些介绍:java-source.net/open-source…
为了让生成的代理类与目标对象(真实主题角色)保持一致性,从如今开始将介绍如下两种最多见的方式:
注:使用ASM对使用者要求比较高,使用Javassist会比较麻烦
JDK动态代理主要涉及两个类:java.lang.reflect.Proxy
和 java.lang.reflect.InvocationHandler
,咱们仍然经过案例来学习
编写一个调用逻辑处理器 LogHandler 类,提供日志加强功能,并实现 InvocationHandler 接口;在 LogHandler 中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在 invoke
方法中编写方法调用的逻辑处理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;
public class LogHandler implements InvocationHandler {
Object target; // 被代理的对象,实际的方法执行者
public LogHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args); // 调用 target 的 method 方法
after();
return result; // 返回方法的执行结果
}
// 调用invoke方法以前执行
private void before() {
System.out.println(String.format("log start time [%s] ", new Date()));
}
// 调用invoke方法以后执行
private void after() {
System.out.println(String.format("log end time [%s] ", new Date()));
}
}
复制代码
编写客户端,获取动态生成的代理类的对象须借助 Proxy 类的 newProxyInstance 方法,具体步骤可见代码和注释
import proxy.UserService;
import proxy.UserServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Client2 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
// 设置变量能够保存动态代理类,默认名称以 $Proxy0 格式命名
// System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 1. 建立被代理的对象,UserService接口的实现类
UserServiceImpl userServiceImpl = new UserServiceImpl();
// 2. 获取对应的 ClassLoader
ClassLoader classLoader = userServiceImpl.getClass().getClassLoader();
// 3. 获取全部接口的Class,这里的UserServiceImpl只实现了一个接口UserService,
Class[] interfaces = userServiceImpl.getClass().getInterfaces();
// 4. 建立一个将传给代理类的调用请求处理器,处理全部的代理对象上的方法调用
// 这里建立的是一个自定义的日志处理器,须传入实际的执行对象 userServiceImpl
InvocationHandler logHandler = new LogHandler(userServiceImpl);
/* 5.根据上面提供的信息,建立代理对象 在这个过程当中, a.JDK会经过根据传入的参数信息动态地在内存中建立和.class 文件等同的字节码 b.而后根据相应的字节码转换成对应的class, c.而后调用newInstance()建立代理实例 */
UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);
// 调用代理的方法
proxy.select();
proxy.update();
// 保存JDK动态代理生成的代理类,类名保存为 UserServiceProxy
// ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy");
}
}
复制代码
运行结果
log start time [Thu Dec 20 16:55:19 CST 2018]
查询 selectById
log end time [Thu Dec 20 16:55:19 CST 2018]
log start time [Thu Dec 20 16:55:19 CST 2018]
更新 update
log end time [Thu Dec 20 16:55:19 CST 2018]
复制代码
InvocationHandler 和 Proxy 的主要方法介绍以下:
java.lang.reflect.InvocationHandler
Object invoke(Object proxy, Method method, Object[] args)
定义了代理对象调用方法时但愿执行的动做,用于集中处理在动态代理类对象上的方法调用
java.lang.reflect.Proxy
static InvocationHandler getInvocationHandler(Object proxy)
用于获取指定代理对象所关联的调用处理器
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
返回指定接口的代理类
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
构造实现指定接口的代理类的一个新实例,全部方法会调用给定处理器对象的 invoke 方法
static boolean isProxyClass(Class<?> cl)
返回 cl 是否为一个代理类
生成的代理类到底长什么样子呢?借助下面的工具类,把代理类保存下来再探个究竟
(经过设置环境变量sun.misc.ProxyGenerator.saveGeneratedFiles=true也能够保存代理类)
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
public class ProxyUtils {
/** * 将根据类信息动态生成的二进制字节码保存到硬盘中,默认的是clazz目录下 * params: clazz 须要生成动态代理类的类 * proxyName: 为动态生成的代理类的名称 */
public static void generateClassFile(Class clazz, String proxyName) {
// 根据类信息和提供的代理类名称,生成字节码
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
String paths = clazz.getResource(".").getPath();
System.out.println(paths);
FileOutputStream out = null;
try {
//保留到硬盘中
out = new FileOutputStream(paths + proxyName + ".class");
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
复制代码
而后在 Client2 测试类的main的最后面加入一行代码
// 保存JDK动态代理生成的代理类,类名保存为 UserServiceProxy
ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy");
复制代码
IDEA 再次运行以后就能够在 target 的类路径下找到 UserServiceProxy.class,双击后IDEA的反编译插件会将该二进制class文件
UserServiceProxy 的代码以下所示:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.UserService;
public final class UserServiceProxy extends Proxy implements UserService {
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m0;
private static Method m3;
public UserServiceProxy(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
// 省略...
}
public final String toString() throws {
// 省略...
}
public final void select() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
// 省略...
}
public final void update() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("proxy.UserService").getMethod("select");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("proxy.UserService").getMethod("update");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
复制代码
从 UserServiceProxy 的代码中咱们能够发现:
public final
修饰,因此代理类只可被使用,不能够再被继承m + 数字
的格式命名super.h.invoke(this, m1, (Object[])null);
调用,其中的 super.h.invoke
其实是在建立代理的时候传递给 Proxy.newProxyInstance
的 LogHandler 对象,它继承 InvocationHandler 类,负责实际的调用处理逻辑而 LogHandler 的 invoke 方法接收到 method、args 等参数后,进行一些处理,而后经过反射让被代理的对象 target 执行方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args); // 调用 target 的 method 方法
after();
return result; // 返回方法的执行结果
}
复制代码
JDK动态代理执行方法调用的过程简图以下:
代理类的调用过程相信你们都明了了,而关于Proxy的源码解析,还请你们另外查阅其余文章或者直接看源码
maven引入CGLIB包,而后编写一个UserDao类,它没有接口,只有两个方法,select() 和 update()
public class UserDao {
public void select() {
System.out.println("UserDao 查询 selectById");
}
public void update() {
System.out.println("UserDao 更新 update");
}
}
复制代码
编写一个 LogInterceptor ,继承了 MethodInterceptor,用于方法的拦截回调
import java.lang.reflect.Method;
import java.util.Date;
public class LogInterceptor implements MethodInterceptor {
/** * @param object 表示要进行加强的对象 * @param method 表示拦截的方法 * @param objects 数组表示参数列表,基本数据类型须要传入其包装类型,如int-->Integer、long-Long、double-->Double * @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用 * @return 执行结果 * @throws Throwable */
@Override
public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(object, objects); // 注意这里是调用 invokeSuper 而不是 invoke,不然死循环,methodProxy.invokesuper执行的是原始类的方法,method.invoke执行的是子类的方法
after();
return result;
}
private void before() {
System.out.println(String.format("log start time [%s] ", new Date()));
}
private void after() {
System.out.println(String.format("log end time [%s] ", new Date()));
}
}
复制代码
测试
import net.sf.cglib.proxy.Enhancer;
public class CglibTest {
public static void main(String[] args) {
DaoProxy daoProxy = new DaoProxy();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Dao.class); // 设置超类,cglib是经过继承来实现的
enhancer.setCallback(daoProxy);
Dao dao = (Dao)enhancer.create(); // 建立代理类
dao.update();
dao.select();
}
}
复制代码
运行结果
log start time [Fri Dec 21 00:06:40 CST 2018]
UserDao 查询 selectById
log end time [Fri Dec 21 00:06:40 CST 2018]
log start time [Fri Dec 21 00:06:40 CST 2018]
UserDao 更新 update
log end time [Fri Dec 21 00:06:40 CST 2018]
复制代码
还能够进一步多个 MethodInterceptor 进行过滤筛选
public class LogInterceptor2 implements MethodInterceptor {
@Override
public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(object, objects);
after();
return result;
}
private void before() {
System.out.println(String.format("log2 start time [%s] ", new Date()));
}
private void after() {
System.out.println(String.format("log2 end time [%s] ", new Date()));
}
}
// 回调过滤器: 在CGLib回调时能够设置对不一样方法执行不一样的回调逻辑,或者根本不执行回调。
public class DaoFilter implements CallbackFilter {
@Override
public int accept(Method method) {
if ("select".equals(method.getName())) {
return 0; // Callback 列表第1个拦截器
}
return 1; // Callback 列表第2个拦截器,return 2 则为第3个,以此类推
}
}
复制代码
再次测试
public class CglibTest2 {
public static void main(String[] args) {
LogInterceptor logInterceptor = new LogInterceptor();
LogInterceptor2 logInterceptor2 = new LogInterceptor2();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserDao.class); // 设置超类,cglib是经过继承来实现的
enhancer.setCallbacks(new Callback[]{logInterceptor, logInterceptor2, NoOp.INSTANCE}); // 设置多个拦截器,NoOp.INSTANCE是一个空拦截器,不作任何处理
enhancer.setCallbackFilter(new DaoFilter());
UserDao proxy = (UserDao) enhancer.create(); // 建立代理类
proxy.select();
proxy.update();
}
}
复制代码
运行结果
log start time [Fri Dec 21 00:22:39 CST 2018]
UserDao 查询 selectById
log end time [Fri Dec 21 00:22:39 CST 2018]
log2 start time [Fri Dec 21 00:22:39 CST 2018]
UserDao 更新 update
log2 end time [Fri Dec 21 00:22:39 CST 2018]
复制代码
CGLIB 建立动态代理类的模式是:
JDK动态代理:基于Java反射机制实现,必需要实现了接口的业务类才能用这种办法生成代理对象。
cglib动态代理:基于ASM机制实现,经过生成业务类的子类做为代理类。
JDK Proxy 的优点:
基于相似 cglib 框架的优点:
来源于网上,用于帮助理解和掌握,欢迎补充
代理能够分为 "静态代理" 和 "动态代理",动态代理又分为 "JDK动态代理" 和 "CGLIB动态代理" 实现。
静态代理:代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理对象而真正调用的是 Real Object
JDK 动态代理:
为了解决静态代理中,生成大量的代理类形成的冗余;
JDK 动态代理只须要实现 InvocationHandler 接口,重写 invoke 方法即可以完成代理的实现,
jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象
jdk动态代理之因此只能代理接口是由于代理类自己已经extends了Proxy,而java是不容许多重继承的,可是容许实现多个接口
优势:解决了静态代理中冗余的代理实现类问题。
缺点:JDK 动态代理是基于接口设计实现的,若是没有接口,会抛异常。
CGLIB 代理:
因为 JDK 动态代理限制了只能基于接口设计,而对于没有接口的状况,JDK方式解决不了;
CGLib 采用了很是底层的字节码技术,其原理是经过字节码技术为一个类建立子类,并在子类中采用方法拦截的技术拦截全部父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。
实现方式实现 MethodInterceptor 接口,重写 intercept 方法,经过 Enhancer 类的回调方法来实现。
可是CGLib在建立代理对象时所花费的时间却比JDK多得多,因此对于单例的对象,由于无需频繁建立对象,用CGLib合适,反之,使用JDK方式要更为合适一些。
同时,因为CGLib因为是采用动态建立子类的方法,对于final方法,没法进行代理。
优势:没有接口也能实现动态代理,并且采用字节码加强技术,性能也不错。
缺点:技术实现相对难理解些。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import proxy.UserService;
import java.lang.reflect.Method;
/** * 建立代理类的工厂 该类要实现 MethodInterceptor 接口。 * 该类中完成三样工做: * (1)声明目标类的成员变量,并建立以目标类对象为参数的构造器。用于接收目标对象 * (2)定义代理的生成方法,用于建立代理对象。方法名是任意的。代理对象即目标类的子类 * (3)定义回调接口方法。对目标类的加强这在这里完成 */
public class CGLibFactory implements MethodInterceptor {
// 声明目标类的成员变量
private UserService target;
public CGLibFactory(UserService target) {
this.target = target;
}
// 定义代理的生成方法,用于建立代理对象
public UserService myCGLibCreator() {
Enhancer enhancer = new Enhancer();
// 为代理对象设置父类,即指定目标类
enhancer.setSuperclass(UserService.class);
/** * 设置回调接口对象 注意,只因此在setCallback()方法中能够写上this, * 是由于MethodIntecepter接口继承自Callback,是其子接口 */
enhancer.setCallback(this);
return (UserService) enhancer.create();// create用以生成CGLib代理对象
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("start invoke " + method.getName());
Object result = method.invoke(target, args);
System.out.println("end invoke " + method.getName());
return result;
}
}
复制代码
参考:
《Java核心技术》卷1
《深刻理解Java虚拟机》7.3
java docs: docs.oracle.com/javase/8/do…
Java三种代理模式:静态代理、动态代理和cglib代理
描述动态代理的几种实现方式 分别说出相应的优缺点
JDK动态代理详解
Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
静态代理和动态代理的理解
欢迎评论、转发、分享,您的支持是我最大的动力
更多内容可访问个人我的博客:laijianfeng.org
关注【小旋锋】微信公众号,及时接收博文推送