AOP详解

1、什么是AOP?

AOP(Aspect Oriented Programming):面向切面编程,降低业务逻辑各部分之间的耦合度,提高程序可重用性,提高开发效率。

2、AOP能干什么?

日志记录、性能统计、性能调优、安全控制、权限管理、事务处理、异常处理、资源池管理、缓存处理、同步持久化等。

3、我太难了!看了没懂!

好吧!以记录日志为例。

原始时代:在做日志处理的时候,在每个方法中添加日志处理。

没工具自己搞

石器时代:为了代码复用,把日志处理抽离成一个新的方法。仍然手动插入这些方法。

有工具也污染

牛逼时代:通过动态代理,将一些横向的功能抽离成一个独立的模块,在指定位置插入这些功能。在指定位置执行对应流程。这样的思想,亦即AOP。

存绿色无污染

4、这么牛逼怎么玩耍的?

一言不合丢代码。。。。。。

POM需要依赖

Spring需要引入,SpringBoot不需要

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
    <version>${spring.version}</version>
  </dependency>
  • Spring使用XML装配方式

  1. 首先定义注解。
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckVersion {
}
  1. 保存订单业务逻辑,添加注解。
public class OrderService {
    // 使用CheckVersion注解,saveOrder是切点。
    @CheckVersion
    public OrderVO saveOrder(OrderVO order) {
      ...
    }
}
  1. AOP增强方法。
/**
 * 版本号检查增强方法
 */
public class CheckVersionAdvice {

    @Autowired
    private OrderDAO orderDAO;

    /**
     * 保存之前做订单版本校验,不一致不能保存
     * @param param
     */
    public void checkVersion(OrderVO param) {
        if (StringUtils.isBlank(param.getOrderId())) {
            return;
        }
        Optional<Order> optional = orderDAO.findById(param.getOrderId());
        Order order = optional.get();
        if (!order.getVersion().equals(param.getVersion())) {
            throw new BusinessException(getConstantValue("不是最新订单!"));
        }
    }
}
  1. spring-aop.xml可以参照如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.springframework.org/schema/aop
          http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--可以使用注解自动装配-->
    <aop:aspectj-autoproxy/>

    <!--如下XML配置AOP,如果类中使用@Aspect如下配置可以省略 -->
    <bean id="checkVersionAdvice" class="com.xyz.demo.CheckVersionAdvice"/>

    <aop:config>
      <!--AOP通知增强方法-->
      <aop:aspect id="versionCheck" ref="checkVersionAdvice" order="1">
        <!--配置AOP切点及增强类型-->
        <aop:before method="checkVersion"
            pointcut="execution(* com.xyz.demo..service..*Service.*(..)) 
              and args(param) and @annotation(com.xyz.demo.CheckVersion)" />
      </aop:aspect>
    </aop:config>

</beans>
  • SpringBoot 用注解自动装配

  1. 测试类:TestController
@RestController
public class TestController {
  @RequestMapping("/{msg}")
  public String hello(@PathVariable("msg") String message) {
     System.out.println(message);
     return message;
  }
}

启动SpringBoot,在浏览器执行http://localhost:8080/Hello,SpringBoot!
返回结果:

Hello,SpringBoot!
  1. 切面类:LogAspect
@Component 
@Aspect  
public class LogAspect {  
  @Pointcut("execution(* com.example.demo.controller..*(..))")  
  private void pointCut() { }    

  @Before("pointCut()")    
  public void before(JoinPoint joinPoint) {  
    String className = joinPoint.getTarget().getClass().getName();   
    String methodName = joinPoint.getSignature().getName();     
    System.out.println(className + "." + methodName + "() 开始!");   
  }  

 @After("pointCut()") 
 public void after(JoinPoint joinPoint) {  
   String className = joinPoint.getTarget().getClass().getName();  
   String methodName = joinPoint.getSignature().getName();  
   System.out.println(className + "." + methodName + "() 结束!");  
  }
}

加人AOP输出结果:

com.example.demo.controller.TestController.hello() 开始!
Hello,SpringBoot!
com.example.demo.controller.TestController.hello() 结束!

哇塞!不用配置XML喽!我的Controller里一点日志代码也没加耶!!!

5、这些注解都是什么鬼?

让我们先了解下边这些名词:


  • TargetObject目标对象
    指被切入的对象。
    例子中,TestController是目标对象。
    例子中,LogAspect就是切面。
  • JoinPoint连接点
    程序执行中的某个点、某个位置。
    例子中,hello()是连接点。
  • PointCut切点
    切面匹配连接点的点,与切点表达式相关,切面如何切点。
    例子中,@PointCut注解就是切点表达式,匹配对应的连接点。
  • Aspect切面
    一个关注点的模块。
  • Advice通知
    指在切面的某个特定的连接点上执行的动作。
    例子中,before()与after()方法中的代码。
  • Weave织入
    将Advice作用在JoinPoint的过程。

看着有点蒙圈,这么名词直接有啥关系呢?

连接点切点通知
AOP详细图解

6、怎么才能玩的溜?

要想玩的溜,继续往下看!

Aop核心类大致分为三类:
  • advisorCreator:默认情况下只使用一种代理机制,主要用来扫描获取advisor。
  • advisor顾问:切面的体现形式,封装AOP切点和通知。LogAspect中的@Pointcut、@before和@after就是Advisor。
  • advice通知:AOP中增强的方法。LogAspect中before()和after()方法是通知。
