Spring基础-06

AOP相关知识点

MyMathCalculator.java:

package com.atguigu.impl;

import com.atguigu.inter.Calculator;
import org.springframework.stereotype.Service;

/**
 * @Title: MyMathCalculator
 * @Description:
 * @Author:
 * @Version: 1.0
 * @create 2020/6/7 19:43
 */
@Service
public class MyMathCalculator /*implements Calculator*/ {

    //@Override
    public int add(int i, int j) {
        //System.out.println("【add】方法开始了,它使用的参数是:【"+i+"】,【"+j+"】");
        //考虑方法的兼容性
        //LogUtils.logStart(i, j);
        int result = i + j;
//        System.out.println("【add】方法运行完成,计算结果是:【"+result+"】");
        System.out.println("方法内部执行");
        return result;
    }

    //@Override
    public int sub(int i, int j) {
        int result = i - j;
        System.out.println("方法内部执行");
        return result;
    }

    //@Override
    public int mul(int i, int j) {
        int result = i * j;
        System.out.println("方法内部执行");
        return result;
    }

    //@Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部执行");
        return result;
    }
}

Calculator.java:

package com.atguigu.inter;

/**
 * 接口不加载在容器中
 * 实际上可以加,加了也不创建对象,只要一看这个组件是一个接口,
 * 相当于告诉Spring,ioc容器中可能有这种类型的组件
 */
public interface Calculator {

    public int add(int i, int j);

    public int sub(int i, int j);

    public int mul(int i, int j);

    public int div(int i, int j);
}

LogUtils.java:

package com.atguigu.utils;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 如何将这个类(切面类)中的这些方法(通知方法)动态地在目标方法运行的各个位置切入
 */
@Aspect//告诉spring这个类是切面类
@Component
@Order(1)//使用Order改变切面顺序,数值越小,优先级越高
public class LogUtils {

    /*public static void logStart(Method method, Object... args) {
        System.out.println("[" + method.getName() + "]方法开始执行,使用的参数列表" + Arrays.asList(args) );
    }

    public static void logReturn(Method method,Object result){
        System.out.println("[" + method.getName() + "]方法正常执行完成了,计算结果是" + result);
    }

    public static void logException(Method method, Exception e) {
        System.out.println("["+method.getName()+"]方法出现异常了,异常信息是:"+e.getCause()+",这个异常已经通知测试测试小组进行排查");
    }

    public static void logEnd(Method method) {
        System.out.println("["+method.getName()+"]方法最终结束");
    }*/

    /**
     * try{
     *
     * @Before method.invoke(obj, args);
     * @AfterReturning }catch(Exception e){
     * @AfterThrowing }finally{
     * @After }
     * 告诉Spring每个方法都什么时候运行
     * 通知注解:
     * @Before:在目标方法之前运行 ———— 前置通知
     * @After:在目标方法运行结束之后 ———— 后置通知
     * @AfterReturning:在目标方法正常返回之后 ———— 返回通知
     * @AfterThrowing:在目标方法抛出异常之后执行 ———— 异常通知
     * @Around:环绕 ———— 环绕通知
     * <p>
     * 抽取可重用的切入点表达式:
     * 1.随便声明一个没有实现的返回void的空方法
     * 2.给方法标注@Pointcut注解
     */

    @Pointcut("execution(public int com.atguigu.impl.MyMathCalculator.*(..))")
    public void hahaMyPoint() {
    }


    //想在执行目标方法之前运行:写切入点表达式
    //execution(访问权限符 返回值类型 方法签名)
//    @Before("execution(public int com.atguigu.impl.MyMathCalculator.add(int, int))")
    //所有方法*
    @Before("hahaMyPoint()")
    public static void logStart(JoinPoint joinPoint) {
        //获取目标方法运行时使用的参数
        Object[] args = joinPoint.getArgs();
        //获取到方法签名
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println("[LogUtils-前置][" + name + "]方法开始执行,使用的参数列表" + Arrays.asList(args));
    }

    /**
     * 切入点表达式的写法:
     * 固定格式:execution(访问权限符 返回值类型 方法全类名(参数表))
     *
     * 通配符:
     *      * :
     *          1)匹配一个或多个字符
     *          execution(public int com.atguigu.impl.MyMath*r.*(int, int))
     *          2)匹配任意一个参数  第一个是int类型,第二个是任意类型 (匹配两个参数)
     *          execution(public int com.atguigu.impl.MyMath*.*(int, *))")
     *          3)只能匹配一层路径
     *          4)权限位置*不能表示任意权限,权限位置不写就表示任意权限
     *             public是可选的
     *      .. :
     *          1)匹配任意多个参数,任意类型参数
     *          execution(public int com.atguigu.impl.MyMathCalculator.*(..))
     *          2)匹配任意多层路径
     *          execution(public int com.atguigu..MyMath*.*(..))
     *
     *  记住两种:
     *      最精确的:execution(public int com.atguigu.impl.MyMathCalculator.add(int, int))
     *      最模糊的:execution(* *.*(..))    *开头表示任意层,不再只表示一层
     *              任意包下的任意类的任意方法
     *              不建议写
     *
     * “&&”、“||”、“!”
     *
     * &&:我们要切入的位置满足这两个表达式
     * execution(public int com.atguigu..MyMath*.*(..))&&execution(* *.*(int,int))
     *
     * ||:满足任意一个表达式即可
     * execution(public int com.atguigu..MyMath*.*(..))||execution(* *.*(int,int))
     *
     * !:只要不是这个位置都切入
     * !execution(public int com.atguigu..MyMath*.*(..))
     */

