前言
最近在学习 Mybatis 的时候发现,里面就是用 JDK动态代理。MapperProxy 实现了 InvocationHandler 接口的 invoke 方法,最终所有的实际调用都会调用到这个方法的包装逻辑。
但是由于不了解,所以得专门来学习学习。
静态代理
先从简单的入手,静态代理:代码在运行前就已经生成了。在我看来,静态代理就是类似于组合调用。
比如说,下面的实现类接口,我们现在不想动这个接口,但是想额外的增加一些逻辑处理,那么我们就需要代理类。
public interface UserService {
public void select();
}
public class UserServiceImpl implements UserService{
@Override
public void select() {
System.out.println("查询数据");
}
}
实现同一个接口,并将接口注入到代理类当中。
public class UserServiceProxy implements UserService {
private UserService userService;
public UserServiceProxy(UserService userService) {
this.userService = userService;
}
@Override
public void select() {
before();
userService.select();
after();
}
//这里定义为私有方法,防止外部调用
private void before(){
System.out.println("做些权限认证啥的");
}
private void after(){
System.out.println("记录些日志啥的");
}
}
测试:把实际的业务类 UserServiceImpl 传入代理类,那么代理类就会做额外的一些逻辑处理。
public static void main(String[] args) {
UserServiceImpl userService=new UserServiceImpl();
UserServiceProxy userServiceProxy=new UserServiceProxy(userService);
userServiceProxy.select();
}
动态代理
这里主要介绍常见的两种动态代理:JDK 动态代理和 CGLIB 动态代理。动态由于和反射机制联系紧密,需要先了解下放射相关的知识:juejin.cn/post/720144…
JDK 动态代理
介绍
JDK 动态代理是不需要依赖第三方的库,只要 JDK 环境就可以进行代理,需要满足以下要求:
- 实现 InvocationHandler 接口,重写 invoke()
- 使用 Proxy.newProxyInstance() 生成代理对象
- 被代理的对象必须要实现接口
代码实战
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 {
System.out.println("JDK的动态代理");
Object result = method.invoke(target, args);
return result;
}
}
tip:这里需要使用到上面的 UserServiceImpl 类。
public class LogHandlerProxy {
public static void main(String[] args) {
//设置变量可以保存动态代理类,默认名称以 $Proxy0 格式命名
// (在项目的根路径下会生成一个 com.sun.proxy 的包,类以 $Proxy0 这种命名方式 )
System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// JDK 动态代理只能基于接口实现,为什么?因为生成的动态代理接口就已经继承了 Proxy 类。Java 只能单继承
// 创建被代理的对象
UserServiceImpl userService = new UserServiceImpl();
// 获取对应的类加载器
ClassLoader classLoader = userService.getClass().getClassLoader();
// 获取所有接口的Class,这里只实现了一个接口 UserService
Class[] interfaces = userService.getClass().getInterfaces();
// 创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用
InvocationHandler handler = new LogHandler(userService);
/**
* 根据上面的信息创建代理对象,在这个过程中:
* a.JDK会通过根据传入的参数信息动态的在内存中创建和 .class 文件等同的字节码
* b.然后根据相应的字节码转换成对应的 class
* c.然后调用 newInstance() 创建代理实例
*
* ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的。
* Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型
* InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法
*
*/
UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, handler);
// 调用代理的方法 :执行 invoke 方法
proxy.select();
}
}
上面的代码中,对每行代码都已经做了详细的说明,这里不在赘述。
- 既然动态代理是运行时生成的,那么我们能不能把动态代理生成的类保存下来,分析分析?
- 动态代理涉及的关键类:InvocationHandler、Proxy(生成代理的方法,需要三个参数,我们“凑齐”这三个参数就能“实现代理”),以及他们的使用方法。
JDK动态生成的代理类
- 看类的定义就知道为什么 JDK 只能基于实现类实现了,因为它默认就继承了 Proxy 类,而 Java 没有多继承,只有多实现。
- 里面会重写 select() 方法
public final class $Proxy0 extends Proxy implements UserService {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
private static Method m4;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void select() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (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");
m3 = Class.forName("com.practice.thinkinbasic.Proxy.StaticProxy.UserService").getMethod("select");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
CGLIB 动态代理
介绍
CGLib 必须依赖于 CGLib 的类库,需要满足以下要求:
- 实现 MethodInterceptor 接口,重写 intercept()
- 使用 Enhancer 对象的 create() 方法产生代理对象
代码实战
public class CGLibFactroy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object myCGLibCreator(Class clazz) {
// 为代理对象设置父类,即指定目标类
enhancer.setSuperclass(clazz);
/**
* 设置回调接口对象 注意,只所以在 setCallback()方法中可以写上 this,
* 是因为 MethodIntecepter 接口继承自 Callback,是其子接口
*/
enhancer.setCallback(this);
return enhancer.create();// create 用以生成 CGLib 代理对象
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("CGLib 动态代理");
return methodProxy.invokeSuper(o,objects);
// return method.invoke(o,objects);
}
}
为了验证 CGLib 可以对类做动态代理,我们这里还定义了一个简单的类做测试。
public class UserDao {
public void select(){
System.out.println("方法查询");
}
final public void update(){
System.out.println("方法更新");
}
}
public class ApiTest {
public static void main(String[] args) {
// 设置动态代理生成代理类的位置
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\myself\gitlab_self\keepon-java\keepon\think-in-basic\src\main\java\com\practice\thinkinbasic\Proxy\DynamicProxy\CGLibProxy\");
// UserDao userDao = new UserDao();
CGLibFactroy cgLibFactroy = new CGLibFactroy();
//基于类
// UserDao userService1 = (UserDao) cgLibFactroy.myCGLibCreator(UserDao.class);
// userService1.select();
//基于接口
UserService userService1 = (UserService) cgLibFactroy.myCGLibCreator(UserServiceImpl.class);
userService1.select();
}
}
CGLib 动态生成的代理类
下图是示例:会生成很多类。其中一个类是继承了被代理类,不管是代理接口还是代理类,都是基于继承,只不过接口是继承的实现类。
总结
静态代理 VS 动态代理
静态:在程序运行之前就已经存在代理类的字节码文件,代理类和真实逻辑类的关系在运行前就确定了。
动态:在程序运行期间由 JVM 根据反射等机制动态生成的,运行前不存在代理类的字节码文件。
JDK VS CGLIB
实现方式 | 生成字节码方式 | 代理调用方式 | |
---|---|---|---|
JDK 动态代理 | 实现被代理对象接口(只能用于接口,从生成的代理类可以看出) | JDK 直接写 Class 字节码 | 通过放射机制 |
CGLIB 动态代理 | 继承被代理对象 | 使用 ASM 框架写Class 字节码,代理实现更复杂,比JDK 效率低 | 通过 FastClass 机制直接调用方法,执行效率更高 |