SpringAop学习之细节

package com.sf.jin;

import com.sf.annotation.Service.Calculator;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author JinJian
 * @version 1.0
 * @date 2022/3/12 16:20
 */
public class SpringAopTest {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("spring01.xml");
       //从IOC容器里面拿到目标对象,如果用类型一定要使用其接口类型,不要用他的本类
        Calculator bean = context.getBean(Calculator.class);
        bean.add(2,1);
        System.out.println(bean);
        System.out.println(bean.getClass());
    }



}

运行结果:

方法开始运行,参数列表是
方法最终完成,参数列表是
方法正常执行完成,参数列表是
com.sf.annotation.ServiceImpl.CalculatorImpl@7b98f307
class com.sun.proxy.$Proxy16

从测试结果可以看出,我们从IOC容器里面拿到bean的类是class com.sun.proxy.$Proxy16

因为AOP的底层是动态代理,容器里面保存的是它的代理对象,而代理对象和目标对象唯一的联系点就是接口,所以在获取bean的时候使用的类型是接口类型而不是它的本类。

这是在使用动态代理的时候,IOC容器里面保存的是目标类的代理对象,如果没有面向切面编程就按原来的方法去写。

细节二:

如果目标对象没有实现接口,Spring也能帮我们实现动态代理。我们将该类改为不实现接口

package com.sf.annotation.ServiceImpl;

import org.springframework.stereotype.Service;

/**
 * @author JinJian
 * @version 1.0
 * @date 2022/3/10 11:13
 */
@Service
public class CalculatorImpl /*implements Calculator*/ {
//    @Override
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }

//    @Override
    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }

//    @Override
    public int mul(int i, int j) {
        int result = i * j;
        return result;
    }

//    @Override
    public int div(int i, int j) {
        int result = i / j;
        return result;
    }
}

测试方法如下:

package com.sf.jin;

import com.sf.annotation.Service.Calculator;
import com.sf.annotation.ServiceImpl.CalculatorImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author JinJian
 * @version 1.0
 * @date 2022/3/12 16:20
 */
public class SpringAopTest {

    @Test
    public void test01(){
        ApplicationContext context = new ClassPathXmlApplicationContext("spring01.xml");

        CalculatorImpl bean = context.getBean(CalculatorImpl.class);
        bean.add(2,1);
        System.out.println(bean);
        System.out.println(bean.getClass());
    }



}

打印结果:

方法开始运行,参数列表是
方法最终完成,参数列表是
方法正常执行完成,参数列表是
com.sf.annotation.ServiceImpl.CalculatorImpl@fd07cbb
class com.sf.annotation.ServiceImpl.CalculatorImpl$$EnhancerByCGLIB$$45cf160e

bean的class是class com.sf.annotation.ServiceImpl.CalculatorImpl$$EnhancerByCGLIB$$45cf160e

这实际上是 CGLIB为我们创建的一个代理对象,实际上还是一个代理对象,这样Spring在我们没有实现接口的时候也可以使用动态代理来实现面向切面编程。

AOP的细节二:通配符

*  和 ..   ,这两个通配符的作用在下面的代码块里面都写了

package com.sf.annotation.bean;

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Service;

/**
 * @author JinJian
 * @version 1.0
 * @date 2022/3/10 11:16
 * 如何让切面类中的通知方法动态地在目标方法的各个位置切入
 *
 *
 */
