Spring AOP基本概念

  1. 是一种动态编译期增强性AOP的实现
  2. 与IOC进行整合,不是全面的切面框架
  3. 与动态代理相辅相成
  4. 有两种实现:基于jdk动态代理、cglib

Spring AOP与AspectJ区别

  1. Spring的AOP是基于动态代理的,动态增强目标对象,而AspectJ是静态编译时增强,需要使用自己的编译器来编译,还需要织入器
  2. 使用AspectJ编写的java代码无法直接使用javac编译,必须使用AspectJ增强的ajc增强编译器才可以通过编译,写法不符合原生Java的语法;而Spring AOP是符合Java语法的,也不需要指定编译器去编译,一切都由Spring 处理。

JDK动态代理与Cglib的区别

  1. jdk的动态代理需要实现接口 InvocationHandler
  2. cglib无需实现接口,使用字节码技术去修改class文件使继承
  3. spring默认使用jdk动态代理,如果没有实现接口会使用cglib

使用步骤

  1. 定义业务组件
  2. 定义切点(重点)
  3. 定义增强处理方法(切面方法)

依赖

jar包依赖,除此以外还有spring依赖

  1. aspectjweaver.jar
  2. aspectjrt.jar
  3. aspectj.jar
  4. aopalliance.jar

maven依赖

	<dependencies>
<!-- 有此依赖会远程下载其它相关依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.9.RELEASE</version>
</dependency>
<!-- aspectJ AOP 织入器 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency> </dependencies>

注解方式开发

  1. 扫描Aspect增强的类
<context:component-scan base-package="">
<context:include-filter type="annotation"
expression="org.aspectj.lang.annotation.Aspect"/>
</context:component-scan>
  1. 开启@AspectJ支持 <aop:aspectj-autoproxy/>
  2. 使用@AspectJ注解来标记一个切面类(spring不会将切面注册为Bean也不会增强,但是需要扫描)
  3. 使用其它注解进行开发(如下)

常用注解的使用

  1. @Before:在切点方法前执行

    • 在增强的方法上@Before("execution(* 包名.*.*(..))")
    • 上述表达式可使用pointcut或切入表达式,效果一致,之后不再赘述
    • 切点方法没有形参与返回值

示例代码

@Aspect
public class AuthAspect { //定义切点
@Pointcut("execution(* com.cnblogs.hellxz.service.*.*(..))")
public void pointCut() {} //前置处理
@Before("pointCut()")
public void auth() {
System.out.println("模拟权限检查……");
}
}
  1. @After:在切点方法后执行

    • 用法同@Before
  2. @Around:在切点方法外环绕执行
    • 在增强的方法上@Around("execution(* 包名.*(..))")或使用切点@Around("pointcut()")
    • 接收参数类型为ProceedingJoinPoint,必须有这个参数在切面方法的入参第一位
    • 返回值为Object
    • 需要执行ProceedingJoinPoint对象的proceed方法,在这个方法前与后面做环绕处理,可以决定何时执行与完全阻止方法的执行
    • 返回proceed方法的返回值
    • @Around相当于@Before和@AfterReturning功能的总和
    • 可以改变方法参数,在proceed方法执行的时候可以传入Object[]对象作为参数,作为目标方法的实参使用。
    • 如果传入Object[]参数与方法入参数量不同或类型不同,会抛出异常
    • 通过改变proceed()的返回值来修改目标方法的返回值

示例代码

@Aspect
public class TxAspect { //环绕处理
@Around("execution(* com.cnblogs.hellxz.service.*.*(..))")
Object auth(ProceedingJoinPoint point) { Object object = null;
try {
System.out.println("事务开启……");
//放行
object = point.proceed();
System.out.println("事务关闭……");
} catch (Throwable e) {
e.printStackTrace();
} return object;
}
}
  1. @AfterRetruning: 在方法返回之前,获取返回值并进行记录操作

    • 和上边的方法不同的地方是该注解除了切点,还有一个返回值的对象名
    • 不同的两个注解参数:returning与pointcut,其中pointcut参数可以为切面表达式,也可为切点
    • returning定义的参数名作为切面方法的入参名,类型可以指定。如果切面方法入参类型指定Object则无限制,如果为其它类型,则当且仅当目标方法返回相同类型时才会进入切面方法,否则不会
    • 还有一个默认的value参数,如果指定了pointcut则会覆盖value的值
    • 与@After类似,但@AfterReturning只有方法成功完成才会被织入,而@After不管结果如何都会被织入