切面提供 5 种通知类型:

Before:在方法调用之前调用通知。
After:在方法完成之后调用通知,无论方法执行成功与否。
After-returning:在方法执行成功之后调用通知。
After-throwing:在方法抛出异常后进行通知。
Around:包裹了被通知方法,在方法调用之前和调用之后执行自定义的行为。


切点表达式分类:
指示器 描述
arg () 限制连接点的指定参数为指定类型的执行方法
@args () 限制连接点匹配参数由指定注解标注的执行方法
execution() 用于匹配连接点的执行方法
this () 限制连接点匹配 AOP 代理的 Bean 引用为指定类型的类
target() 限制连接点匹配特定执行对象,这些对象对应类要具备指定类型注解
within() 限制连接点匹配指定类型
@within() 限制连接点匹配指定注释所标注的类型
@annotation 限制匹配带有指定注释的连接点
让你牛掰让你飞
execution指示器简介
指示器结构
  1. 开头* 号标识了我们不关心的方法返回值的类型。
  2. 后我们指定了类名和方法名。
  3. (..)标识切点选择任意的 play( ) 方法,无论入参是什么。
execution指示器进阶
复杂应用场景

假设仅匹配 com.Springinaction.springidol 包。
可以使用 within()注意 && 是将 execution()和 within()连接起来,形成的 and 关系。同理也可以使用 || 或关系、!非关系。

7、我要飞得更高

获取参数的值和方法名称

AspectJ使用JoinPoint接口表示目标类连接点对象,如果是环绕增强时,使用ProceedingJoinPoint表示连接点对象,该类是JoinPoint的子接口。
我们先来了解一下这两个接口的主要方法:

  1. JoinPoint
  • Object[] getArgs():获取连接点方法运行时的入参列表;
  • Signature getSignature() :获取连接点的方法签名对象;
  • Object getTarget() :获取连接点所在的目标对象;
  • Object getThis() :获取代理对象本身;
  1. ProceedingJoinPoint
    继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法:
  • Object proceed() throws Throwable:通过反射执行目标对象的连接点处的方法;
  • Object proceed(Object[] args) throws Throwable:通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。
切点表达式
execute表达式
  • 拦截任意公共方法
    execution(public * *(..))
  • 拦截以set开头的任意方法
    execution(* set*(..))
  • 拦截类或者接口中的方法
    execution(* com.xyz.service.AccountService.*(..))
    拦截AccountService(类、接口)中定义的所有方法
  • 拦截包中定义的方法,不包含子包中的方法
    execution(* com.xyz.service..(..))
    拦截com.xyz.service包中所有类中任意方法,不包含子包中的类
  • 拦截包或者子包中定义的方法
    execution(* com.xyz.service...(..))
    拦截com.xyz.service包或者子包中定义的所有方法
within表达式
  • 拦截包中任意方法,不包含子包中的方法
    within(com.xyz.service.*)
    拦截service包中任意类的任意方法
  • 拦截包或者子包中定义的方法
    within(com.xyz.service..*)

拦截service包及子包中任意类的任意方法

this表达式

代理对象为指定的类型会被拦截
this(com.xyz.service.AccountService)
被spring代理之后生成的对象必须为ServiceImpl才会被拦截。

target表达式

目标对象为指定的类型被拦截
target(com.xyz.service.AccountService)
目标对象为AccountService类型的会被代理。

args 表达式

匹配方法中的参数

  • 匹配只有一个参数,且类型UserModel
    @Pointcut("args(com.xyz.demo.UserModel)")
  • 匹配多个参数
    args(type1,type2,typeN)
  • 匹配任意多个参数
    @Pointcut("args(com.xyz.demo.UserModel,..)")
    匹配第一个参数类型为UserModel的所有方法, .. 表示任意个参数。
@target表达式

匹配的目标对象的类有一个指定的注解
@target(com.xyz.demo.Annotation1)
目标对象中包含Annotation1注解,调用该目标对象的任意方法都会被拦截。

@within表达式

指定匹配必须包含某个注解的类里的所有连接点
@within(com.xyz.demo.Annotation1)
声明有Annotation1注解的类中的所有方法都会被拦截。

@annotation表达式

匹配有指定注解的方法
@annotation(com.xyz.demo.Annotation1)
被调用的方法包含指定的注解。

@args表达式

方法参数所属的类型上有指定的注解,被匹配
注意:是方法参数所属的类型上有指定的注解,不是方法参数中有注解

8、修仙秘籍

Spring AOP的流程
  1. Spring加载自动代理器AnnotationAwareAspectJAutoProxyCreator,当作一个系统组件。spring aop 开启注解方式之后,该类会扫描所有@Aspect()标准的类,生成对应的adviosr。目前SpringBoot框架中默认支持的方式,自动配置。
  2. 当一个bean加载到Spring中时,会触发自动代理器中的bean后置处理
  3. bean后置处理,会先扫描bean中所有的Advisor
  4. 然后用这些Adviosr和其他参数构建ProxyFactory
  5. ProxyFactory会根据配置和目标对象的类型寻找代理的方式(JDK动态代理或CGLIG代理)
  6. 然后代理出来的对象放回context中,完成Spring AOP代理。
AOP源码分析

Spring AOP 实现机制

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容