Java 基础 - 动态代理

本贴最后更新于 1858 天前,其中的信息可能已经东海扬尘

Java 基础-动态代理

在切面编程(AOP)中,可以方便的对某些业务进行增强,比如添加日志。

在声明式事务中,可以方便的通过 @Transactional 注解开启事务支持。

在 Mybatis 中,我们没有创建 Mapper 接口的实现类,却可以直接注入 Mapper 并进行 CRUD。

而这些底层的实现,就是动态代理。

本篇博客包括以下内容:

  • 静态代理
  • jdk 动态代理
  • cglib 动态代理

一、静态代理

静态代理意味着我们需要在程序运行前创建代理类。

首先,创建 UserService 接口

public interface UserService { void addUser(); void selectUser(String name); }

然后创建 UserService 接口的实现类 UserServiceImpl

public class UserServiceImpl implements UserService { @Override public void addUser() { System.out.println("添加用户..."); } @Override public void selectUser(String name) { System.out.println("查询:" + name); } }

假如静态代理需要给 UserService 接口的中的方法添加调用前后的日志记录,那么可以创建 UserServiceProxy

public class UserServiceProxy implements UserService { private UserService target; public UserServiceProxy(UserService target) { this.target = target; } @Override public void addUser() { System.out.println("日志记录前..."); target.addUser(); System.out.println("日志记录后..."); } @Override public void selectUser(String name) { System.out.println("日志记录前..."); target.selectUser(name); System.out.println("日志记录后..."); } }

简单使用静态代理:

UserService target = new UserServiceImpl(); UserService proxy = new UserServiceProxy(target); proxy.addUser(); proxy.selectUser("ccran");

控制台输出如下:

日志记录前... 添加用户... 日志记录后... 日志记录前... 查询:ccran 日志记录后...

至此,分析一下静态代理的缺点:

  1. 日志记录的代码存在大量冗余
  2. 如果需要代理多个接口,需要修改原代理类或创建新的代理类,难以维护和拓展。

二、jdk 动态代理

与静态代理通过 javac 生成.class 字节码文件,然后通过 ClassLoader 加载到 JVM 不同。

动态代理在程序运行时生成字节码文件,通过 Proxy 的 newProxyInstance 方法可以实现 jdk 动态代理。

首先,需要创建 InvocationHandler 接口的实现类 LogInvocationHandler,这个类规定了代理的业务实现,目前是日志记录。

public class LogInvocationHandler implements InvocationHandler { private UserService target; public LogInvocationHandler(UserService target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("日志记录前..."); Object res = method.invoke(target, args); System.out.println("日志记录后..."); return res; } }

通过 Proxy 类的 newProxyInstance 方法可以创建动态代理对象

第一个参数需要传入类加载器,一般传入加载代理类的类加载器

第二个参数需要传入需要代理接口的 class 对象

第三个参数就是之前定义的 InvocationHandler 实现类

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

创建与使用代码如下,输出结果与静态代理的使用相同。

// jdk动态代理 public static void dynamicProxyJDK() { UserService target = new UserServiceImpl(); InvocationHandler invocationHandler = new LogInvocationHandler(target); UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), invocationHandler); proxy.addUser(); proxy.selectUser("ccran"); }

通过 ProxyGenerator 的 generateProxyClass 方法将代理类的字节码保存到文件后反编译打开,动态代理的本质就很清晰了。

