AOP: Aspect-Oriented Programming 面向切面编程。

首先明确一个点:AOP是一个概念。那么对于一个概念,其实现方式多种多样,分为静态AOP、动态AOP,而对于动态AOP的实现又分为动态代理和动态字节码增强实现。

这里分享的AOP主要是基于Spring的AOP实现。

AOP的一些基础概念

  • 连接点(Joinpoint):程序执行的某个特定位置,比如方法调用前、方法调用后、方法抛出异常后等,这些点都可以是连接点。

  • 切点(PointCut):一个程序有若干个连接点,并不是所有的连接点都是需要关注的,需要我们关注的那部分连接点就是切点。

  • 通知(增强)(Advice):就是在切点处要插入的逻辑;分为前置、后置、异常、返回、环绕通知;

  • 切面(Advice):切点和通知组合在一起形成切面,对应的切面横切整个应用程序。切面实现了横切关注点的模块化。

  • 目标对象(Target):需要被增强的业务对象。

  • 织入(Weaving):将通知增强添加到目标对象的具体连接点的过程。具体来说,就是生成代理对象并将切面融入到业务流程的过程。

  • 代理类(Proxy):一个类在被织入器织入增强逻辑后产生的一个类就是代理类。

上面这些点在Spring中的体现就是一个一个的注解。

Spring AOP的使用方式

比如我们现在有一个除法计算类,这个方法计算可能会抛错,在抛错的时候我们需要记录一下日志,那么使用方式如下:

(说明:很多地方在介绍AOP的时候,都喜欢以日志来当做案例,但是日志记录并不是一个好的AOP使用场景,因为系统中各个日志记录点和记录格式不具有通用性。)

  • 首先定义实现方法:

    1. public class MathCalculator {
    2. public int div(int i, int j) {
    3. return i / j;
    4. }
    5. }
  • 然后定义切面:

    1. @Aspect
    2. public class LogAspects {
    3. @Pointcut("execution(public int MathCalculator.*(..))")
    4. public void poinCut() {
    5. }
    6. @Around("poinCut()")
    7. public Object logArround(ProceedingJoinPoint joinPoint) throws Throwable {
    8. System.out.println("环绕开始!");
    9. try {
    10. return joinPoint.proceed();
    11. } catch (Exception e) {
    12. System.out.println("环绕异常!");
    13. throw e;
    14. } finally {
    15. System.out.println("环绕结束!");
    16. }
    17. }
    18. }

我们在切面中定义了一个环绕通知,这样就对MathCalculator的div方法调用进行了环绕处理。

Spring AOP的实现方式

上面提到动态AOP有两种实现方式:动态代理和动态字节码增强。

在Spring中主要是通过动态代理实现的:基于JDK的动态代理和基于CGLIB的动态代理。

要了解Spring AOP的实现原理,只要把上面两个技术了解后就能明白相应的原理了。

JDK动态代理

  • 1、JDK动态代理是基于接口的,所以我们首先来创建一个接口

    1. /**
    2. * 抽象主题接口
    3. **/
    4. public interface Subject {
    5. void doSomething();
    6. }
  • 2、创建对应接口的实现类

    1. /**
    2. * 真实主题类
    3. **/
    4. public class RealSubject implements Subject {
    5. @Override
    6. public void doSomething() {
    7. System.out.println("doSomething....");
    8. }
    9. }
  • 3、创建代理类,实现 java.lang.reflect.InvocationHandler 接口

    1. import java.lang.reflect.InvocationHandler;
    2. import java.lang.reflect.Method;
    3. import java.lang.reflect.Proxy;
    4. /**
    5. * jdkd动态代理类
    6. **/
    7. public class JDKDynamicProxy implements InvocationHandler {
    8. private Object target;
    9. public JDKDynamicProxy(Object target) {
    10. this.target = target;
    11. }
    12. /**
    13. * 获取被代理接口实例对象
    14. */
    15. public <T> T getProxy() {
    16. return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    17. }
    18. @Override
    19. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    20. System.out.println("Do something before");
    21. Object result = method.invoke(target, args);
    22. System.out.println("Do something after");
    23. return result;
    24. }
    25. }
  • 4、创建测试类

    1. import com.lnjecit.proxy.dynamic.jdk.JDKDynamicProxy;
    2. /**
    3. * 测试类
    4. **/
    5. public class Ceshi {
    6. public static void main(String[] args) {
    7. Subject subject = new JDKDynamicProxy(new RealSubject()).getProxy();
    8. subject.doSomething();
    9. }
    10. }