@Aspect
@Service
public class LogUtils {
    /*
    * 还得告诉spring这些方法什么时候开始执行
    * @Before 在目标方法运行之前执行
    * @AfterReturning 在目标方法正常返回之后执行
    * @AfterThrowing  在目标方法抛出异常之后执行
    * @After  在目标方法结束之后运行
    * @Around  环绕
    *
    * 切入点表达式的写法:
    * 固定格式 execution(访问权限 返回值类型 方法全类名(参数列表))
    *
    * 通配符
    * *:
    * 1)匹配一个或多个字符:  execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, int))
    * 2)匹配任意一个参数:execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, *))
    *   第一个参数是int类型,第二个参数是任意类型
    * 3)只能匹配一层路径: execution(public int com.sf.annotation.ServiceImpl.*.*(int, *))
    * 4)权限位置不能写*
    * ..:
    * 1)匹配任意多个和任意类型的参数 execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(..))
    * 2)匹配任意多层路径:execution(public int com.sf.annotation..CalculatorImpl.*(..))
    *
    * 记住两种表达式:
    * 1)最精确 execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, int))
    * 2)最模糊 execution(* *.*(..))
    *
    * 切入点表达式还能使用逻辑修饰符去连接
    * && || !
    * execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, int)) && execution(* *.*(..))
    * 两个表达式都需要满足
    * */
    //目标方法执行之前执行
    @Before("execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, int))")
    public static void logStart(){
        System.out.println("方法开始运行,参数列表是");
    }

    //目标方法正常执行完成之后执行
    @AfterReturning("execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, int))")
    public static void logReturn(){
        System.out.println("方法正常执行完成,参数列表是");
    }

    //目标方法出现异常的时候执行
    @AfterThrowing("execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, int))")
    public static void logException(){
        System.out.println("方法异常,参数列表是");
    }

    //目标方法结束的时候执行
    @After(("execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, int))"))
    public static void logEnd(){
        System.out.println("方法最终完成,参数列表是");
    }
}

AOP的细节三:通知方法的执行顺序

/*
* 通知方法的执行顺序
*        try{
*        @Before
*        method.invoke(obj,args)
*        @AfterRetrning
        }catch (){
        @AfterThrowing
        }finally {
        @After
        }

方法正常执行:          @Before====method.invoke(obj,args)====@After====@AfterRetrning
方法异常执行:@Before====method.invoke(obj,args)====@After===@AfterThrowing
* */

本来应该时结束方法放在最后的,看起来有些别扭, 但是就是这个规定的。

AOP细节四:在通知方法运行时拿到目标方法的详细信息

细节四;在通知方法运行时拿到目标方法的详细信息 *

1、在通知方法的参数列表上面写一个参数JoinPoint joinPoint,该参数封装了目标方法的详细信息 * 2、用哪个参数来接受返回值,以及异常情况,只需要在切入点表达式里面加入一些参数如returning = "res" throwing="exception"等 * @AfterReturning(value = "execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, int))",returning = "res" ) * @AfterThrowing(value = "execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, int))", throwing="exception")

package com.sf.annotation.bean;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Service;

import java.util.Arrays;

/**
 * @author JinJian
 * @version 1.0
 * @date 2022/3/10 11:16
 * 如何让切面类中的通知方法动态地在目标方法的各个位置切入
 *
 *
 */
@Aspect
@Service
public class LogUtils {
    /*
* 通知方法的执行顺序
*        try{
*        @Before
*        method.invoke(obj,args)
*        @AfterRetrning
        }catch (){
        @AfterThrowing
        }finally {
        @After
        }

        方法正常执行:@Before====method.invoke(obj,args)====@After====@AfterRetrning
        方法异常执行:@Before====method.invoke(obj,args)====@After===@AfterThrowing
* */
    /*
    * 还得告诉spring这些方法什么时候开始执行
    * @Before 在目标方法运行之前执行
    * @AfterReturning 在目标方法正常返回之后执行
    * @AfterThrowing  在目标方法抛出异常之后执行
    * @After  在目标方法结束之后运行
    * @Around  环绕
    *
    * 切入点表达式的写法:
    * 固定格式 execution(访问权限 返回值类型 方法全类名(参数列表))
    *
    * 通配符
    * *:
    * 1)匹配一个或多个字符:  execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, int))
    * 2)匹配任意一个参数:execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, *))
    *   第一个参数是int类型,第二个参数是任意类型
    * 3)只能匹配一层路径: execution(public int com.sf.annotation.ServiceImpl.*.*(int, *))
    * 4)权限位置不能写*
    * ..:
    * 1)匹配任意多个和任意类型的参数 execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(..))
    * 2)匹配任意多层路径:execution(public int com.sf.annotation..CalculatorImpl.*(..))
    *
    * 记住两种表达式:
    * 1)最精确 execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, int))
    * 2)最模糊 execution(* *.*(..))
    *
    * 切入点表达式还能使用逻辑修饰符去连接
    * && || !
    * execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, int)) && execution(* *.*(..))
    * 两个表达式都需要满足
    * */

