AOP(Aspect Oriented Programming),即面向切面编程。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

AOP 术语

  • Join point(连接点):目标对象中的方法,我们要关注和增强的方法,也就是我们要作用的点
  • Pointcut(切点):连接点的集合,常用正则表达式定义所匹配的类和方法名称来指定这些切点。
  • Advice(通知):通知定义了切面的位置,应用在某个方法被调用之前?之后?还是抛出异常时?等等。
  • Aspect(切面):切面是通知和切点的结合,在spring bean通常是个类,xml中是个label。
  • Target object(目标对象):原始对象
  • AOP proxy(代理对象):AOP框架创建的对象,包含了原始对象的代码和增加后的代码的对象。
  • Weaving(织入):把代理逻辑加入到目标对象上的过程
  • Introduction(引入):向现有的类添加新方法或属性,从而无需修改这些现有类的情况下,让他们具有新的行为和状态。织入是对方法的增强,引入是对类的增强

通知的类型

  • 前置通知: 在一个方法执行之前,执行通知。
  • 后置通知: 在一个方法执行之后,不考虑其结果,执行通知。
  • 返回后通知: 在一个方法执行之后,只有在方法成功完成时,才能执行通知。
  • 抛出异常后通知:在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。
  • 环绕通知:在方法调用之前和之后,执行通知。

Advice(通知):通知定义了切面的位置,应用在某个方法被调用之前?之后?还是抛出异常时?等等。

各个通知的使用

引入jar

首先需要引入下面的jar包:

compile(project(":spring-aop"))
compile("org.aspectj:aspectjweaver:1.9.6")
compile("org.aspectj:aspectjrt:1.9.6")

aop参数-JoinPoint

每一个通知都有可以使用org.aspectj.lang.JoinPoint类型的参数作为方法的第一个参数,可以通过这个参数来查看连接点的信息。

getArgs(): 获取方法的参数.
getThis(): 获取代理对象.
getTarget(): 获取目标对象.
getSignature(): 获取方法签名.
toString(): 获取方法描述.

环绕通知可以使用JoinPoint的子类ProceedingJoinPoint获取更多的方法。

Object proceed() throws Throwable; // 执行目标方法
Object proceed(Object[] var1) throws Throwable; // 设置目标方法的参数后执行目标方法

切面的使用

package com.morris.spring.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.stream.Collectors;

@Component
@Aspect // 切面
@EnableAspectJAutoProxy
 // 开启AOP
public class UserServiceAspect {

	@Pointcut("execution(* com.morris.spring.service..*.*(..))") // 切点
	public void pointCut() {
	}

	@Before("pointCut()") // 前置通知
	public void before(JoinPoint joinPoint) {
		System.out.println("---Before begin---");
		System.out.println("getArgs: " + Arrays.asList(joinPoint.getArgs()).stream().map(Object::toString).collect(Collectors.joining(",")));
		System.out.println("getThis: " + joinPoint.getThis());
		System.out.println("getTarget: " + joinPoint.getTarget());
		System.out.println("getSignature: " + joinPoint.getSignature());
		System.out.println("toString: " + joinPoint.toString());
		System.out.println("---Before  end---");
	}

	@After("pointCut()") // 返回通知
	public void after() {
		System.out.println("After");
	}

	@AfterReturning("pointCut()") // 正常返回通知
	public void returning() {
		System.out.println("AfterReturning");
	}

	@AfterThrowing(value = "pointCut()", throwing = "throwing") // 异常返回通知
	public void logThrowing(JoinPoint joinPoint, Throwable throwing) {
		System.out.println("异常返回通知:" + joinPoint.getSignature().getDeclaringTypeName() + " 异常:");
		throwing.printStackTrace();
	}

	@Around("pointCut()") // 环绕通知
	public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		System.out.println("---Around begin---");
		proceedingJoinPoint.proceed();
		System.out.println("---Around end---");
	}

}

多个AOP之间的执行顺序

切面类1

package com.morris.spring.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect // 切面
@Order(3)
public class UserServiceAspect2 {

	@Pointcut("execution(* com.morris.spring.service..*.*(..))") // 切点
	public void pointCut() {
	}

	@Before("pointCut()") // 前置通知
	public void before() {
		System.out.println("---UserServiceAspect2.before begin---");
	}

	@Before("pointCut()") // 前置通知
	public void before2() {
		System.out.println("---UserServiceAspect2.before2 begin---");
	}

}

切面类2

