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 日志记录后...
至此,分析一下静态代理的缺点:
- 日志记录的代码存在大量冗余
- 如果需要代理多个接口,需要修改原代理类或创建新的代理类,难以维护和拓展。
二、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 动态代理解读:
- 继承 Proxy 类(因此只能代理接口),并实现了 newProxyInstance 方法中第二个参数规定的代理接口。
- 通过 final 保证行为不可更改。
- 在静态代码块中通过反射拿到了代理接口的所有方法。
- 所有方法都是调用 super.h.invoke 方法,在 Proxy 父类中可以发现,super.h 正是 newProxyInstance 方法中第三个参数传入的 LogInvocationHandler 对象(代理的业务实现:日志记录)。
- 调用链:查询用户---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; }
五、总结
- 静态代理比较简单直观,但是会使编写的代码冗余,并且代理多个类时,难以维护和拓展。
- jdk 动态代理本质是实现了代理类实现的接口,因此无法代理非接口方法,但是原生无依赖。
- cglib 动态代理可以代理非接口方法,代码相比于 jdk 动态代理可读性更强。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于