    /**
     * 我们可以在通知方法运行的时候,拿到目标方法的详细信息
     * 1)只需要为通知方法的参数列表上写一个参数
     * JoinPoint joinPoint:封装了当前目标方法的详细信息
     */

    //想在目标方法正常执行完成之后执行
    //告诉spring这个result用来接收返回值
    //returning:参数名是什么,这里就写什么
    @AfterReturning(value = "execution(public int com.atguigu.impl.MyMathCalculator.*(int, int))", returning = "result")
    public static void logReturn(JoinPoint joinPoint, Object result) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println("[LogUtils-返回][" + name + "]方法正常执行完成了,计算结果是" + result);
    }

    /**
     * 想在目标方法出现异常的时候执行
     * 告诉spring哪个参数是用来接收异常
     * throwing = "exception"
     * Exception exception:指定通知方法可以接收哪些异常,如果范围过小,即该异常不是目标异常,该方法不会被调用
     * <p>
     * <p>
     * 类似ajax接收服务器数据
     * $.post(url,function(abc){
     * alert(abc)
     * })
     */
    @AfterThrowing(value = "execution(public int com.atguigu.impl.MyMathCalculator.*(int, int))", throwing = "exception")
    public static void logException(JoinPoint joinPoint, Exception exception) {
        System.out.println("[LogUtils-异常][" + joinPoint.getSignature().getName() + "]方法出现异常了,异常信息是:[" + exception + "],这个异常已经通知测试测试小组进行排查");
    }

    //想在目标方法结束的时候执行

    /**
     * Spring对通知方法的要求不严格
     * 唯一要求的就是方法的参数列表一定不能乱写
     * 通知方法是Spring利用反射调用的,每次方法调用得确定这个方法的参数表的值
     * 参数表上的每一个参数,Spring都得知道是什么
     * JoinPoint:认识
     * 不知道的参数一定要告诉spring这是什么
     */
    @After("execution(public int com.atguigu.impl.MyMathCalculator.*(int, int))")
    public static int logEnd(JoinPoint joinPoint) {
        System.out.println("[LogUtils-后置][" + joinPoint.getSignature().getName() + "]方法最终结束");
        return 0;
    }

    /**
     * @Around:环绕 ———— 环绕通知:是Spring中最强大的通知
     * @Around:环绕:动态代理 try{
     * @Before 前置通知
     * method.invoke(obj, args);
     * @AfterReturning 返回通知
     * }catch(Exception e){
     * @AfterThrowing 异常通知
     * }
     * finally{
     * @After 后置通知
     * }
     * <p>
     * 四合一通知就是环绕通知:
     * 环绕通知中有一个参数:ProceedingJoinPoint pjp
     *
     * 环绕通知:是优先于普通通知执行,执行顺序:
     * [普通通知]
     * {
     *
     *      try{
     *          环绕前置
     *          环绕执行:目标方法执行
     *          环绕返回
     *      }catch(Exception e){
     *          环绕出现异常
     *      }finally{
     *          环绕后置
     *      }
     *
     * }
     * [普通后置]
     * [普通方法返回/方法异常]
     *
     * 新的顺序:
     *      环绕前置---普通前置---目标方法执行---环绕正常返回/出现异常---环绕后置---普通后置---普通返回或异常
     * 注意:
     *
     */

    @Around("hahaMyPoint()")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        String name = pjp.getSignature().getName();
        Object proceed = null;
        // idea ctrl + alt + t
        try {
            //利用反射调用目标方法即可,就是method.invoke(obj,args)
            //@Before
            System.out.println("[环绕前置通知]-[" + name + "方法开始]");
            proceed = pjp.proceed();
            //@AfterReturning
            System.out.println("[环绕返回通知]-[" + name + "方法返回,返回值为:" + proceed + "]");
        } catch (Exception e) {
            //@AfterThrowing
            System.out.println("[环绕异常通知]-[" + name + "方法出现异常],异常信息" + e.getCause());
            //为了让外界能知道这个异常,这个异常一定要抛出去
            throw new RuntimeException(e);
        } finally {
            //@After
            System.out.println("[环绕后置通知最终结束]-[" + name + "]方法结束");
        }
        //反射调用后的返回值也一定返回出去
        return proceed;
    }


}

