springAOP

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("异常通知");
            }
        });
    }
}

测试结果,分为无异常与有异常,同时测试返回值
无异常情况无返回值

在这里插入图片描述
无异常有返回值
在这里插入图片描述
有异常,执行的方法需要参数,而我没有提供参数过去,在发生异常后,执行了异常通知,与后置通知,而环绕后通知没有执行

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值