1.1. 静态代理 优点:
缺点:
当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护
当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类
编写一个接口 UserService ,以及该接口的一个实现类 UserServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 public interface UserService { public void select () ; public void update () ; } public class UserServiceImpl implements UserService { public void select () { System.out.println("查询 selectById" ); } public void update () { System.out.println("更新 update" ); } }
通过静态代理对 UserServiceImpl 进行功能增强,写一个代理类 UserServiceProxy,代理类需要实现 UserService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class UserServiceProxy implements UserService { private UserService target; public UserServiceProxy (UserService target) { this .target = target; } public void select () { before(); target.select(); after(); } public void update () { before(); target.update(); after(); } private void before () { System.out.println(String.format("log start time [%s] " , new Date ())); } private void after () { System.out.println(String.format("log end time [%s] " , new Date ())); } }
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Client1 { public static void main (String[] args) { UserService userServiceImpl = new UserServiceImpl (); UserService proxy = new UserServiceProxy (userServiceImpl); proxy.select(); proxy.update(); } }
1.2. 动态代理 Java 虚拟机类加载时,操作类的二进制字节流,然后再加载到 JVM 中使用。
常见的字节码操作类库:
Apache BCEL (Byte Code Engineering Library)
:是 Java classworking 广泛使用的一种框架,它可以深入到 JVM 汇编语言进行类操作的细节。
ObjectWeb ASM
:是一个 Java 字节码操作框架。它可以用于直接以二进制形式动态生成 stub 根类或其他代理类,或者在加载时动态修改类。
CGLIB(Code Generation Library)
:是一个功能强大,高性能和高质量的代码生成库,用于扩展 JAVA 类并在运行时实现接口。
Javassist
:是 Java 的加载时反射系统,它是一个用于在 Java 中编辑字节码的类库; 它使 Java 程序能够在运行时定义新类,并在 JVM 加载之前修改类文件。
主要实现方式:
通过实现接口的方式 -> JDK动态代理
通过继承类的方式 -> CGLIB动态代理
1.2.1. JDK 动态代理 JDK 动态代理主要涉及两个类:java.lang.reflect.Proxy
和 java.lang.reflect.InvocationHandler
。主要过程是:
编写增强功能类,这里假设是日志增强,LogHandler 类,并实现 InvocationHandler 接口
在 LogHandler 中维护一个目标对象,这个对象是被代理的对象(真实主题角色)
在 invoke 方法中编写方法调用的逻辑处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 { before(); Object result = method.invoke(target, args); after(); return result; } private void before () { System.out.println(String.format("log start time [%s] " , new Date ())); } private void after () { System.out.println(String.format("log end time [%s] " , new Date ())); } }
编写客户端,获取动态生成的代理类的对象须借助 Proxy 类的 newProxyInstance 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Client2 { public static void main (String[] args) throws IllegalAccessException, InstantiationException { UserServiceImpl userServiceImpl = new UserServiceImpl (); ClassLoader classLoader = userServiceImpl.getClass().getClassLoader(); Class[] interfaces = userServiceImpl.getClass().getInterfaces(); InvocationHandler logHandler = new LogHandler (userServiceImpl); UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler); proxy.select(); proxy.update(); } }
1.2.2. CGLIB动态代理 CGLIB 创建动态代理类的模式是:
查找目标类上的所有非 final 的 public 类型的方法定义
将这些方法的定义转换成字节码
将组成的字节码转换成相应的代理的 class 对象
实现 MethodInterceptor 接口,用来处理对代理类上所有方法的请求
引入 CGLIB 包,编写一个UserDao类,它没有接口,只有两个方法,select() 和 update()
1 2 3 4 5 6 7 8 public class UserDao { public void select () { System.out.println("UserDao 查询 selectById" ); } public void update () { System.out.println("UserDao 更新 update" ); } }
编写一个 LogInterceptor ,继承了 MethodInterceptor,用于方法的拦截回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class LogInterceptor implements MethodInterceptor { @Override public Object intercept (Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object result = methodProxy.invokeSuper(object, objects); after(); return result; } private void before () { System.out.println(String.format("log start time [%s] " , new Date ())); } private void after () { System.out.println(String.format("log end time [%s] " , new Date ())); } }
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import net.sf.cglib.proxy.Enhancer;public class CglibTest { public static void main (String[] args) { DaoProxy daoProxy = new DaoProxy (); Enhancer enhancer = new Enhancer (); enhancer.setSuperclass(Dao.class); enhancer.setCallback(daoProxy); Dao dao = (Dao)enhancer.create(); dao.update(); dao.select(); } }
还可以进一步多个 MethodInterceptor 进行过滤筛选
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class LogInterceptor2 implements MethodInterceptor { @Override public Object intercept (Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object result = methodProxy.invokeSuper(object, objects); after(); return result; } private void before () { System.out.println(String.format("log2 start time [%s] " , new Date ())); } private void after () { System.out.println(String.format("log2 end time [%s] " , new Date ())); } } public class DaoFilter implements CallbackFilter { @Override public int accept (Method method) { if ("select" .equals(method.getName())) { return 0 ; } return 1 ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class CglibTest2 { public static void main (String[] args) { LogInterceptor logInterceptor = new LogInterceptor (); LogInterceptor2 logInterceptor2 = new LogInterceptor2 (); Enhancer enhancer = new Enhancer (); enhancer.setSuperclass(UserDao.class); enhancer.setCallbacks(new Callback []{logInterceptor, logInterceptor2, NoOp.INSTANCE}); enhancer.setCallbackFilter(new DaoFilter ()); UserDao proxy = (UserDao) enhancer.create(); proxy.select(); proxy.update(); } }
1.2.3. JDK 动态代理与 CGLIB 动态代理对比
JDK 动态代理
:基于 Java 反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。
cglib 动态代理
:基于 ASM 机制实现,通过生成业务类的子类作为代理类。
JDK 动态代理的优势:
最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠
平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用
代码实现简单
基于类似 cglib 框架的优势:
无需实现接口,达到代理类无侵入
只操作我们关心的类,而不必为其他相关类增加工作量
高性能