动态代理
- 一、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; } @Override public 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 {
@Override
public 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