如何从动态代理实现到Spring AOP

90次阅读
没有评论

共计 4999 个字符,预计需要花费 13 分钟才能阅读完成。

如何从动态代理实现到 Spring AOP,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

丸趣 TV 小编主要讲了 Spring Aop 动态代理实现的两种方式。

1. Spring AOP

Spring 是一个轻型容器,Spring 整个系列的最最核心的概念当属 IoC、AOP。可见 AOP 是 Spring 框架中的核心之一,在应用中具有非常重要的作用,也是 Spring 其他组件的基础。AOP(Aspect Oriented Programming),即面向切面编程,可以说是 OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP 引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过 OOP 允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。

关于 AOP 的基础知识,并不是本文的重点,我们主要来看下 AOP 的核心功能的底层实现机制:动态代理的实现原理。AOP 的拦截功能是由 java 中的动态代理来实现的。在目标类的基础上增加切面逻辑,生成增强的目标类(该切面逻辑或者在目标类函数执行之前,或者目标类函数执行之后,或者在目标类函数抛出异常时候执行。不同的切入时机对应不同的 Interceptor 的种类,如 BeforeAdviseInterceptor,AfterAdviseInterceptor 以及 ThrowsAdviseInterceptor 等)。

那么动态代理是如何实现将切面逻辑(advise)织入到目标类方法中去的呢?下面我们就来详细介绍并实现 AOP 中用到的两种动态代理。

AOP 的源码中用到了两种动态代理来实现拦截切入功能:jdk 动态代理和 cglib 动态代理。两种方法同时存在,各有优劣。jdk 动态代理是由 java 内部的反射机制来实现的,cglib 动态代理底层则是借助 asm 来实现的。总的来说,反射机制在生成类的过程中比较高效,而 asm 在生成类之后的相关执行过程中比较高效(可以通过将 asm 生成的类进行缓存,这样解决 asm 生成类过程低效问题)。

下面我们分别来示例实现这两种方法。

2. JDK 动态代理 2.1 定义接口与实现类

public interface OrderService { public void save(UUID orderId, String name);
 public void update(UUID orderId, String name);
 public String getByName(String name);
}

上面代码定义了一个被拦截对象接口,即横切关注点。下面代码实现被拦截对象接口。

public class OrderServiceImpl implements OrderService {
 private String user = null;
 public OrderServiceImpl() { }
 public OrderServiceImpl(String user) { this.setUser(user);
 }
 //...
 
 @Override
 public void save(UUID orderId, String name) { System.out.println( call save() 方法,save:  + name);
 }
 @Override
 public void update(UUID orderId, String name) { System.out.println( call update() 方法 
 }
 @Override
 public String getByName(String name) { System.out.println( call getByName() 方法 
 return  aoho 
 }
}

2.2 JDK 动态代理类

public class JDKProxy implements InvocationHandler {
 // 需要代理的目标对象
 private Object targetObject;
 
 public Object createProxyInstance(Object targetObject) {
 this.targetObject = targetObject;
 return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),
 this.targetObject.getClass().getInterfaces(), this);
 }
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 // 被代理对象
 OrderServiceImpl bean = (OrderServiceImpl) this.targetObject;
 Object result = null;
 // 切面逻辑(advise),此处是在目标类代码执行之前
 System.out.println( ---before invoke---- 
 if (bean.getUser() != null) { result = method.invoke(targetObject, args);
 }
 System.out.println( ---after invoke---- 
 return result;
 }
 //...
}

上述代码实现了动态代理类 JDKProxy,实现 InvocationHandler 接口,并且实现接口中的 invoke 方法。当客户端调用代理对象的业务方法时,代理对象执行 invoke 方法,invoke 方法把调用委派给 targetObject,相当于调用目标对象的方法,在 invoke 方法委派前判断权限,实现方法的拦截。

2.3 测试

public class AOPTest { public static void main(String[] args) { JDKProxy factory = new JDKProxy();
 //Proxy 为 InvocationHandler 实现类动态创建一个符合某一接口的代理实例  
 OrderService orderService = (OrderService) factory.createProxyInstance(new OrderServiceImpl( aoho));
 // 由动态生成的代理对象来 orderService  代理执行程序
 orderService.save(UUID.randomUUID(),  aoho 
 }
}

结果如下:

---before invoke----
call save() 方法,save:aoho
---after invoke----

3. CGLIB 字节码生成 3.1 要代理的类

CGLIB 既可以对接口的类生成代理,也可以针对类生成代理。示例中,实现对类的代理。

public class OrderManager {
 private String user = null;
 public OrderManager() { }
 public OrderManager(String user) { this.setUser(user);
 }
 //...
 public void save(UUID orderId, String name) { System.out.println( call save() 方法,save:  + name);
 }
 public void update(UUID orderId, String name) { System.out.println( call update() 方法 
 }
 public String getByName(String name) { System.out.println( call getByName() 方法 
 return  aoho 
 }
}

该类的实现和上面的接口实现一样,为了保持统一。

3.2 CGLIB 动态代理类

public class CGLibProxy implements MethodInterceptor {
 // CGLib 需要代理的目标对象
 private Object targetObject;
 public Object createProxyObject(Object obj) {
 this.targetObject = obj;
 Enhancer enhancer = new Enhancer();
 enhancer.setSuperclass(obj.getClass());
 // 回调方法的参数为代理类对象 CglibProxy,最后增强目标类调用的是代理类对象 CglibProxy 中的 intercept 方法  
 enhancer.setCallback(this);
 // 增强后的目标类
 Object proxyObj = enhancer.create();
 //  返回代理对象
 return proxyObj;
 }
 @Override
 public Object intercept(Object proxy, Method method, Object[] args,
 MethodProxy methodProxy) throws Throwable {
 Object obj = null;
 // 切面逻辑(advise),此处是在目标类代码执行之前
 System.out.println( ---before intercept---- 
 obj = method.invoke(targetObject, args);
 System.out.println( ---after intercept---- 
 return obj;
 }
}

上述实现了创建子类的方法与代理的方法。getProxy(SuperClass.class) 方法通过入参即父类的字节码,扩展父类的 class 来创建代理对象。intercept() 方法拦截所有目标类方法的调用,obj 表示目标类的实例,method 为目标类方法的反射对象,args 为方法的动态入参,methodProxy 为代理类实例。method.invoke(targetObject, args) 通过代理类调用父类中的方法。

3.3 测试

public class AOPTest { public static void main(String[] args) { OrderManager order = (OrderManager) new CGLibProxy().createProxyObject(new OrderManager( aoho));
 order.save(UUID.randomUUID(),  aoho 
 }

结果如下:

---before intercept----
call save() 方法,save:aoho
---after intercept----

4. 总结

主要讲了 Spring Aop 动态代理实现的两种方式,并分别介绍了其优缺点。jdk 动态代理的应用前提是目标类基于统一的接口。如果没有该前提,jdk 动态代理不能应用。由此可以看出,jdk 动态代理有一定的局限性,cglib 这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。

JDK 动态代理机制是委托机制,不需要以来第三方的库,只要要 JDK 环境就可以进行代理,动态实现接口类,在动态生成的实现类里面委托为 hanlder 去调用原始实现类方法;CGLib 必须依赖于 CGLib 的类库,使用的是继承机制,是被代理类和代理类继承的关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。

关于如何从动态代理实现到 Spring AOP 问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注丸趣 TV 行业资讯频道了解更多相关知识。

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-25发表,共计4999字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)