Spring AOP

面向切面编程(Aspect oriented programming)作为面向对象编程的一种补充,广泛应用于处理一些具有横切性质的系统级服务

在实际开发中,比如商品查询、促销查询等业务,都需要记录日志、异常处理等操作,AOP把所有共用代码都剥离出来,单独放置到某个类中进行集中管理,在具体运行时,由容器进行动态织入这些公共代码。

AOP主要一般应用于签名验签、参数校验、日志记录、事务控制、权限控制、性能统计、异常处理等。

3.1 AOP涉及名词

  • 切面(Aspect):共有功能的实现。如日志切面、权限切面、验签切面等。在实际开发中通常是一个存放共有功能实现的标准Java类。当Java类使用了@Aspect注解修饰时,就能被AOP容器识别为切面。

  • 通知(Advice):切面的具体实现。就是要给目标对象织入的事情。以目标方法为参照点,根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)5种。在实际开发中通常是切面类中的一个方法,具体属于哪类通知,通过方法上的注解区分。

  • 连接点(JoinPoint):程序在运行过程中能够插入切面的地点。例如,方法调用、异常抛出等。Spring只支持方法级的连接点。一个类的所有方法前、后、抛出异常时等都是连接点。

  • 切入点(Pointcut):用于定义通知应该切入到哪些连接点上。不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。
    比如,在上面所说的连接点的基础上,来定义切入点。我们有一个类,类里有10个方法,那就产生了几十个连接点。但是我们并不想在所有方法上都织入通知,我们只想让其中的几个方法,在调用之前检验下入参是否合法,那么就用切点来定义这几个方法,让切点来筛选连接点,选中我们想要的方法。切入点就是来定义哪些类里面的哪些方法会得到通知。

  • 目标对象(Target):那些即将切入切面的对象,也就是那些被通知的对象。这些对象专注业务本身的逻辑,所有的共有功能等待AOP容器的切入。

  • 代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象本身业务逻辑加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。目标对象被织入共有功能后产生的对象。

  • 织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译时、类加载时、运行时。Spring是在运行时完成织入,运行时织入通过Java语言的反射机制与动态代理机制来动态实现。

具体使用

Spring 允许使用 AspectJ Annotation 用于定义方面(Aspect)、切入点(Pointcut)和增强处理(Advice),Spring 框架则可识别并根据这些 Annotation 来生成 AOP 代理。Spring 只是使用了和 AspectJ 5 一样的注解,但并没有使用 AspectJ 的编译器或者织入器(Weaver),底层依然使用的是 Spring AOP,依然是在运行时动态生成 AOP 代理,并不依赖于 AspectJ 的编译器或者织入器。
当我们使用 @Aspect 来修饰一个 Java 类之后,Spring 将不会把该 Bean 当成组件 Bean 处理,因此负责自动增强的后处理 Bean 将会略过该 Bean,不会对该 Bean 进行任何增强处理。

@Pointcut切入点

参考
例子

1
2
3
4
5
6
7
    @Pointcut("execution(* com.example.aop.service..TestImpl.*(..))")
// @Pointcut("within(com.example.aop.service.impl..*)")
// @Pointcut("this(com.example.aop.service.impl.TestImpl)")
// @Pointcut("target(com.example.aop.service.impl.TestImpl)")
// @Pointcut("@within(com.example.aop.anno.AopTest)")
// @Pointcut("@target(com.example.aop.anno.AopTest)")
// @Pointcut("@annotation(com.example.aop.anno.AopTest)")
  • execution: 匹配连接点
  • within: 某个类里面
  • this: 指定AOP代理类的类型
  • target:指定目标对象的类型
  • args: 指定参数的类型
  • bean:指定特定的bean名称,可以使用通配符(Spring自带的)
  • @target: 带有指定注解的类型
  • @args: 指定运行时传的参数带有指定的注解
  • @within: 匹配使用指定注解的类
  • @annotation:指定方法所应用的注解
    以上切入点表达式可以用&&,||,!来组合使用