ValidateAspect.java:

package com.atguigu.utils;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * @Title: ValidateApsect
 * @Description:
 * @Author:
 * @Version: 1.0
 * @create 2020/6/8 11:53
 */

@Aspect
@Component
@Order(2)
public class ValidateAspect {

    @Before("com.atguigu.utils.LogUtils.hahaMyPoint()")
    public void logStart(JoinPoint joinPoint) {
        //获取目标方法运行时使用的参数
        Object[] args = joinPoint.getArgs();
        //获取到方法签名
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println("[VaAspect-前置][" + name + "]方法开始执行,使用的参数列表" + Arrays.asList(args));
    }

    @AfterReturning(value = "com.atguigu.utils.LogUtils.hahaMyPoint()",returning = "result")
    public void logReturn(JoinPoint joinPoint, Object result) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println("[VaAspect-返回][" + name + "]方法正常执行完成了,计算结果是" + result);
    }

    @AfterThrowing(value = "com.atguigu.utils.LogUtils.hahaMyPoint()",throwing = "exception")
    public void logException(JoinPoint joinPoint, Exception exception) {
        System.out.println("[VaAspect-异常][" + joinPoint.getSignature().getName() + "]方法出现异常了,异常信息是:[" + exception + "],这个异常已经通知测试测试小组进行排查");
    }

    @After("com.atguigu.utils.LogUtils.hahaMyPoint()")
    public int logEnd(JoinPoint joinPoint) {
        System.out.println("[VaAspect-后置][" + joinPoint.getSignature().getName() + "]方法最终结束");
        return 0;
    }
}

applicationContext.xml:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4        xmlns:context="http://www.springframework.org/schema/context"
 5        xmlns:aop="http://www.springframework.org/schema/aop"
 6        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
 7 
 8     <context:component-scan base-package="com.atguigu"></context:component-scan>
 9     <!--开启基于注解的AOP功能:AOP名称空间-->
10     <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
11 
12 </beans>

AOPTest.java:

package com.atguigu.test;

import com.atguigu.impl.MyMathCalculator;
import com.atguigu.inter.Calculator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @Title: AOPTest
 * @Description:
 * @Author:
 * @Version: 1.0
 * @create 2020/6/7 23:35
 */
//@ContextConfiguration(locations = "classpath:applicationContext.xml")
//@RunWith(SpringJUnit4ClassRunner.class)
public class AOPTest {

    ApplicationContext ioc = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");

    //@Autowired
    //private Calculator calculator;

    //1.从ioc容器中拿到目标对象,注意:一定用它的接口类型,不要用它本类
    //细节一:com.atguigu.impl.MyMathCalculator@3c947bc5
    //class com.sun.proxy.$Proxy18
    //AOP的底层就是动态代理,容器中保存的组件是它的代理对象 $Proxy18 当然不是本类的类型
    @Test
    public void test(){

        //calculator.add(1,2);
        Calculator bean = ioc.getBean(Calculator.class);
        bean.add(2,1);
        System.out.println(bean);
        System.out.println(bean.getClass());
    }

    @Test
    public void test02(){
        Calculator bean = (Calculator) ioc.getBean("myMathCalculator");
        System.out.println(bean.getClass());
    }

    @Test
    public void test03(){
        //没有接口就算本类类型
        //cglib帮我们创建好了代理对象
        //  class com.atguigu.impl.MyMathCalculator$$EnhancerBySpringCGLIB$$355554e1
        //总结:有接口就算jdk创建对象,没有接口就算cglib创建对象
        MyMathCalculator bean = ioc.getBean(MyMathCalculator.class);
        bean.add(1,2);
        System.out.println(bean.getClass());
    }

    /**
     * 通知方法的执行顺序:
     *
     * try{
     *     @Before
     *     method.invoke(obj,args);
     *     @AfterReturning
     * }catch(Exception c){
     *     @AfterThrowing
     * }finally{
     *     @After
     * }
     *
     * 正常执行:@Before(前置通知)-----> @After(后置通知) -----> @AfterReturning(正常返回)
     * 异常执行:@Before(前置通知)-----> @After(后置通知) -----> @AfterThrowing(异常通知)
     */
    //切面执行顺序:以类名首字母位置排序,排序靠前的先执行
    //@Order(1)//使用Order改变切面顺序,数值越小,优先级越高
    @Test
    public void test04(){
        MyMathCalculator bean = ioc.getBean(MyMathCalculator.class);
        bean.add(1,2);
        System.out.println("============");
//        bean.div(1,0);
        bean.div(1,1);
    }
}

 图解:

总结:

 

posted @ 2020-06-08 17:25  清晨的第一抹阳光  阅读(121)  评论(0)    收藏  举报