虽然可以拿到返回值,但无法改变返回值

示例代码

@Aspect
public class AfterReturningAspect { @AfterReturning(returning="rvt",
pointcut = "execution(* com.cnblogs.hellxz.service.*.*(..))")
//声明rvt时指定的类型会限定目标方法的返回值类型,必须返回指定类型或者没有返回值
//rvt类型为Object则是不对返回值做限制
public void log(Object rvt) {
System.out.println("获取目标返回值:"+ rvt);
System.out.println("假装在记录日志……");
} /**
* 这个方法可以看出如果目标方法的返回值类型与切面入参的类型相同才会执行此切面方法
* @param itr
*/
@AfterReturning(returning="itr",
pointcut="execution(* com.cnblogs.hellxz.service.*.*(..))")
public void test(Integer itr) {
System.out.println("故意捣乱……:"+ itr);
}
}
  1. @AfterThrowing: 在异常抛出前进行处理,比如记录错误日志

    • @AfterReturning类似,同样有一个切点和一个定义参数名的参数——throwing
    • 同样可以通过切面方法的入参进行限制切面方法的执行,e.g. 只打印IOException类型的异常, 完全不限制可以使用Throwable类型
    • pointcut使用同@AfterReturning
    • 还有一个默认的value参数,如果指定了pointcut则会覆盖value的值
    • 如果目标方法中的异常被try catch块捕获,此时异常完全被catch块处理,如果没有另外抛出异常,那么还是会正常运行,不会进入AfterThrowing切面方法

示例代码

@Aspect
public class AfterThrowingAspect { @Pointcut("execution(* com.cnblogs.hellxz.test.*.*(..))")
public void pointcut() {} /**
* 如果抛出异常在切面中的几个异常类型都满足,那么这几个切面方法都会执行
*/
@AfterThrowing(throwing="ex1",
pointcut="pointcut()")
//无论异常还是错误都会记录
//不捕捉错误可以使用Exception
public void throwing(Throwable ex1) {
System.out.println("出现异常:"+ex1);
} @AfterThrowing(throwing="ex",
pointcut="pointcut()")
//只管IOException的抛出
public void throwing2(IOException ex) {
System.out.println("出现IO异常: "+ex);
}
}

pointcut定义的切点方法在@Before/@After/@Around需要写在双引号中,e.g. @Before("pointCut()")

JoinPoint的概念与方法说明

概念

  • 顾名思义,连接点,织入增强处理的连接点
  • 程序运行时的目标方法的信息都会封装到这个连接点对象中
  • 此连接点只读

方法说明

  • Object[] getArgs():返回执行目标方法时的参数
  • Signature getSignature():返回被增强方法的相关信息,e.g 方法名 etc
  • Object getTarget():返回被织入增强处理的目标对象
  • Object getThis():返回AOP框架目标对象生成的代理对象

使用

  • 在@Before/@After/@AfterReturning/@AfterThrowing所修饰的切面方法的参数列表中加入JoinPoint对象,可以使用这个对象获得整个增强处理中的所有细节
  • 此方法不适用于@Around, 其可用ProceedingJoinPoint作为连接点

ProceedingJoinPoint的概念与方法说明

概念

  • 是JoinPoint的子类
  • 与JoinPoint概念基本相同,区别在于是可修改的
  • 使用@Around时,第一个入参必须为ProceedingJoinPoint类型
  • 在@Around方法内时需要执行proceed()或proceed(Object[] args)方法使方法继续,否则会一直处于阻滞状态

方法说明

ProceedingJoinPoint是JoinPoint的子类,包含其所有方法外,还有两个公有方法

  • Object proceed():执行此方法才会执行目标方法
  • Object proceed(Object[] args):执行此方法才会执行目标方法,而且会使用Object数组参数去代替实参,如果传入Object[]参数与方法入参数量不同或类型不同,会抛出异常

通过修改proceed方法的返回值来修改目标方法的返回值

编入的优先级

优先级最高的会最先被织入,在退出连接点的时候,具有最高的优先级的最后被织入

当不同切面中两个增强处理切入同一连接点的时候,Spring AOP 会使用随机织入的方式

如果想要指定优先级,那么有两种方案:

  • 让切面类实现 org.springframework.core.Ordered接口,实现getOrder方法,返回要指定的优先级
  • 切面类使用@Order修饰,指定一个优先级的值,值越小,优先级越高