像这样运行Ceshi类的main方法就能发现对应的doSomething()方法被我们的逻辑进行了拦截。

  • 5、动态代理原理

    我们打开生成的代理类,可以看到内容如下:

    1. import com.lnjecit.proxy.Subject;
    2. import java.lang.reflect.InvocationHandler;
    3. import java.lang.reflect.Method;
    4. import java.lang.reflect.Proxy;
    5. import java.lang.reflect.UndeclaredThrowableException;
    6. public final class $Proxy0 extends Proxy implements Subject {
    7. private static Method m1;
    8. private static Method m3;
    9. private static Method m2;
    10. private static Method m0;
    11. public $Proxy0(InvocationHandler var1) throws {
    12. super(var1);
    13. }
    14. public final void doSomething() throws {
    15. try {
    16. super.h.invoke(this, m3, (Object[])null);
    17. } catch (RuntimeException | Error var2) {
    18. throw var2;
    19. } catch (Throwable var3) {
    20. throw new UndeclaredThrowableException(var3);
    21. }
    22. }
    23. // 为了精简代码,下面省略了重写的 equals、hashCode、toString、toString 方法
    24. static {
    25. try {
    26. m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
    27. m3 = Class.forName("com.lnjecit.proxy.Subject").getMethod("doSomething");
    28. m2 = Class.forName("java.lang.Object").getMethod("toString");
    29. m0 = Class.forName("java.lang.Object").getMethod("hashCode");
    30. } catch (NoSuchMethodException var2) {
    31. throw new NoSuchMethodError(var2.getMessage());
    32. } catch (ClassNotFoundException var3) {
    33. throw new NoClassDefFoundError(var3.getMessage());
    34. }
    35. }
    36. }

    可以看到,动态生成的代理类有如下特性:

    1、继承了Proxy类,实现了代理的接口,由于java不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。

    2、提供了一个使用InvocationHandler作为参数的构造方法。

    3、生成静态代码块来初始化接口中方法的Method对象,以及Object类的equals、hashCode、toString方法。

    4、重写了Object类的equals、hashCode、toString,它们都只是简单的调用了InvocationHandler的invoke方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法。

    5、代理类实现代理接口的sayHello方法中,只是简单的调用了InvocationHandler的invoke方法,我们可以在invoke方法中进行一些特殊操作,甚至不调用实现的方法,直接返回。

    上面的InvocationHandler就是我们实现横切逻辑的地方,它是横切逻辑的载体。

CGLIB动态代理

CGLIB使用如下:

  • 1、定义代理目标类

    1. public class UserService {
    2. public String getUserName(Long userId) {
    3. System.out.println("获取用户名..");
    4. return "user" + userId;
    5. }
    6. }
  • 2、实现MethodInterceptor,定义事务拦截器

    1. public class TransactionInterceptor implements MethodInterceptor {
    2. Object target;
    3. public TransactionInterceptor(Object target) {
    4. this.target = target;
    5. }
    6. /**
    7. * proxy:代理对象,CGLib动态生成的代理类实例
    8. * method:目标对象的方法,上文中实体类所调用的被代理的方法引用
    9. * args:目标对象方法的参数列表,参数值列表
    10. * methodProxy:代理对象的方法,生成的代理类对方法的代理引用
    11. */
    12. @Override
    13. public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    14. System.out.println("开启事务..." + proxy.getClass().getSimpleName());
    15. Object objValue = null;
    16. try {
    17. // 反射调用目标类方法
    18. objValue = method.invoke(target, args);
    19. System.out.println("返回值为:" + objValue);
    20. } catch (Exception e) {
    21. System.out.println("调用异常!" + e.getMessage());
    22. } finally {
    23. System.out.println("调用结束,关闭事务...");
    24. }
    25. return objValue;
    26. }
    27. /**
    28. * 获取代理实例
    29. */
    30. public Object getTargetProxy() {
    31. // Enhancer类是cglib中的一个字节码增强器,它可以方便的为你所要处理的类进行扩展
    32. Enhancer eh = new Enhancer();
    33. // 1.将目标对象所在的类作为Enhancer类的父类
    34. eh.setSuperclass(target.getClass());
    35. // 2.通过实现MethodInterceptor实现方法回调
    36. eh.setCallback(this);
    37. // 3. 创建代理实例
    38. return eh.create();
    39. }
    40. }
  • 3、使用

    1. public static void main(String[] args) {
    2. // 1. 创建目标实例
    3. UserService userService = new UserService();
    4. // 2. 创建事务拦截器
    5. TransactionInterceptor transactionInterceptor = new TransactionInterceptor(userService);
    6. // 3. 创建代理实例
    7. UserService userServiceProxy = (UserService) transactionInterceptor.getTargetProxy();
    8. // 4. 使用代理实例调用目标方法
    9. userServiceProxy.getUserName(6L);
    10. }

说明:CGLIB是基于继承来实现的。它首先创建一个类,继承代理目标类,然后重写其中的方法,然后在重写的方法中将调用委托给目标对象,然后在委托调用前后实现横切逻辑。

通过上面两个例子可以看出,JDK的动态代理不是通过继承实现的,那么就是通过实现接口实现的。而CGLIB则是通过动态生成代理类的子类来实现的。