    /*
    * 细节四;在通知方法运行时拿到目标方法的详细信息
    * 1、在通知方法的参数列表上面写一个参数JoinPoint joinPoint,该参数封装了目标方法的详细信息
    * 2、用哪个参数来接受返回值,以及异常情况,只需要在切入点表达式里面加入一些参数如returning = "res"  throwing="exception"等
    * @AfterReturning(value = "execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, int))",returning = "res" )
    * @AfterThrowing(value = "execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, int))", throwing="exception")
    * 
    * */
    //目标方法执行之前执行
    @Before("execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, int))")
    public static void logStart(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        String name = joinPoint.getSignature().getName();
        System.out.println(name + "方法开始运行,参数列表是"+ Arrays.asList(args));
    }

    //目标方法正常执行完成之后执行
    @AfterReturning(value = "execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, int))",returning = "res" )
    public static void logReturn(JoinPoint joinPoint,Object res){
        String name = joinPoint.getSignature().getName();
        System.out.println(name + "方法正常执行完成,结果为"+res);
    }

    //目标方法出现异常的时候执行
    @AfterThrowing(value = "execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, int))", throwing="exception")
    public static void logException(JoinPoint joinPoint,Exception exception){
        String name = joinPoint.getSignature().getName();
        System.out.println(name + "方法异常,异常信息是" + exception);
    }

    //目标方法结束的时候执行
    @After(("execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, int))"))
    public static void logEnd(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println(name + "方法最终完成");
    }
}

细节5:Spring对通知方法不严格,唯一需要注意的是通知方法的参数列表不能乱写

细节6:通知方法上面的切入点表达式,也可以写在一个方法上面,这样可以解耦,需要修改切入点表达式时只需要修改该方法上面的切入点表达式。

抽取可重用的切入点表达式:

1、随便声明一个没有实现的返回void的空方法

2、给方法标注上@Pointcut注解,切入点表达式写在注解后面的括号里面

3、 直接在@Before等注解后面写上该方法名即可,需要修改切入点表达式的时候只需要修改该方法上面的@Pointcut注解里面的切入点表达式即可

细节7环绕通知:

  /*
    * 环绕通知:是Spring里面最强大的通知
    * 环绕通知其实就是一个动态代理:
    *
    *         try {
    *               //前置通知
                    method.invoke(obj,args);
                    //返回通知
                } catch (Exception e) {
                    e.printStackTrace();
                    //异常通知
                } finally {
                    //后置通知
                }

        环绕通知就是上面四种通知四合一的通知

        环绕通知和普通通知放在一起的执行顺序:
        环绕前置====普通前置====目标方法执行====环绕返回/异常====环绕后置===普通后置===普通返回/异常


    * */
    @Around(("execution(public int com.sf.annotation.ServiceImpl.CalculatorImpl.*(int, int))"))
    public static Object MyAround(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        String name = pjp.getSignature().getName();

        Object proceed = null;

        try {
            //@Before
            System.out.println("[环绕前置通知]"+name+"方法开始了");
            //就是通过反射调用目标方法,就是method.invoke(obj,args);这个方法一定要执行
            proceed = pjp.proceed(args);
            //@AfterReturning
            System.out.println("[环绕返回通知]"+name+"方法正常返回,返回值是"+proceed);
            } catch (Exception e) {
            //@AfterThrowing
            System.out.println("[环绕异常通知]"+name+"方法返回异常,异常是"+e);
            //这里的异常要抛出去,这样普通通知才能拿到,不然普通通知拿不到异常
            throw new RuntimeException(e);
            } finally {
            //@After
            System.out.println("[环绕后置通知]"+name+"方法结束");
            }

        //反射调用后的返回值也一定要返回出去
        return proceed;
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值