访问目标方法的形参

除了使用JoinPoint或ProceedingJoinPoint来获取目标方法的相关信息外(包括形参),如果只是简单访问形参,那么还有一种方法可以实现

  • 在pointcut的execution表达式之后加入&& args(arg0,arg1)这种方式
@Aspect
public class AccessInputArgs { @Before("execution(* com.cnblogs.hellxz.test.*.*(..)) && args(arg0, arg1)")
public void access(String arg0, String arg1){
System.out.println("接收到的参数为arg0="+arg0+",arg1="+arg1);
}
}

注意:通过这种方式会只匹配到方法只有指定形参数量的方法,并且,在切面方法中指定的类型会限制目标方法,不符合条件的不会进行织入增强

定义切入点

通过定义切入点,我们可以复用切点,减少重复定义切点表达式等

切入点定义包含两个部分:

  • 切入点表达式
  • 包含名字和任意参数的方法签名

使用@Pointcut注解进行标记一个无参无返回值的方法,加上切点表达式

    @Pointcut("execution(* com.cnblogs.hellxz.test.*.*(..))")
public void pointcut(){}

切入点指示符

Spring AOP 支持10种切点指示符:execution、within、this、target、args、@target、@args、@within、@annotation、bean下面做下简记(没有写@Pointcut(),请注意):

  • execution: 用来匹配执行方法的连接点的指示符。

    用法相对复杂,格式如下:execution(权限访问符 返回值类型 方法所属的类名包路径.方法名(形参类型) 异常类型)

    e.g. execution(public String com.cnblogs.hellxz.test.Test.access(String,String))

    权限修饰符和异常类型可省略,返回类型支持通配符,类名、方法名支持*通配,方法形参支持..通配

  • within: 用来限定连接点属于某个确定类型的类。

    within(com.cnblogs.hellxz.test.Test)

    within(com.cnblogs.hellxz.test.) //包下类

    within(com.cnblogs.hellxz.test..
    ) //包下及子包下

  • this和target: this用于没有实现接口的Cglib代理类型,target用于实现了接口的JDK代理目标类型

    举例:this(com.cnblogs.hellxz.test.Foo) //Foo没有实现接口,使用Cglib代理,用this

    实现了个接口public class Foo implements Bar{...}

    target(com.cnblogs.hellxz.test.Test) //Foo实现了接口的情况

  • args: 对连接点的参数类型进行限制,要求参数类型是指定类型的实例。

    args(Long)

  • @target: 用于匹配类头有指定注解的连接点

    @target(org.springframework.stereotype.Repository)

  • @args: 用来匹配连接点的参数的,@args指出连接点在运行时传过来的参数的类必须要有指定的注解

    @Pointcut("@args(org.springframework.web.bind.annotation.RequestBody)")
    public void methodsAcceptingEntities() {}
  • @within: 指定匹配必须包括某个注解的的类里的所有连接点

    @within(org.springframework.stereotype.Repository)

  • @annotation: 匹配那些有指定注解的连接点

    @annotation(org.springframework.stereotype.Repository)

  • bean: 用于匹配指定Bean实例内的连接点,传入bean的id或name,支持使用*通配符

切点表达式组合

使用&&、||、!、三种运算符来组合切点表达式,表示与或非的关系

execution(* com.cnblogs.hellxz.test.*.*(..)) && args(arg0, arg1)

部分代码可以参考:我的Github

本文为学习总结,如需转载请注明出处