public final class DynamicUserServiceProxy extends Proxy implements UserService { private static Method m1; private static Method m2; private static Method m4; private static Method m0; private static Method m3; public DynamicUserServiceProxy(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 selectUser(String var1) throws { try { super.h.invoke(this, m4, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } 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); } } public final void addUser() throws { try { super.h.invoke(this, m3, (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"); m4 = Class.forName("com.ccran.proxy.UserService").getMethod("selectUser", Class.forName("java.lang.String")); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); m3 = Class.forName("com.ccran.proxy.UserService").getMethod("addUser"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }

jdk 动态代理解读:

  1. 继承 Proxy 类(因此只能代理接口),并实现了 newProxyInstance 方法中第二个参数规定的代理接口。
  2. 通过 final 保证行为不可更改。
  3. 在静态代码块中通过反射拿到了代理接口的所有方法。
  4. 所有方法都是调用 super.h.invoke 方法,在 Proxy 父类中可以发现,super.h 正是 newProxyInstance 方法中第三个参数传入的 LogInvocationHandler 对象(代理的业务实现:日志记录)。
  5. 调用链:查询用户---DynamicUserServiceProxy(selectUser)---LogInvocationHandler(invoke)---(日志前)被代理对象的 selectUser(日志后)

优点:

解决静态代理的问题。

java 原生代理,无需依赖。

缺点:

只能代理接口。

三、cglib 动态代理

jdk 动态代理无法代理非接口方法,通过 cglib 动态代理可以解决,首先引入 cglib 依赖

<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>

移除 UserServiceImpl 实现的 UserService 接口,实现 cglib 动态代理

// 动态代理 public static void dynamicProxyCglib() { UserServiceImpl target = new UserServiceImpl(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserServiceImpl.class); // 设置enhancer的回调对象 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("日志记录前..."); Object res = methodProxy.invokeSuper(o, objects); System.out.println("日志记录后..."); return res; } }); // 创建代理对象 UserServiceImpl proxy = (UserServiceImpl) enhancer.create(); // 通过代理对象调用目标方法 proxy.addUser(); proxy.selectUser("ccran"); }

我们猜测,cglib 可能创建出来的代理类如下:

public final class CglibProxy extends UserServiceImpl{ private MethodInterceptor m; public final void addUser(){ if(m!=null){ m.intercept(xxx) }else{ super.addUser(); } } }

四、实现

4.1 AOP 实现

AOP 的使用很简单,通过切面表达式定义切点(需要代理的方法,如 addUser),然后配合相关的注解定义增强的方法(代理的业务:如日志记录)即可。

故猜测实现如下(以 jdk 动态代理为例):

@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object res = null; // 符合切面表达式,是需要代理的方法 if(jointExp.match(method)){ // 封装切点参数 JointPoint jointPoint = getJointPoint(proxy,method,args,xxx); // 前置通知 beforeMethod.invoke(jointPoint); res = method.invoke(target, args); }else{ res = method.invoke(target, args); } return res; }

4.2 声明式事务

对于某个添加了 @Transactional 注解的方法,如

@Transactional public void insertInfo(){ userService.insertUser(); otherService.insertOther(); }

最终的代理对象方法可能实现如下:

@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { conn.setAutoCommit(false); Object res = null; try{ res = method.invoke(target, args); }catch (SQLException e) { // 回滚事务: conn.rollback(); } finally { conn.setAutoCommit(true); conn.close(); } return res; }

4.3 Mapper

在 Mybatis 中,对用户 CRUD 的 Mapper 可能如下:

public interface UserMapper { @Select("SELECT id,name,age FROM user where id= #{id}") User selectById(int id); }

最终注入到 UserService 中的 UserMapper 实现可能如下

@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object res = null; if(method.isAnnotationPresent(Select.class)){ // 获取sql Select selectAnnotation = method.getAnnotation(Select.class); String query = generateQuery(selectAnnotation.value()); // 执行sql ResultSet resultSet = conn.excuteQuery(sql); // 封装成对象返回 res = generate(resultSet); }else if(method.isAnnotationPresent(Update.class)){ xxx } xxx return res; }

五、总结

  1. 静态代理比较简单直观,但是会使编写的代码冗余,并且代理多个类时,难以维护和拓展。
  2. jdk 动态代理本质是实现了代理类实现的接口,因此无法代理非接口方法,但是原生无依赖。
  3. cglib 动态代理可以代理非接口方法,代码相比于 jdk 动态代理可读性更强。
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3201 引用 • 8216 回帖 • 4 关注
  • 动态代理
    6 引用 • 10 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...