简介
AOP(Aspect Oriented Programming),面向切面编程技术。AOP是基于IOC的基础上。
从图中看出,使用横行的通用逻辑中切入到业务逻辑中,在逐渐完善的横向通用逻辑后,让编码人员更专注于业务逻辑工作。
基本概念
切面(Aspect)
一个关注点的模块化,这个关注点实现可能另外横切多个对象。包括了横切的逻辑。切面用Spring的Advisor或拦截器实现。
连接点(Joinpoint)
程序执行到某个特定的位置。如方法的调用或者某个位置的异常抛出。
通知(Advice)
接入到连接点上的程序代码。
前置通知(before)
在连接点之前调用。
后置通知(after)
在连接点退出的时候调用,不论是正常返回还是异常退出。
异常通知(afterThrowing)
在方法抛出异常退出的时候调用。
返回通知(afterReturning)
在连接点正常完成后执行的调用,不包括抛出异常的情况
环绕通知(around)
围绕一个连接点的通知,可以在方法调用前后完成自定义的行为,也可以不执行。
切入点(Pointcut)
指定连接点作为切入点。
目标对象(TargetObject)
被切入(代理)的对象。
AOP代理(AOP Proxy)
一个类被AOP织入后,就产生了一个代理类。
编织(Weaving)
将通知增加到目标类具体到连接点上的过程。
思想概念:
运行过程:
简单案例
UserDao的实现
|
自定义的注解
UserDao实现中看到5个注解用来完成5个通知操作
- @BeforTest
- @AfterReturningTest
- @AfterThrowableTest
- @AfterTest
- @AroundTest
配置的切面
|
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
|
测试日志
|
注解说明
定义切入点函数
|
配置切入点函数
|
切入点指示符
- 通配符
- .. : 匹配方法定义中的任意数量的参数,此外还匹配类定义中的任意数量包
// 匹配任意返回值、函数名、任意参数的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
-----> 函数调用后