【学习笔记】Spring AOP注解使用总结的更多相关文章

  1. Spring学习笔记之aop动态代理(3)

    Spring学习笔记之aop动态代理(3) 1.0 静态代理模式的缺点: 1.在该系统中有多少的dao就的写多少的proxy,麻烦 2.如果目标接口有方法的改动,则proxy也需要改动. Person ...

  2. Spring学习笔记--spring+mybatis集成

    前言: 技术的发展, 真的是日新月异. 作为javaer, 都不约而同地抛弃裸写jdbc代码, 而用各种持久化框架. 从hibernate, Spring的JDBCTemplate, 到ibatis, ...

  3. CgLib动态代理学习【Spring AOP基础之一】

    如果不了解JDK中proxy动态代理机制的可以先查看上篇文章的内容:Java动态代理学习[Spring AOP基础之一] 由于Java动态代理Proxy.newProxyInstance()的时候会发 ...

  4. springmvc学习笔记(13)-springmvc注解开发之集合类型參数绑定

    springmvc学习笔记(13)-springmvc注解开发之集合类型參数绑定 标签: springmvc springmvc学习笔记13-springmvc注解开发之集合类型參数绑定 数组绑定 需 ...

  5. Spring AOP注解为什么失效?90%Java程序员不知道

    使用Spring Aop注解的时候,如@Transactional, @Cacheable等注解一般需要在类方法第一个入口的地方加,不然不会生效. 如下面几种场景 1.Controller直接调用Se ...

  6. springmvc学习笔记(12)-springmvc注解开发之包装类型參数绑定

    springmvc学习笔记(12)-springmvc注解开发之包装类型參数绑定 标签: springmvc springmvc学习笔记12-springmvc注解开发之包装类型參数绑定 需求 实现方 ...

  7. springmvc学习笔记(10)-springmvc注解开发之商品改动功能

    springmvc学习笔记(10)-springmvc注解开发之商品改动功能 标签: springmvc springmvc学习笔记10-springmvc注解开发之商品改动功能 需求 开发mappe ...

  8. Spring aop注解失效

    问题 在spring 中使用 @Transactional . @Cacheable 或 自定义 AOP 注解时,对象内部方法中调用该对象的其他使用aop机制的方法会失效. @Transactiona ...

  9. Spring in Action 4th 学习笔记 之 AOP

    前提:本文中的AOP仅限于Spring AOP. 先说说为什么需要AOP 最简单的一个例子就是日志记录,如果想记录一些方法的执行情况,最笨的办法就是修改每一个需要记录的方法.但这,真的很笨... 好的 ...

随机推荐

  1. 在nodejs中引进模块要经历的步骤

    在nodejs中引入模块需要经历如下3个步骤 1.路径分析 2.文件定位 3.编译执行 在nodejs中模块分为两类,一类是nodejs提供的模块,称为核心模块,另一类的用户编写的模块,称为文件模块. ...

  2. cocos2dx渲染架构

    2dx的时代UI树便利和渲染是没有分开的,遍历UI树的时候就渲染.3dx版本为了分离了ui树的遍历和渲染,先遍历生成渲染命令发到渲染队列,之后遍历渲染命令队列开始渲染.这样做的好处是渲染命令可以重用, ...

  3. Java内存区域的划分和异常

    Java内存区域的划分和异常   运行时数据区域 JVM在运行Java程序时候会将内存划分为若干个不同的数据区域. 打开百度App,看更多美图 程序计数器 线程私有.可看作是当前线程所执行的字节码的行 ...

  4. 拓扑排序(Topological Sort)

    Graph 拓扑排序(Topological Sort) 假设一个应用场景:你用 C 编写了一个爬虫工具,其中有很多自定义的库:queue.c.queue.h.stack.c.stack.h.heap ...

  5. Notes of Daily Scrum Meeting(12.8)

    今日团队任务总结: 团队成员 今日团队工作 陈少杰 使用例子对json数据进行解析 王迪 确定搜索功能的接口 金鑫 对布局文件进行协助修改 雷元勇 开始进行搜索功能的代码实现 高孟烨 按照学长的样本对 ...

  6. linux第七章笔记

    第七章 链接 链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(或拷贝)到存储器并执行.链接可以执行于编译时,也就是在源代码被翻译成机器代码时:也可以执行于加载时,也就 ...

  7. 【MOOC EXP】Linux内核分析实验五报告

    程涵  原创博客 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 分析system_call中断处理过程 ...

  8. FuelPHP 系列(三) ------ Model 模型

    框架封装好的 model 类有几个,按需继承就好. 有:/fuel/core/classes/model/crud.php /fuel/packages/orm/classes/model.php / ...

  9. shell 命令 if [ -d filename] 判断文件

    作者:曹毅涵  [ -a FILE ] 如果 FILE 存在则为真. [ -b FILE ] 如果 FILE 存在且是一个块特殊文件则为真. [ -c FILE ] 如果 FILE 存在且是一个字特殊 ...

  10. ES6学习笔记(一):变量赋值和基本数据类型

    let和const let和const不存在变量提升 变量一定要在声明后使用,否则报错. var a = []; for (var i = 0; i < 10; i++) { a[i] = fu ...