SpringAOP-基础

简介

AOP(Aspect Oriented Programming),面向切面编程技术。AOP是基于IOC的基础上。
github
从图中看出,使用横行的通用逻辑中切入到业务逻辑中,在逐渐完善的横向通用逻辑后,让编码人员更专注于业务逻辑工作。

基本概念

切面(Aspect)

一个关注点的模块化,这个关注点实现可能另外横切多个对象。包括了横切的逻辑。切面用Spring的Advisor或拦截器实现。

连接点(Joinpoint)

程序执行到某个特定的位置。如方法的调用或者某个位置的异常抛出。

通知(Advice)

接入到连接点上的程序代码。

前置通知(before)

连接点之前调用。

后置通知(after)

连接点退出的时候调用,不论是正常返回还是异常退出。

异常通知(afterThrowing)

在方法抛出异常退出的时候调用。

返回通知(afterReturning)

连接点正常完成后执行的调用,不包括抛出异常的情况

环绕通知(around)

围绕一个连接点的通知,可以在方法调用前后完成自定义的行为,也可以不执行。

切入点(Pointcut)

指定连接点作为切入点。

目标对象(TargetObject)

被切入(代理)的对象。

AOP代理(AOP Proxy)

一个类被AOP织入后,就产生了一个代理类。

编织(Weaving)

将通知增加到目标类具体到连接点上的过程。

思想概念:
github

运行过程:
github

简单案例

UserDao的实现

// UserDao接口
public interface UserDao {
JSONObject addUser(int a, int b);
}

// UserDao实现
@Repository
public class UerDaoImpl implements UserDao {

@BeforeTest
@AfterReturningTest
@AfterThrowableTest
@AfterTest
@AroundTest
public JSONObject addUser(int a, int b) {
System.out.println("-----> add User");
return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}");
}

}

自定义的注解

UserDao实现中看到5个注解用来完成5个通知操作

  • @BeforTest
  • @AfterReturningTest
  • @AfterThrowableTest
  • @AfterTest
  • @AroundTest

配置的切面

@Aspect
public class MyAspect {

}
Before

前置通知。

@Before("@annotation(annotation.BeforeTest)")
public void before() {
System.out.println("-----> before");
}

After

后置通知。

@After("@annotation(annotation.AfterTest)")
public void after(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String method = signature.getName();
System.out.println("-----> after:"+method);
}

AfterReturning

返回通知。

@AfterReturning(value = "@annotation(annotation.AfterReturningTest)", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
Signature signature = joinPoint.getSignature();
String classMethod = signature.getName();
System.out.println("-----> afterReturning:"+classMethod+", "+result);
}

AfterThrowing

异常通知。

@AfterThrowing(value = "@annotation(annotation.AfterThrowableTest)", throwing = "ex")
public void afterThrowable(JoinPoint joinPoint, Throwable ex) {
Signature signature = joinPoint.getSignature();
String method = signature.getName();
System.out.println("-----> afterThrowable:"+method+", "+ex);
}

Around
@Around("@annotation(annotation.AroundTest)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("-----> around 开始");
Object[] objects = joinPoint.getArgs();
for (Object obj :
objects) {
System.out.println("-----> obj:"+obj);
}

JSONObject jsonObject = new JSONObject();
jsonObject.put("arg1", objects[0]);
jsonObject.put("arg2", objects[1]);

objects[0] = jsonObject;
joinPoint.proceed();
System.out.println("-----> around 结束");

return jsonObject;
}
测试日志
-----> around 开始
-----> obj:1
-----> obj:2
-----> before
-----> add User
-----> around 结束
-----> after:addUser
-----> afterReturning:addUser, {"arg2":2,"arg1":1}
-----> result:{"arg2":2,"arg1":1}

注解说明