AOP随笔的更多相关文章

  1. Aspectj 实现Method条件运行

    最近我花了半个小时实现了一个Method的按自定义条件运行的plugin,Condition-Run.实现场景是由于我所工作的客户经常会是在同一个代码集上实现多个Brand,所以有些功能只会限制是几个 ...

  2. SSM框架之AOP、动态代理、事务处理相关随笔

    AOP: 原理:底层利用动态代理(两种动态代理技术都使用了) 两种实现方案: 第一种:JDK动态代理技术 实现的InvocationHandler接口,要想实现某个类的动态代理对象,必须有接口有实现类 ...

  3. Spring随笔-核心知识DI与AOP

    DI 依赖注入,使得相互依赖的组件松耦合. AOP 面向切面编程,使各种功能分离出来,形成可重用的组件.

  4. 自己实现简单的AOP(三) 实现增强四项基本功能

    前面的两篇随笔,都是只是个铺垫,真正实现增强四项基本功能的重头戏,在本篇随笔中, 本文将通过AOP实现如下的四个基本功能: /// <para>1.自动管理数据库连接[可选]</pa ...

  5. 自己实现简单的AOP(四)自动初始化代理对象

    前面三篇随笔,已经完成了AOP的核心功能,但 代理对象的初始化还是有些麻烦,本文将解决该问题. Demo 片段如下: public class HomeController : Controller ...

  6. Spring 3.0 AOP (一)AOP 术语

    关于AOP.之前我已写过一个系列的随笔: <自己实现简单的AOP>,它的关注点在于实现.实现语言是C#,实现方式为 自定义实现 RealProxy 抽象类.重写Invoke方法,以便进行方 ...

  7. 转载:Spring AOP (下)

    昨天记录了Spring AOP学习的一部分(http://www.cnblogs.com/yanbincn/archive/2012/08/13/2635413.html),本来是想一口气梳理完的.但 ...

  8. 转载:Spring AOP (上)

    工 作忙,时间紧,不过事情再多,学习是必须的.记得以前的部门老大说过:“开发人员不可能一天到晚只有工作,肯定是需要自我学习.第一:为了更充实自己,保 持进步状态.第二:为了提升技术,提高开发能力.第三 ...

  9. Spring学习总结(三)——Spring实现AOP的多种方式

    AOP(Aspect Oriented Programming)面向切面编程,通过预编译方式和运行期动态代理实现程序功能的横向多模块统一控制的一种技术.AOP是OOP的补充,是Spring框架中的一个 ...

随机推荐

  1. JS中EventLoop、宏任务与微任务的个人理解

    为什么要EventLoop? JS 作为浏览器脚本语言,为了避免复杂的同步问题(例如用户操作事件以及操作DOM),这就决定了被设计成单线程语言,而且也将会一直保持是单线程的.而在单线程中若是遇到了耗时 ...

  2. 四、MYSQL数据练习题

    我的MYSQL版本是mysql-5.7.24-winx64,每天练习5道习题. 如果有错误或者更优的解决方法,欢迎大家指出,谢谢!! 一.测试表格 --1.学生表Student(Sid,Sname,S ...

  3. Apache SkyWalking 告警配置指南

    Apache SkyWalking Apache SkyWalking是分布式系统的应用程序性能监视工具(Application Performance Management,APM),专为微服务.云 ...

  4. Spring Security OAuth 格式化 token 输出

    个性化token 背景 上一篇文章<Spring Security OAuth 个性化token(一)>有提到,oauth2.0 接口默认返回的报文格式如下: {     "ac ...

  5. Ananagrams UVA - 156

      Most crossword puzzle fans are used to anagrams - groups of words with the same letters in differe ...

  6. OO第四单元总结与课程总结

    OO第四单元总结与课程总结 第四单元作业架构设计 总体分析:本单元作业的需求集中于对UML类图进行查询.对于查询操作来说自然的想法是提前预见到需要查询的内容,在一开始就采用适当的数据结构将必要的信息进 ...

  7. Linux连接Windows服务器以及文件传输方法

    Ubantu系统上连接Windows服务器,操作步骤 安装rdesktop sudo apt-get install rdesktop 连接命令 rdesktop -f IP -r disk:mydi ...

  8. OD调试程序常用断点大全

    常用断点  拦截窗口:  bp CreateWindow 创建窗口  bp CreateWindowEx(A) 创建窗口  bp ShowWindow 显示窗口  bp UpdateWindow 更新 ...

  9. POJ1703带权并查集(距离或者异或)

    题意:       有两个黑社会帮派,有n个人,他们肯定属于两个帮派中的一个,然后有两种操作 1 D a b 给出a b 两个人不属于同一个帮派 2 A a b 问a b 两个人关系 输出 同一个帮派 ...

  10. WindowsPE 第七章 资源表

    资源表 在程序设计中,总会设计一些数据.这些数据可能是源代码内部需要用到的常量,菜单选项.界面描述等:也可能是源代码外部的,比如程序的图标文件.北京音乐文件.配置文件等,以上这些数据统称为资源.按照程 ...