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); } }
图解:
总结: