一、什么是动态代理?
动态代理(Dynamic Proxy)是在程序运行时给目标对象创建代理对象,代理对象可以对方法进行增强、控制等。在生成代理对象的过程中,目标对象不变,代理对象中方法对目标对象方法进行增强。代理对象执行目标方法时会被拦截,转而调用写好的代理方法。
动态代理的实现方式有两种:JDK代理、CGLib代理
二、JDK动态代理
特点:基于 接口 实现(implement)的动态代理,即 生成的代理对象必须实现指定的接口
生成代理对象方法(Proxy类中的静态方法newProxyInstance)
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h));
ClassLoader loader:要代理对象的类加载器
Class<?>[] interfaces:代理对象实现的接口
InvocationHandler h:调用处理器,代理对象每次执行方法都会被拦截,将相关信息传递给调用处理器的invoke方法
特别注意:在调用处理器的 invoke方法中尽量别调用代理对象的任何方法 ,因为在任何地方执行代理对象的方法都会被拦截,然后将调用信息传递给调用处理器的invoke方法,在内部调用代理对象的方法,很容易造成无限递归(包括不要打印代理对象,因为会隐式调用代理对象的toString方法)
实现jdk动态代理核心步骤:
1)定义任务处理器,实现 InvocationHandler 接口,实现invoke方法
2)通过 Proxy.newProxyInstance(...) 创建代理对象
具体jdk动态代理实现:
① 声明接口类
import java.util.List;public interface IUserService {/* 获取所有用户的名字*/List<String> getUserNames() ;
}
② 定义实现类(即将被代理的类)
import java.util.Arrays;
import java.util.List;public class UserServiceImpl implements IUserService{@Overridepublic List<String> getUserNames() {return Arrays.asList("超人强", "GGB", "小呆呆");}
}
③ 实现调用处理器(定义代理行为)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class UserInvocationHandler implements InvocationHandler {// 目标对象private Object target;// 接收目标对象public UserInvocationHandler(Object target) {this.target = target;}/*** @param proxy 代理对象* @param method 执行方法* @param args 方法参数* @return 返回方法的执行结果*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// ====== !!! 尽量不要再该方法中调用proxy对象的方法,不然很容易无限递归 !!! ======// 1.记录方法的开始时间long startTime = System.currentTimeMillis();// 2.执行方法Object result = method.invoke(target, args);// 3.记录方法的结束时间long endTime = System.currentTimeMillis();// 4.打印方法耗时System.out.printf("====== %s 方法执行耗时:%d ms ======\n", method.getName(), endTime- startTime);// 5.返回方法的执行结果return result;}
}
④ 创建代理对象,调用代理方法
import java.lang.reflect.Proxy;
import java.util.List;public class Main {public static void main(String[] args) {// 生成代理对象IUserService userService = (IUserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), // 需要代理对象类的加载器UserServiceImpl.class.getInterfaces(), // 代理类实现的接口new UserInvocationHandler(new UserServiceImpl()) // 调用处理器);// 通过代理对象执行方法List<String> userNames = userService.getUserNames();System.out.println(userNames);}
}
执行结果:
====== getUserNames 方法执行耗时:0 ms ======
[超人强, GGB, 小呆呆]进程已结束,退出代码为 0
三、CGLib动态代理
特点:基于类 继承(extend)的动态代理,即代理类必须实现目标类,无需提前写好接口,属于第三方依赖,需要导入
工作原理
生成子类:CGLIB通过字节码技术在运行时生成目标类的子类(代理类)。
方法拦截:在生成的子类中,方法可以被重写以添加额外的逻辑,例如前置处理、后置处理等,这些逻辑组成了代理的行为。
创建代理对象:通过创建代理类对象,得到一个与目标对象有相同的方法名的对象。
导入CGLib依赖
方式一:maven导入
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
方式二:从远程仓库下载
下载地址:Maven Repository: cglib » cglib (mvnrepository.com)
生成代理对象方法(Enhancer类中的静态方法create)
public static Object create(Class type, Callback callback);
Class type:要代理类的Class对象
Callback callback:代理类实现行为的接口,其主要实现是MethodInterceptor
实现CGLib动态代理的核心步骤:
1)定义代理类的行为,实现方法拦截器MethodInterceptor,实现其中的intercept方法
2)通过Enhancer.create(...)创建代理对象
具体GGLib动态代理实现:
① 定义目标类
public class Person {public void marry() {System.out.println("Person.marry()+++");}
}
② 实现方法拦截器(定义代理行为)
intercept方法介绍
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy);
Object proxy:代理对象,与jdk代理逻辑类似,不要在intercept方法内部直接调用代理对象的方法,不然容易形成无限递归
Method method:要执行的方法
Object[] args:方法参数
MethodProxy methodProxy:方法的代理,这个类中重点关注两个方法:
invoke(Object obj, Object[] args) :
与Method中的invoke方法效果一样,都是执行特定对象中的方法
invokeSuper(Object obj, Object[] args):
执行obj父类对象中的方法,这里可以将代理对象proxy传递过去,这样就会直接执行proxy父类中的同名方法,
即 invokeSuper(proxy, args) 等价于 ==> invoke(target, args) ,target的需要被代理的对象
方法拦截器具体实现
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/*代理对象方法拦截器*/
public class ProcedureHandler implements MethodInterceptor {// 目标对象private Object target;public ProcedureHandler(Object target) {this.target = target;}/*** @param proxy 代理对象* @param method 调用方法* @param args 方法参数* @param methodProxy 代理方法* @return 调用方法的返回值* @throws Throwable*/@Overridepublic Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// --- 记录开始时间long startTime = System.currentTimeMillis();System.out.println("方法开始时间:" + startTime);// ====== 执行方法,这里方便对比,将方法执行了三遍 ======// 执行当前类的当前方法,以下两行代码效果相同Object result = method.invoke(target, args); Object result2 = methodProxy.invoke(target, args);// 执行父类同名方法Object result3 = methodProxy.invokeSuper(proxy, args);// --- 记录结束时间long endTime = System.currentTimeMillis();System.out.println("方法开始时间:" + endTime);// -- 打印方法耗时System.out.printf("====== 方法总耗时:%d ms ======\n", endTime - startTime);return result;}
}
③ 创建代理对象,调用代理方法
import net.sf.cglib.proxy.*;public class Main {public static void main(String[] args) {Person person = (Person) Enhancer.create(Person.class, new ProcedureHandler(new Person()));person.marry();}
}
执行结果:
方法开始时间:1722225835558
Person.marry()+++
Person.marry()+++
Person.marry()+++
方法开始时间:1722225835568
====== 方法总耗时:10 ms ======进程已结束,退出代码为 0
动态代理使用推荐
当有代理类的接口时,可以选择jdk动态代理;没有接口就用CGLib动态代理,毕竟CGLib动态代理太好用了
四、问题解决
我这里使用的是jdk8版本,如果大于8可能会出现一些反射相关的异常(由于反射需要大量性能,所以一些使用频率不高的方法就被关闭了,所以从 JDK1.8 后大量包的可访问反射属性都被关闭了,lang 就是其中之一。)。
如:“module java.base does not “opens java.lang“ to unnamed module”。
只需要在启动虚拟机选项中加上以下参数即可:
--add-opens java.base/java.lang=ALL-UNNAMED