package com.morris.spring.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect // 切面
@Order(4)
public class UserServiceAspect3 {

	@Before("com.morris.spring.aop.UserServiceAspect2.pointCut()") // 前置通知
	public void before() {
		System.out.println("---UserServiceAspect3.before begin---");
	}

}

结论:order越小越是最先执行,但更重要的是最先执行的最后结束,Spring AOP就是一个同心圆,要执行的方法为圆心,最外层的order最小。@Order注解只能作用于类上,标记在方法上无效,同一个切面中,两个通知作用于同一个目标方法是无序(先按注解排序,再按方法名排序,没有优先级,这点可以从源码中分析得到)的。

简写

简写如下:

package com.morris.spring.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect // 切面
public class UserServiceAspect4 {

	@Before("execution(* com.morris.spring.service..*.*(..))") // 切点+通知
	public void before() {
		System.out.println("---UserServiceAspect4.before begin---");
	}
}

切点表达式

execution

用于匹配方法执行 join points连接点,最小粒度为方法级别,在aop中主要使用。

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

这里问号表示当前项可以有也可以没有,其中各项的语义如下

  • modifiers-pattern:方法的可见性,如public,protected;
  • ret-type-pattern:方法的返回值类型,如int,void等;
  • declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
  • name-pattern:方法名类型,如buisinessService();
  • param-pattern:方法的参数类型,如java.lang.String;
  • throws-pattern:方法抛出的异常类型,如java.lang.Exception;

例子:

@Pointcut("execution(* com.chenss.dao.*.*(..))") // 匹配com.chenss.dao包下的任意接口和类的任意方法
@Pointcut("execution(public * com.chenss.dao.*.*(..))") // 匹配com.chenss.dao包下的任意接口和类的public方法
@Pointcut("execution(public * com.chenss.dao.*.*())") // 匹配com.chenss.dao包下的任意接口和类的public 无方法参数的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String, ..))") // 匹配com.chenss.dao包下的任意接口和类的第一个参数为String类型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))") // 匹配com.chenss.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))") // 匹配com.chenss.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
@Pointcut("execution(public * *(..))") // 匹配任意的public方法
@Pointcut("execution(* te*(..))") // 匹配任意的以te开头的方法
@Pointcut("execution(* com.chenss.dao.IndexDao.*(..))") // 匹配com.chenss.dao.IndexDao接口中任意的方法
@Pointcut("execution(* com.chenss.dao..*.*(..))") // 匹配com.chenss.dao包及其子包中任意的方法

within

表达式的最小粒度为包

@Pointcut("within(com.chenss.dao.*)") // 匹配com.chenss.dao包中的任意方法
@Pointcut("within(com.chenss.dao..*)") // 匹配com.chenss.dao包及其子包中的任意方法

args

匹配指定参数类型和指定参数数量的方法,与包名和类名无关。

args同execution不同的地方在于:args匹配的是运行时传递给方法的参数类型execution匹配的是方法在声明时指定的方法参数类型。

@Pointcut("args(java.io.Serializable)") // 匹配运行时传递的参数类型为指定类型的、且参数个数和顺序匹配

this

this表示代理对象。

@Pointcut("this(com.morris.spring.service.UserService)")

JDK代理时,this指向接口和代理类proxy,也就是说@Pointcut("this(com.morris.spring.service.UserServiceImpl)")无法实现代理。

cglib代理时(@EnableAspectJAutoProxy(proxyTargetClass = true) ),this指向接口和子类,也就是说@Pointcut("this(com.morris.spring.service.UserServiceImpl)")可以实现代理。

target

target代表目标对象。

@Before("target(com.morris.spring.aop.annotation.target.UserServiceImpl)")

@target

任何目标对象上面指定注解的类方法。

@Pointcut("@target(com.morris.spring.annotation.Abcd)") // @Abcd

@within

任何目标对象上面指定注解的类方法。

@Pointcut("@within(com.morris.spring.annotation.Abcd)")

注意@target和@within的区别:

  • @target要求对象的运行时类型与被注解的类型是同一个类型。
  • @within要求对象的运行时类型是被注解的类型的子类。

@args

任何方法参数对应的类上面有指定的注解(不是方法的参数前面有指定的注解)的方法。

@Pointcut("@args(com.morris.spring.annotation.Abcd)")

@annotation

目标方法上有指定注解的方法。

@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")

bean

作用于Spring中beanName为指定名称的类的方法。

@Pointcut("bean(userServiceImpl)")
@Pointcut("bean(*ServiceImpl)")