由于AOP是使用代理实现的方式,因此并不是目标类的所有方法都能拦截。对于JDK代理,只有public接口的方法才能被拦截,对于CGLIB,只有public和protected方法能被拦截

@Before @Around @After @AfterReturning @AfterThrowing

执行顺序
aop.png
around.jpg

@Around由于是环绕执行,是通过调用proceed方法执行原方法,所以,是可以进行包括修改参数,修改返回值等操作的

实现原理

AOP 实现的关键就在于 AOP 框架自动创建的 AOP 代理,AOP 代理则可分为静态代理和动态代理两大类,其中静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;而动态代理则在运行时借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。

静态代理主要是指AspectJ实现,这里只讨论Spring实现的动态代理

1. java动态代理

主要通过实现InvocationHandler和使用Proxy来生成新的代理类。

1
2
3
4
5
6
7
8
9
10
11
12
public interface AService {

/**
* add方法
*/
public void add();

/**
* update方法
*/
public void update();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class AServiceImpl implements AService {
@Override
public void add() {
System.out.println("AService add>>>>>>>>>>>>>>>>>>");
}

@Override
public void update() {
System.out.println("AService update>>>>>>>>>>>>>>>");

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MyInvocationHandler implements InvocationHandler {
private Object target;

MyInvocationHandler() {
super();
}

public MyInvocationHandler(Object target) {
super();
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 程序执行前加入逻辑,MethodBeforeAdviceInterceptor
System.out.println("before-----------------------------");
// 程序执行
Object result = method.invoke(target, args);
// 程序执行后加入逻辑,MethodAfterAdviceInterceptor
System.out.println("after------------------------------");
return result;
}
}

然后执行这段逻辑

1
2
3
4
5
6
7
8
9
10
AService aService = new AServiceImpl();
MyInvocationHandler handler = new MyInvocationHandler(aService);
// Proxy为InvocationHandler实现类动态创建一个符合某一接口的代理实例
AService aServiceProxy = (AService) Proxy.newProxyInstance(aService
.getClass().getClassLoader(), aService.getClass()
.getInterfaces(), handler);
// 由动态生成的代理对象来aServiceProxy 代理执行程序,其中aServiceProxy 符合Service接口
aServiceProxy.add();
System.out.println();
aServiceProxy.update();

其实InvactionHandler的实现类就类似于Spring中的切面类。Proxy中代码就是Spring内部去做的事,其实他做了以下几件事:

  • 创建一个新的类代码
  • 实现原始类的接口和方法
  • 使用反射获得方法,回调InvocationHandler的实现类的方法来增强方法
  • 将这个类加载入内存
2. CGLIB动态代理

它是基于继承来实现代理,java的动态代理是基于反射的。主要是使用MethodInterceptor 和工厂类Enhancer来实现,其实和java的代理差不多,限制少一点。

1
2
3
4
5
6
7
8
public class CGBase {
/**
* 一个模拟的add方法
*/
public void add() {
System.out.println("add ------------");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CGProxy implements MethodInterceptor {

@Override
public Object intercept(Object object, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
// 添加切面逻辑(advise),此处是在目标类代码执行之前,即为MethodBeforeAdviceInterceptor。
System.out.println("before-------------");
// 执行目标类add方法
proxy.invokeSuper(object, args);
// 添加切面逻辑(advise),此处是在目标类代码执行之后,即为MethodAfterAdviceInterceptor。
System.out.println("after--------------");
return null;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Factory {
/**
* 获得增强之后的目标类,即添加了切入逻辑advice之后的目标类
*
* @param proxy
* @return
*/
public static CGBase getInstance(CGProxy proxy) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(CGBase.class);
//回调方法的参数为代理类对象CglibProxy,最后增强目标类调用的是代理类对象CglibProxy中的intercept方法
enhancer.setCallback(proxy);
// 此刻,base不是单纯的目标类,而是增强过的目标类
CGBase base = (CGBase) enhancer.create();
return base;
}
}
1
2
3
4
CGProxy proxy = new CGProxy();
// base为生成的增强过的目标类
CGBase base = Factory.getInstance(proxy);
base.add();