springAOP
一、springAOP
- aop相关术语
- 切点(pointcut),主要编写切入点(目标位置)表达式execution
- 通知(advice),在切点位置织入通知,其实就是拦截时的操作
- 切面(aspect),包含切点与通知即可
- 连接点(JoinPoint),切点位置的一些相关信息,例如:被拦截方法的参数、目标对象等
二、切点pointcut表达式execution
1.execution(访问修饰符 方法返回值 类限定名.方法(参数))
2.先看下访问修饰符与返回值的使用
execution(public void)->public修饰的无返回值方法
execution(private double)->private修饰的返回值为double的方法
execution(public *)->public修饰的任意返回值方法
execution(*)->任意修饰符及返回值方法
3.类限定名与方法参数的使用,"*"匹配任意字符串,".."表示任意数量(用在参数就是任意参数个数,用在路径下,就是任意子包)
execution(* com.xxf.demo.Service.*(double))->匹配类Service下任意方法且参数为double
execution(* com.xxf.demo.Service.*(..))->匹配类Service下任意方法且参数个数任意、类型任意
execution(* com.xxf.demo.Service.*(double ...))->匹配类Service下任意方法,第一个参数为double,其他参数个数、类型任意(这里要三个点)
execution(* com.xxf.demo.Service.*(com.xxf.demo.pojo.User ...))->除了基本类型以及对应的包装类,其他都需要全类限定名,eg:java.util.Date
execution(* com.xxf.demo.Service.set*(..))->匹配类Service下set开头的方法,且参数个数、类型任意
execution(* com.xxf.demo.Service.*User(..))->匹配类Service下User结尾的方法,且参数个数、类型任意
execution(* com.xxf.demo..Service.*User(..))->匹配任意包下Service类的...方法
execution(* com.xxf.demo..Service..*User(..))->匹配任意包下Service类的任意内部类的...方法
注意,静态方法不能用aop拦截!!!
配置pointcut就是为了告诉aop拦截的位置,有了位置,我们就需要拦截时做一些操作,也就是通知Advice
这时还会有个需求,那就是我们可能需要被拦截方法的信息,这时就有了连接点JoinPoint,用于储存参数等信息
三、通知(拦截方法后的操作)
1.通知在拦截后提供五个操作,且执行顺序按着这个排序:around、before、after、afterReturning、AfterThrowing
2.先说如何使用,可以使用注解或者使用xml配置,这里直接贴代码
3.我进行了源码的模拟,使用责任链模式以及测试
注解创建切面
//交给ioc创建管理
@Component
//使用该注解告知是一个切面
@Aspect
public class AspectBook {
//这里可以减少代码的冗余,相当于一个方法
@Pointcut("execution(* com.xxf.demo.service.impl.BookServiceImpl.A.one(java.util.Date))")
public void pointcut(){}
/**
* 1.还可以这么写,不过这样太多重复了,所以使用@Pointcut进行统一管理
* @After(value = "execution(* com.xxf.demo.service.impl.BookServiceImpl.A.one(java.util.Date))")
*
* 2.使用全类限定名.方法也可以,同理,其他类的也可以使用
* @After(value = "com.xxf.demo.aop.AspectBook.pointcut()")
*/
//ProceedingJoinPoint是底层的责任链,调用proceed方法可以继续执行
@Around(value = "pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("书籍环绕前通知->第一个做通知");
Object obj=joinPoint.proceed();
System.out.println("书籍环绕后通知->在after通知之后,最后通知,出现异常不通知");
return obj;
}
//JoinPoint就是连接点,主要是提供被拦截方法的一些信息
@Before(value = "pointcut()")
public void before(JoinPoint joinPoint){
System.out.println("书籍前置通知->环绕前通知后,在通知");
}
@After(value = "pointcut()")
public void after(JoinPoint joinPoint){
System.out.println("书籍后置通知->环绕后通知前进行通知,一定会通知");
}
@AfterReturning(value = "pointcut()")
public void afterReturning(JoinPoint joinPoint){
System.out.println("目标方法执行后无异常通知");
}
@AfterThrowing(value = "pointcut()")
public void afterException(JoinPoint joinPoint){
System.out.println("目标方法执行后有异常通知,这时环绕通知的后部分不会进行输出,只会输出后置通知");
}
}
xml配置创建切面
@Component
public class LogAdvice{
public void before(JoinPoint joinPoint){
System.out.println("前置通知");
}
public void after(JoinPoint joinPoint){
System.out.println("后置通知");
}
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前通知");
Object obj=joinPoint.proceed();
System.out.println("环绕后通知");
return obj;
}
public void afterReturn(JoinPoint joinPoint){
System.out.println("返回结果通知");
}
public void exception(JoinPoint joinPoint){
System.out.println("出现异常通知");
}
}
<aop:config>
<!--ref依赖通知,而LogAdvice通知使用了@Component注解,所以可以直接获取,也可以自己使用bean标签配置-->
<aop:aspect ref="logAdvice" >
<!--切点,编写表达式即可-->
<aop:pointcut id="logPointcut" expression="execution(public * com.xxf.demo.service.impl.UserServiceImpl2.update*(com.xxf.demo.pojo.Book ...))"/>
<!--需要要通知类配置对应的注解方法,这里执行方法的顺序有点问题,更换一下位置,输出顺序就不对了-->
<aop:before method="before" pointcut-ref="logPointcut"/>
<aop:after method="after" pointcut-ref="logPointcut"/>
<aop:around method="around" pointcut-ref="logPointcut"/>
<aop:after-returning method="afterReturn" pointcut-ref="logPointcut"/>
<aop:after-throwing method="exception" pointcut-ref="logPointcut"/>
</aop:aspect>
</aop:config>
1.这下俩种方法的配置都有了,执行顺序我看过别人的,跟我执行的不一样,我不大确定是什么问题,我现在是用的spring aop(最新版)来测试的,没有使用springboot
2.先说下执行顺序,使用注解的执行顺序时一定的:around->before->after->afterReturning->afterThrowing,这种执行顺序
输出的结果是这样的:
I.无异常情况下
环绕前通知->前置通知->对应方法执行->无异常返回通知->后置通知->环绕后通知
II.有异常情况下
环绕前通知->前置通知->对应方法执行->异常通知->后置通知
3.不使用注解时,执行顺序有些奇怪...
springAOP底层使用责任链模式(拦截器模式),我模拟了其实现方式,这里模拟的使用注解情况下,不使用注解顺序很奇怪,没有深入了解
import java.lang.reflect.Method;
public class AopAdviceChain {
//通知
private Advice[] advices;
//被拦截的对象
private Object target;
//被拦截对象的方法
private Method method;
//被拦截对象的方法的参数
private Object[] args;
//当前通知索引值
private int p;
//通知数量
private int n;
public AopAdviceChain(Object target,Method method,Object[] args,Advice[] advices){
this.target=target;
this.method=method;
this.args=args;
this.advices=advices;
this.p=0;
this.n=advices.length;
}
//执行方法
public Object proceed() throws Throwable{
if(p<n){
return advices[p++].proceed(this);
}
Object res=null;
//这里大致实现一下
//无返回值,就不接收了
if(method.getReturnType()==void.class)
method.invoke(target,args);
else
res=method.invoke(target,args);
return res;
}
//环绕通知
public abstract static class AroundAdvice implements Advice{
//使用模板设计模式,交给开发者去实现,这个通知去其他通知不一样就在,将链的继续执行交给开发调用
public abstract Object around(AopAdviceChain chain) throws Throwable;
public Object proceed(AopAdviceChain chain) throws Throwable{
return around(chain);
}
}
//前置通知
public abstract static class BeforeAdvice implements Advice{
//Advice通知里有提供连接点,这里不好实现,就不提供了
public abstract void before();
@Override
public Object proceed(AopAdviceChain chain) throws Throwable{
before();
return chain.proceed();
}
}
//后置通知
public abstract static class AfterAdvice implements Advice{
public abstract void after();
@Override
public Object proceed(AopAdviceChain chain) throws Throwable{
//后置通知关键就在这里,先不调用实现的方法,而是继续走责任链,如果往下继续走,出现异常了,那么"环绕通知后"就不会执行了
try{
return chain.proceed();
}finally {
//这里很巧妙,无论如何都会执行
after();
}
}
}
//无异常通知
public abstract static class AfterReturning implements Advice{
public abstract void afterReturning();
@Override
public Object proceed(AopAdviceChain chain) throws Throwable {
//无异常通知,继续走责任链,如果出现异常,那么第二个语句就不输出,返回到后置通知那执行finally
Object res=chain.proceed();
afterReturning();
return res;
}
}
//异常通知
public abstract static class AfterThrowing implements Advice{
public abstract void afterThrowing();
@Override
public Object proceed(AopAdviceChain chain) throws Throwable{
//继续走责任链,有异常进行捕获
try{
return chain.proceed();
}catch (Throwable throwable){
afterThrowing();
//捕获后继续抛出
throw throwable;
}
}
}
//通知接口
public interface Advice{
Object proceed(AopAdviceChain chain) throws Throwable;
}
}
实现对应的抽象类(我们开发时,就是使用注解,让框架使用反射技术获取对应的方法,我们这里测试就直接实现对应的抽象类,可能跟框架不完全一样,但大致思路就是这样子),进行测试…
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class TestChain {
public static void main(String[] args) throws Throwable{
List<AopAdviceChain.Advice> advices=new ArrayList<>();
//初始化通知,框架的话,就是使用一些反射获取通知,我们这里就直接实现,模拟就行了
initAdvice(advices);
//创建被拦截的方法信息
TargetTest target=new TargetTest();
Method method=target.getClass().getMethod("print", String.class);
Object[] arg=new Object[]{"小明"};
//创建责任链
AopAdviceChain chain=new AopAdviceChain(target,method,arg,advices.toArray(new AopAdviceChain.Advice[0]));
//执行责任链
chain.proceed();
}
//被拦截的类以及方法
public static class TargetTest{
public void print(String name){
System.out.println(name+"执行了目标方法");
}
public String getName(String name){
print(name);
return name;
}
}
//使用匿名内部类,不然一个个创建实现类有点麻烦
public static void initAdvice(List<AopAdviceChain.Advice> advices){
//环绕通知
advices.add(new AopAdviceChain.AroundAdvice() {
@Override
public Object around(AopAdviceChain chain) throws Throwable{
System.out.println("环绕前通知");
Object res=chain.proceed();
System.out.println("环绕后通知:我拿到返回值了,为res->"+res);
return res;
}
});
//前置通知
advices.add(new AopAdviceChain.BeforeAdvice() {
@Override
public void before() {
System.out.println("前置通知");
}
});
//后置通知
advices.add(new AopAdviceChain.AfterAdvice() {
@Override
public void after() {
System.out.println("后置通知");
}
});
//无异常通知
advices.add(new AopAdviceChain.AfterReturning() {
@Override
public void afterReturning() {
System.out.println("无异常通知");
}
});
//异常通知
advices.add(new AopAdviceChain.AfterThrowing() {
@Override
public void afterThrowing() {
System.out.println("异常通知");
}
});
}
}
测试结果,分为无异常与有异常,同时测试返回值
无异常情况无返回值
无异常有返回值
有异常,执行的方法需要参数,而我没有提供参数过去,在发生异常后,执行了异常通知,与后置通知,而环绕后通知没有执行