Java 动态代理
动态代理简介
在不改变原有功能代码的前提下,能够动态的实现方法的增强,通过代理转移部分职责,使得类的代码职责单一,只专注于自己本身的工作,对于非业务代码,如请求用时记录、事务、鉴权、异常处理等,通过代理对象对目标对象的功能进行扩展,同时减少代码冗余,这也体现了open-close principle。生活中有很多代理案例,比如房屋中介或是商品代购或是秘书工作等。代理在java中的应用比如统一的异常处理、MyBatis、SpringAOP等。
静态代理
静态代理实际上是创建了一个代理类,在代理类的内部先执行增强的功能,在执行被代理类的方法,代理类和被代理类会实现同一个接口来规范行为。
缺点:
- 代理类和被代理类实现了相同的接口,有较多的代码冗余
- 一个代理类只能对一个被代理类的方法进行增强,如果要对另外一个Service类也实现日志打印、执行时间计算等功能,必须在重新创建一个代理类,代码不能复用。
下面是静态代理的一个事务处理的简单案例:
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
public interface StudentService { void save(); void query(Integer id); }
public class StudentServiceImpl implements StudentService { public void save() { System.out.println("save操作"); } public Student query(Integer id) { System.out.println("query操作"); return new Student(); } }
public class Transaction { public void before(){ System.out.println("开启事务"); } public void after() { System.out.println("关闭事务"); } }
public class ProxyStudent implements StudentService { private StudentServiceImpl studentService; private Transaction transaction; public ProxyStudent(StudentServiceImpl studentService, Transaction transaction) { this.studentService = studentService; this.transaction = transaction; } public void save() { transaction.before(); studentService.save(); transaction.after(); } public Student query(Integer id) { return studentService.query(id); } }
|
JDK动态代理
JDK动态代理会在运行时为目标类生成一个动态代理类$Proxy*.class
该代理类会实现目标类的接口,并且代理类会实现接口中的所有的方法增强代码
调用时通过代理类先去调用处理类进行增强,在通过反射的方式调用目标方法
Proxy.newProxyInstance()
JDK动态代理通过Proxy.newProxyInstance()来创建代理对象
1 2
| public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
|
该方法的三个参数:
- ClassLoader loader:表示类加载器,用于加载生成的代理类。可以通过目标类的 ClassLoader 实例或者其他合适的类加载器来指定。
- Class<?>[] interfaces:表示代理类要实现的
接口列表。这是一个数组,指定了代理类要实现的接口,通过代理对象调用接口中的方法时,实际上会被转发给 InvocationHandler 的 invoke() 方法处理。
- InvocationHandler h:表示代理对象的调用处理程序,也是一个实现 InvocationHandler 接口的对象。InvocationHandler 接口中只有一个方法 invoke(),当代理对象的方法被调用时,会被传递到 invoke() 方法中进行处理。
invoke()
- Object proxy:代理对象本身。在 invoke() 方法中,可以使用 proxy 参数来调用代理对象的其他方法,或者在特定情况下使用代理对象本身。
- Method method:被调用的方法对象。method 参数表示当前正在调用的方法,通过它可以获取方法的名称、参数等信息。
- Object[] args:方法的参数数组。args 参数是一个对象数组,表示传递给方法的参数。通过这个数组,可以获取方法调用时传递的具体参数值。
简单使用案例
下面是一个简单的例子:
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 27 28 29 30 31
| public class LogServiceProxy implements InvocationHandler { private Object targetObj;
public LogServiceProxy(Object targetObj){ this.targetObj = targetObj; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method.getName()+",开始执行"); method.invoke(targetObj,args); System.out.println(method.getName()+",执行成功"); return null; } }
@Test public void test(){ IUserService userService = new UserServiceImpl(); LogServiceProxy logServiceProxy = new LogServiceProxy(userService); IUserService userServiceProxy= (IUserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(),logServiceProxy); }
|
CGLIB动态代理
CGLIB的底层是通过ASM在运行时动态的生成目标类的一个子类,代理类继承了被代理类。(还有其他相关类,主要是为了提高调用时效率)会生成多个
并且会重写父类的所有方法增强代码
调用时先通过代理类进行增强,在直接调用父类对应的方法进行调用目标方法,CGLIB是通过继承方式做的动态代理,因此如果某个类被标记为final,那么他是无法使用CGLIB做动态代理的
CGLIB除了生成目标子类代理之外,还有一个FastClass(路由类),通过路由类再次调用代理对象中的方法,可以(但不是必须)让本类方法调用进行增强,而不会像jdk代理那样本类方法之间相互调用增强会失效
使用cglib动态代理之前先导入maven依赖:
1 2 3 4 5 6
| <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
|
下面是cglib动态代理的一个简单案例:(此测试案例使用jdk8,使用高版本报错🤡)
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| public interface UserService { void save(); void query(); }
public class UserServiceImpl implements UserService { @Override public void save() { System.out.println("保存操作"); }
@Override public void query() { System.out.println("查询操作"); } }
public class LogServiceByCglib implements MethodInterceptor { private Object targetObject;
public LogServiceByCglib(Object targetObject) { this.targetObject = targetObject; } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println(method.getName() + "开始执行"); method.invoke(targetObject, objects); System.out.println(method.getName() + "结束执行"); return null; } }
public class test { public static void main(String[] args) { UserService userService = new UserServiceImpl(); LogServiceByCglib logServiceByCglib = new LogServiceByCglib(userService); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserService.class); enhancer.setCallback(logServiceByCglib); UserService proxyUserService = (UserService)enhancer.create(); proxyUserService.query(); } }
|
两者的主要区别
- JDK动态代理只能提供接口的代理,不支持类的代理;如果没有实现接口Spring AOP选择cglib进行动态代理。
- JDK动态代理是通过jdk内部生成字节码$proxy*.class;cglib通过ASM这样一个开源的字节码生成库生成代理类,除了代理类还会生成其他相关的类。
- JDK动态代理中代理类实现目标类的接口;cglib中代理类将目标类作为父类继承。
- JDK动态代理调用目标方法是通过反射的方式进行调用;cglib通过子类调用父类方法调用目标方法。