定义切入点函数
@After(value="execution(* xxxx.dao.UserDao.addUser(..))")
配置切入点函数
@After(value="pointcut()")
切入点指示符
  • 通配符
    • .. : 匹配方法定义中的任意数量的参数,此外还匹配类定义中的任意数量包
      // 匹配任意返回值、函数名、任意参数的public函数
      execution(public * *(..))
    • + : 匹配给定类的任意子类
      // 匹配实现了该类接口的所有子类的函数
      execution(xx.xx.xx.UserDao+)
    • * : 匹配任意数量的字符
      // 匹配set开头的函数名、任意返回值、参数为int的函数
      execution(* set*(int))
  • 类型签名表达式
    为了方便类型(如接口、类名、包名)过滤方法,Spring AOP 提供了within关键字。
    within(<type name>)
    @Pointcut("within(xxx.xxx.xxx.dao..*)")
  • 方法签名表达式
    如果想根据方法签名进行过滤,关键字execution可以完成。
    // scope :方法作用域,如public,private,protect
    // returnt-type:方法返回值类型
    // fully-qualified-class-name:方法所在类的完全限定名称
    // parameters 方法参数
    execution(<scope> <return-type> <fully-qualified-class-name>.*(parameters))
  • bean : Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法
    // 匹配名称后缀中有Service的bean
    @Pointcut("bean(*Service)")
  • this : 用于匹配当前AOP代理对象类型的执行方法;请注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配
    // 匹配任意实现了UserDao接口的代理对象方法
    @Pointcut("this(xxx.xxx.xx.dao.UserDao)")
  • target : 用于匹配当前目标对象类型的执行方法
    // 匹配了任意实现了UserDao接口的目标对象的方法
    @Pointcut("target(xxx.xxx.xx.dao.UserDao)")
  • @within : 用于匹配所以持有指定注解类型内的方法;请注意与within是有区别的, within是用于匹配指定类型内的方法执行
    // 匹配使用了BeforeTest注解的类(类)
    @Pointcut("@within(xxx.xxx.BeforeTest)")
  • @annotation
    // 匹配使用了BeforeTest注解的方法(方法)
    @Pointcut("@annotation(xxx.xxx.BeforeTest)")
  • 运算符 : 包括 &&、||、!等。

Aspect优先级

针对多个通知切入一个切入点方法执行的顺序说明,即优先级配置。

@Aspect
public class MyAspect implements Ordered {
// 配置优先级,值越低,优先级越高
@Override
public int getOrder() {
return 0;
}
}

Spring AOP实现原理

Spring AOP的实现原理基于动态织入的动态代理技术。分为JavaJDK动态代理和CGLIB动态代理,前者基于反射、后者基于继承。

JDK动态代理

通过JDK动态代理对实现了接口的类进行代理。

public class UserDaoProxy implements InvocationHandler {

private UserDao userDao;

public UserDaoProxy(UserDao userDao) {
this.userDao = userDao;
}

public UserDao createProxy() {
return (UserDao) java.lang.reflect.Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), this);
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

if (method.getName().equals("addUser")) {
// ===
System.out.println("-----> 函数调用前");
Object r = method.invoke(userDao, args);
// ===
System.out.println("-----> 函数调用后");
return r;
}

return method.invoke(userDao, args);
}
}

测试:

UserDao userDao = new UserDaoImpl();
UserDaoProxy proxy = new UserDaoProxy(userDao);
UserDao u = proxy.createProxy();
u.addUser(1, 2);

结果:

-----> 函数调用前
-----> add User
-----> 函数调用后

CGLIB动态代理

可以对类进行代理,主要对指定的类生成一个子类。通常情况下都使用JDK代理,因为业务基本会抽象成一个接口。

public class UserDaoCglib implements MethodInterceptor {

private UserDao userDao;

public UserDaoCglib(UserDao userDao) {
super();
this.userDao = userDao;
}

public UserDao createProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(userDao.getClass());
enhancer.setCallback(this);
return (UserDao) enhancer.create();
}

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if (method.getName().equals("addUser")) {
// ===
System.out.println("-----> 函数调用前");
Object r = methodProxy.invokeSuper(o, objects);
// ===
System.out.println("-----> 函数调用后");
return r;
}
return methodProxy.invokeSuper(userDao, objects);
}
}

测试:

UserDao u = new UserDaoImpl();
UserDaoCglib userDaoCglib = new UserDaoCglib(u);
UserDao u1 = userDaoCglib.createProxy();
u1.addUser(1, 2);

结果:

-----> 函数调用前
-----> add User
-----> 函数调用后