动态代理 在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。 在内存当中动态生成类的技术常见的包括: ● JDK动态代理技术:只能代理接口。 ● CGLIB动态代理技术:它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。 它可以在运行期扩展Java类与实现Java接口。(底层有一个小而快的字节码处理框架ASM。) ● Javassist动态代理技术: JDK动态代理: 在JDK动态代理中一般有这几个步骤: 1. 创建目标对象 2. 创建代理对象 : 通过Proxy.newProxyInstance()进行创建 --并且代理对象和目标对象实现的接口要一样,也就是需要向下转型 3. 调用代理对象中的代理方法 在创建代理对象的时候,用到了newProxyInstance方法 --> 翻译为新建代理对象 也就是说,通过这个方法可以创建代理对象,本质上,这个方法的执行做了两件事: 1.在内存中动态生成了一个代理类的字节码class 2.new 对象了,通过内存中生成的代理类代理这个代码,实例化了代理对象 关于newProxyInstance方法中的三个参数: 1.ClassLoader loader --> (类加载器) 在内存中生成的字节码也是class文件,如果需要执行就需要先加载到内存中,加载器就需要类加载器,所以这里需要指定类加载器 并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个 2.Class>[] interfaces --> (接口) 代理类和目标类要实现同一个接口或者同一些接口,在内存中生成代理类的时候,这个代理类需要告诉他实现了哪些接口 3.InvocationHandler h --> (调用处理器--是一个接口) 在调用处理器接口中编写的就是 : 增强代码,既然是接口,就需要写接口的实现类 , 这个不会出现类爆炸,主要原因是:这种调用处理器写一次就可以了 代码如下: OrderService proxyObj = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),new TimerInvocationHandler(target)) 关于InvocationHandler的实现: 1.目标对象(也就是定义成员变量) 2.通过构造器赋值给成员变量 3.实现接口中的invoke()方法 invoke()方法什么时候被调用?被谁调用呢? 当代理独享调用代理方法的时候,注册在InvocationHandler调用处理器接口当中的invoke()方法就被调用了 这个方法并不是我们来调用的,是JDK负责调用的 invoke()方法的三个参数:JDK 在调用这个方法的时候,会自动给我们传过来三个参数: 1. Object proxy : 代理对象的引用。这个参数使用较少。 2. Method method 目标对象上的目标方法。(要执行的目标方法就是它。也就是说,在执行过程中,使用method来调用目标对象的目标方法) 3. Object[] args : 目标方法上的实参 代码的实现如下: @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 这个接口的目的就是为了让你有地方写增强代码。 //System.out.println("增强1"); long begin = System.currentTimeMillis(); // 调用目标对象上的目标方法 // 方法四要素:哪个对象,哪个方法,传什么参数,返回什么值。 Object retValue = method.invoke(target, args); //System.out.println("增强2"); long end = System.currentTimeMillis(); System.out.println("耗时"+(end - begin)+"毫秒"); // 注意这个invoke方法的返回值,如果代理对象调用代理方法之后,需要返回结果的话,invoke方法必须将目标对象的目标方法执行结果继续返回。 return retValue; }
/*接口和目标对象就不创建了 ,自行脑补*/
public class Client {
//客户端程序
public static void main(String[] args) {
// 创建目标对象
OrderService target = new OrderServiceImpl();
// 创建代理对象
OrderService proxyObj = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new TimerInvocationHandler(target));
// 调用代理对象的代理方法 注意:调用代理对象的代理方法的时候,如果你要做增强的话,目标对象的目标方法得保证执行。
proxyObj.generate();
proxyObj.modify();
proxyObj.detail();
String name = proxyObj.getName();
System.out.println(name);
}
}
public class TimerInvocationHandler implements InvocationHandler {
private Object target; // 目标对象
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);
//System.out.println("增强2");
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
// 注意这个invoke方法的返回值,如果代理对象调用代理方法之后,需要返回结果的话,invoke方法必须将目标对象的目标方法执行结果继续返回。
return retValue;
}
}
CGLIB动态代理: 底层本质上:CGLIB既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用final修饰。 1.添加 CGLIB 的相关依赖 2.准备一个没有实现接口的类UserService 3.使用CGLIB在内存中为UserService类生成代理类,并创建对象:主要分为以下步骤: a.创建字节码增强器对象,这个对象是CGLIB库当中的核心对象,就是依靠它来生成代理类。new Enhancer(); b.告诉CGLIB父类是谁,也就是告诉CGLIB目标类是谁 : enhancer.setSuperclass(UserService.class); c.设置回调接口(等同于JDK动态代理当中的调用处理器。InvocationHandler 但是在CGLIB当中不是InvocationHandler接口, 是方法拦截器接口:MethodInterceptor) enhancer.setCallback(方法拦截器对象); d.创建代理对象(会做两件事:1.在内存中生成UserService类的子类,其实就是代理类的字节码 2.创建代理对象)父类是UserService,子类这个代理类一定是UserService UserService userServiceProxy = (UserService) enhancer.create(); e.调用代理对象的代理方法 MethodInterceptor接口中有一个方法intercept(),该方法有4个参数: Object target:目标对象 Method method:目标方法 Object[] objects:目标方法调用时的实参 MethodProxy methodProxy:代理方法
public class Client {
public static void main(String[] args) {
// 创建字节码增强器对象 这个对象是CGLIB库当中的核心对象,就是依靠它来生成代理类。
Enhancer enhancer = new Enhancer();
// 告诉CGLIB父类是谁。告诉CGLIB目标类是谁。
enhancer.setSuperclass(UserService.class);
// 设置回调(等同于JDK动态代理当中的调用处理器。InvocationHandler)在CGLIB当中不是InvocationHandler接口,是方法拦截器接口:MethodInterceptor
enhancer.setCallback(new TimerMethodInterceptor());
// 创建代理对象
UserService userServiceProxy = (UserService) enhancer.create();
// 调用代理对象的代理方法。
boolean success = userServiceProxy.login("admin", "123");
System.out.println(success ? "登录成功" : "登录失败");
userServiceProxy.logout();
}
}
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;
}
}
public class UserService {
// 目标方法
public boolean login(String username, String password){
System.out.println("系统正在验证身份...");
if ("admin".equals(username) && "123".equals(password)) {
return true;
}
return false;
}
// 目标方法
public void logout(){
System.out.println("系统正在退出...");
}
}
注意:对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数
--add-opens java.base/java.lang=ALL-UNNAMED
●--add-opens java.base/sun.net.util=ALL-UNNAMED