动态代理
- 一、JDK动态代理
- 二、CGLIB动态代理
- 三、Javassist动态代理技术
- 在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。
一、JDK动态代理
-
DK动态代理技术:只能代理接口。
-
Java 动态代理实现相关类位于
java.lang.reflect
包,主要涉及两个类:-
InvocationHandler
接口。它是代理实例的调用处理程序实现的接口,该接口中定义了如下方法:public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
invoke()
方法上有三个参数:- 第一个参数
proxy
表示代理类。设计这个参数只是为了后期的方便,如果想在invoke方法中使用代理对象的话,尽管通过这个参数来使用。 - 第二个参数
method
表示需要代理的方法。 - 第三个参数 args 表示代理方法的参数数组。
- 第一个参数
-
Proxy
类。该类即为动态代理类,该类最常用的方法为:public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
- 这行代码做了两件事:
- 第一件事:在内存中生成了代理类的字节码。
- 第二件事:创建代理对象。
newProxyInstance()
方法有三个参数:- 第一个参数
loader
表示代理类的类加载器。。在内存中生成了字节码,要想执行这个字节码,也是需要先把这个字节码加载到内存当中的。所以要指定使用哪个类加载器加载。 - 第二个参数
interfaces
表示代理类实现的接口列表(与真实主题类的接口列表一致)。通过这个参数告诉JDK动态代理生成的类要实现哪些接口。 - 第三个参数
h
表示所指派的调用处理程序类。这是一个回调接口,也就是说调用这个接口中方法的程序已经写好了,就差这个接口的实现类了。
- 第一个参数
- 这行代码做了两件事:
-
-
还是使用静态代理中的例子:一个接口和一个实现类。(参考上面的代码)
-
要写一下java.lang.reflect.InvocationHandler接口的实现类,并且实现接口中的方法,代码如下:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method;/*** 动态代理类**/public class TimerInvocationHandler implements InvocationHandler {private Object target; //需要代理的目标对象public TimerInvocationHandler() {}public TimerInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 目标执行之前增强。long begin = System.currentTimeMillis();// 调用目标对象的目标方法Object retValue = method.invoke(target, args);// 目标执行之后增强。long end = System.currentTimeMillis();System.out.println("耗费时常" + (end - begin) + "毫秒");// 返回目标对象方法的返回值。return retValue;} }
-
编写 Client 测试程序:
import service.OrderService; import service.OrderServiceImpl;public class Client {public static void main(String[] args) {// 创建目标对象OrderService target = new OrderServiceImpl();// 创建代理对象OrderService ret = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimerInvocationHandler(target));// 调用代理对象的代理方法ret.generate();ret.detail();ret.delete();//当你调用代理对象的代理方法的时候,注册在InvocationHandler接口中的invoke()方法会被调用。} }
-
提供一个工具类:ProxyUtil,封装一个方法:
package utils;import dynamic_proxy.TimerInvocationHandler;import java.lang.reflect.Proxy;public class ProxyUtil {private ProxyUtil(){}public static Object newProxyInstance(Object target) {return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimerInvocationHandler(target));} }
-
这样客户端代码就不需要写那么繁琐了:
import service.OrderService; import service.OrderServiceImpl; import utils.ProxyUtil;public class Client {public static void main(String[] args) {// 创建目标对象OrderService target = new OrderServiceImpl();// 创建代理对象OrderService ret = (OrderService) ProxyUtil.newProxyInstance(target);// 调用代理对象的代理方法ret.generate();ret.detail();ret.delete();//当你调用代理对象的代理方法的时候,注册在InvocationHandler接口中的invoke()方法会被调用。} }
-
在
Spring AOP
实现中使用了动态代理模式
,使得代码中不存在与具体要用到的接口或类相关的引用。
二、CGLIB动态代理
- CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,
底层是通过继承的方式实现的
。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
- CGLIB既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用final修饰。
- 使用CGLIB,需要引入它的依赖:
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
- 我们准备一个没有实现接口的类,如下:
public class UserService {public String login(String username, String password) {try {Thread.sleep(13654);} catch (InterruptedException e) {e.printStackTrace();}if (username.equals("root") && password.equals("123456")) {return "success~";}return "username or password error!";}
}
- 和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,而是:net.sf.cglib.proxy.MethodInterceptor编写MethodInterceptor接口实现类:
public class TimerMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {// 前增强long begin = System.currentTimeMillis();// 调用目标Object retValue = methodProxy.invokeSuper(target, objects);// 后增强long end = System.currentTimeMillis();System.out.println("耗时" + (end - begin) + "毫秒");// 一定要返回return retValue;}
}
- MethodInterceptor接口中有一个方法intercept(),该方法有4个参数:
- 第一个参数:目标对象
- 第二个参数:目标方法
- 第三个参数:目标方法调用时的实参
- 第四个参数:代理方法
- 使用CGLIB在内存中为UserService类生成代理类,并创建对象:
public class Client {public static void main(String[] args) {// 创建字节码增强器Enhancer enhancer = new Enhancer();// 告诉cglib要继承哪个类enhancer.setSuperclass(UserService.class);// 设置回调接口enhancer.setCallback(new TimerMethodInterceptor());// 生成源码,编译class,加载到JVM,并创建代理对象UserService userServiceProxy = (UserService)enhancer.create();String ret = userServiceProxy.login("root", "123456");System.out.println(ret);}
}
- 对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:
- –add-opens java.base/java.lang=ALL-UNNAMED
- –add-opens java.base/sun.net.util=ALL-UNNAMED