本文最后更新于:4 个月前
spring-day03 第一章 AOP面向切面编程 第一节 AOP的概述 1. 为什么需要AOP 1.1 情景设定 1.1.1 声明一个计算器接口 1 2 3 4 5 6 7 8 9 10 11 public interface Calculator { int add (int i, int j) ; int sub (int i, int j) ; int mul (int i, int j) ; int div (int i, int j) ; }
1.1.2 给接口声明一个纯净版实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.atguigu.proxy.imp; import com.atguigu.proxy.api.Calculator; public class CalculatorPureImpl 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; } }
1.1.3 需求 在计算器的每个方法执行前后加入日志打印:
1.1.4 实现方案探讨 方案一: 在每个方法的前后都加上日志打印的代码
方案二: 创建一个工具类,将日志打印的代码写在工具类中,然后在每个方法的前后直接调用工具类中的方法打印日志
方案三: 创建一个父类,在父类的方法中打印日志,子类重写父类的方法(对目前功能不适用)
方案四: 动态代理
方案五: AOP
2. 代理模式 2.1 概念 二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接 调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦 。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
未经过代理的情况:
使用了代理模式的情况:
2.2 相关术语
代理: 又称之为代理者,用于将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法
目标: 又称之为被代理者,用于执行核心逻辑,并且将代理者的非核心逻辑代码套用 在目标类、对象、方法上
2.3 静态代理 2.3.1 创建静态代理类: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class CalculatorStaticProxy implements Calculator { private Calculator target; public CalculatorStaticProxy (Calculator target) { this .target = target; } @Override public int add (int i, int j) { System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j); int addResult = target.add(i, j); System.out.println("[日志] add 方法结束了,结果是:" + addResult); return addResult; } ……
2.3.2 问题思考 静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。
提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。
2.4 动态代理
2.4.1 创建生产代理对象的工厂类 JDK本身就支持动态代理,这是反射技术的一部分。下面我们还是创建一个代理类(生产代理对象的工厂类):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package com.atguigu.factory;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class LogDynamicProxyFactory <T > { private T target; public LogDynamicProxyFactory (T target) { this .target = target; } public T getProxy () { Class<?> clazz = target.getClass(); ClassLoader classLoader = clazz.getClassLoader(); Class<?>[] interfaces = clazz.getInterfaces(); T t = (T) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { String proxyMethodName = method.getName(); if (proxyMethodName.equals("add" ) || proxyMethodName.equals("sub" ) || proxyMethodName.equals("mul" ) || proxyMethodName.equals("div" )) { System.out.println("[日志] " +proxyMethodName+"方法开始了,参数是:" + args[0 ] + "," + args[1 ]); Object result = method.invoke(target, args); System.out.println("[日志] " +proxyMethodName+" 方法结束了,结果是:" + result); return result; } return method.invoke(target,args); } }); return t; } }
2.4.2 测试 1 2 3 4 5 6 7 8 9 10 11 @Test public void testDynamicProxy () { Calculator calculator = new CalculatorPureImpl(); LogDynamicProxyFactory<Calculator> proxyFactory = new LogDynamicProxyFactory<Calculator>(calculator); Calculator calculatorProxy = proxyFactory.getProxy(); System.out.println(calculatorProxy.sub(3 , 4 )); }
3. AOP的相关概念 3.1 概念 AOP:Aspect Oriented Programming面向切面编程
3.2 作用
简化代码:把方法中固定位置的重复的代码抽取 出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。
代码增强:把抽取出来的特定的功能封装到切面类中,看哪里有需要,就往上套,被套用 了切面逻辑的方法就被切面给增强了。
3.3 AOP的核心思路
3.4 AOP的相关术语 3.4.1 横切关注点 横切关注点是从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。
这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。
3.4.2 通知 每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。
前置通知:在被代理的目标方法前 执行
返回通知:在被代理的目标方法成功结束 后执行(寿终正寝 )
异常通知:在被代理的目标方法异常结束 后执行(死于非命 )
后置通知:在被代理的目标方法最终结束 后执行(盖棺定论 )
环绕通知:使用try…catch…finally结构围绕整个 被代理的目标方法,包括上面四种通知对应的所有位置
3.4.3 切面 封装通知方法的类。
3.4.4 目标 被代理的目标对象。
3.4.5 代理 向目标对象应用通知之后创建的代理对象。
3.4.6 连接点 这也是一个纯逻辑概念,不是语法定义的。
把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。连接点其实就是各个方法中可以被增强或修改的点
3.4.7 切入点 每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。而切入点指的则是方法中真正要去配置增强或者配置修改的地方
第二节 基于注解方式配置AOP 1. 基于注解的AOP用到的技术
动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口 。
cglib:通过继承被代理的目标类 实现代理,所以不需要目标类实现接口。
AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件 ,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
2. 实现基于注解的AOP 2.1 加入依赖 在IOC所需依赖基础上再加入下面依赖即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <dependencies > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 5.3.1</version > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.12</version > <scope > test</scope > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aspects</artifactId > <version > 5.3.1</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-test</artifactId > <version > 5.3.1</version > </dependency > </dependencies >
2.2 准备被代理的目标资源 2.2.1 接口 1 2 3 4 5 6 7 8 9 10 11 public interface Calculator { int add (int i, int j) ; int sub (int i, int j) ; int mul (int i, int j) ; int div (int i, int j) ; }
2.2.2 接口的实现类 在Spring环境下工作,所有的一切都必须放在IOC容器中。现在接口的实现类是AOP要代理的目标类,所以它也必须放入IOC容器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.atguigu.component;import org.springframework.stereotype.Component;@Component public class CalculatorPureImpl 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; } }
2.2.3 创建切面类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 package com.atguigu.aspect;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;@Aspect @Component public class LogAspect { @Before("com.atguigu.aspect.AtguiguPointcut.pt1()") public void printLogBefore (JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); Signature signature = joinPoint.getSignature(); String methodName = signature.getName(); System.out.println("[日志] " +methodName+" 方法开始了,参数是:" + args[0 ] + "," + args[1 ]); } @AfterReturning(value = "com.atguigu.aspect.AtguiguPointcut.pt1().pt1()",returning = "result") public void printLogAfterReturning (JoinPoint joinPoint,int result) { String methodName = joinPoint.getSignature().getName(); System.out.println("[日志] " +methodName+" 方法结束了,结果是:" + result); } @AfterThrowing(value = "com.atguigu.aspect.AtguiguPointcut.pt1().pt1()",throwing = "throwable") public void printLogAfterThrowing (JoinPoint joinPoint,Throwable throwable) { String methodName = joinPoint.getSignature().getName(); System.out.println("[日志] " +methodName+" 方法出现了异常,异常信息是:" +throwable.getStackTrace()); } @After("com.atguigu.aspect.AtguiguPointcut.pt1().pt1()") public void printLogAfter (JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("[日志] " +methodName+" 方法结束了" ); } @Around("com.atguigu.aspect.AtguiguPointcut.pt1()") public Object printLogAround (ProceedingJoinPoint proceedingJoinPoint) { long startTime = System.currentTimeMillis(); try { Object result = proceedingJoinPoint.proceed(); return result; } catch (Throwable throwable) { throwable.printStackTrace(); throw new RuntimeException(throwable.getMessage()); } finally { long endTime = System.currentTimeMillis(); System.out.println(endTime - startTime); } } }
2.2.4 创建Spring的配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:context ="http://www.springframework.org/schema/context" xmlns:aop ="http://www.springframework.org/schema/aop" 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" > <context:component-scan base-package ="com.atguigu" /> <aop:aspectj-autoproxy /> </beans >
2.2.5 测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.atguigu;import com.atguigu.component.Calculator;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:spring-application.xml") public class TestAop { @Autowired private Calculator calculator; @Test public void testAdd () { System.out.println("返回值是:" +calculator.add(1 , 2 )); } }
打印效果如下:
[AOP前置通知] 方法开始了 方法内部 result = 12 [AOP返回通知]
方法成功返回了 [AOP后置通知] 方法最终结束了 方法外部 add = 12
2.3 通知执行顺序
Spring版本5.3.x以前:
Spring版本5.3.x以后:
3. 在通知内部获取细节信息 3.1 JoinPoint接口 org.aspectj.lang.JoinPoint
要点1:JoinPoint接口通过getSignature()方法获取目标方法的签名
要点2:通过目标方法签名对象获取方法名
要点3:通过JoinPoint对象获取外界调用目标方法时传入的实参列表组成的数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Before(value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))") public void printLogBeforeCore (JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); String methodName = signature.getName(); System.out.println("methodName = " + methodName); int modifiers = signature.getModifiers(); System.out.println("modifiers = " + modifiers); String declaringTypeName = signature.getDeclaringTypeName(); System.out.println("declaringTypeName = " + declaringTypeName); Object[] args = joinPoint.getArgs(); List<Object> argList = Arrays.asList(args); System.out.println("[AOP前置通知] " + methodName + "方法开始了,参数列表:" + argList); }
需要获取方法签名、传入的实参等信息时,可以在通知方法声明JoinPoint类型的形参。
3.2 获取目标方法的方法返回值 只有在AfterReturning返回通知中才能够获取目标方法的返回值
通过@AfterReturning注解的returning属性获取目标方法的返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @AfterReturning( value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))", returning = "targetMethodReturnValue" ) public void printLogAfterCoreSuccess (JoinPoint joinPoint, Object targetMethodReturnValue) { String methodName = joinPoint.getSignature().getName(); System.out.println("[AOP返回通知] " +methodName+"方法成功结束了,返回值是:" + targetMethodReturnValue); }
3.3 获取目标方法抛出的异常 只有在 AfterThrowing 异常通知中才能获取到目标方法抛出的异常
通过@AfterThrowing注解的throwing属性获取目标方法抛出的异常对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @AfterThrowing( value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))", throwing = "targetMethodException" ) public void printLogAfterCoreException (JoinPoint joinPoint, Throwable targetMethodException) { String methodName = joinPoint.getSignature().getName(); System.out.println("[AOP异常通知] " +methodName+"方法抛异常了,异常类型是:" + targetMethodException.getClass().getName()); }
打印效果局部如下:
[AOP异常通知] div方法抛异常了,异常类型是:java.lang.ArithmeticException
java.lang.ArithmeticException: / by zero
at com.atguigu.aop.imp.CalculatorPureImpl.div(CalculatorPureImpl.java:42) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
4. 切入点 4.1 重用切入点 4.1.1 声明切入点 在一处声明切入点表达式之后,其他有需要的地方引用这个切入点表达式。易于维护,一处修改,处处生效。声明方式如下:
1 2 3 @Pointcut("execution(int com.atguigu.component.CalculatorPureImpl.*(int,int))") public void calculatorPointCut () { }
4.1.2 同一个类内部引用切入点 通过方法名引入
1 2 @Before("calculatorPointCut()") public void printLogBeforeCore (JoinPoint joinPoint) {
4.1.3 在其它类中引用切入点 通过全限定名引入
1 2 @Before("com.atguigu.pointcut.AtguiguPointCut.calculatorPointCut()") public void printLogBeforeCore (JoinPoint joinPoint) {}
4.1.4 对项目中的所有切入点进行统一管理 而作为存放切入点表达式的类,可以把整个项目中所有切入点表达式全部集中过来,便于统一管理:
1 2 3 4 5 6 7 8 9 package com.atguigu.pointcut;import org.aspectj.lang.annotation.Pointcut;public class AtguiguPointCut { @Pointcut("execution(int com.atguigu.component.CalculatorPureImpl.*(int,int))") public void calculatorPointCut () { } }
4.2 切入点表达式语法 4.2.1 切入点表达式的作用 切入点表达式的作用是用于描述将代理逻辑套用在哪些目标方法上
4.2.2 语法细节
用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限
在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的。
例如:*.Hello匹配com.Hello,不匹配com.atguigu.Hello
在包名的部分,使用“*..”表示包名任意、包的层次深度任意
在类名的部分,类名部分整体用*号代替,表示类名任意
在类名的部分,可以使用*号代替类名的一部分
上面例子表示匹配所有名称以Service结尾的类或接口
在方法名部分,可以使用*号表示方法名任意
在方法名部分,可以使用*号代替方法名的一部分
上面例子表示匹配所有方法名以Operation结尾的方法
在方法参数列表部分,使用(..)表示参数列表任意
在方法参数列表部分,使用(int,..)表示参数列表以一个int类型的参数开头
在方法参数列表部分,基本数据类型和对应的包装类型是不一样的
切入点表达式中使用 int 和实际方法中 Integer 是不匹配的
在方法返回值部分,如果想要明确指定一个返回值类型,那么权限修饰符不能使用 *
1 execution(public int *..*Service.*(.., int ))
上面例子是对的,下面例子是错的:
1 execution(* int *..*Service.*(.., int ))
但是public *表示权限修饰符明确,返回值任意是可以的。
对于execution()表达式整体可以使用三个逻辑运算符号
execution() || execution()表示满足两个execution()中的任何一个即可
execution() && execution()表示两个execution()表达式必须都满足
!execution()表示不满足表达式的其他方法
4.2.3 总结
5. 环绕通知 环绕通知对应整个try…catch…finally结构,可以在目标方法的各个部位进行套用代理逻辑,它能够真正介入并改变目标方法的执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Around("com.atguigu.pointcut.AtguiguPointCut.calculatorPointCut()") public Object around (ProceedingJoinPoint proceedingJoinPoint) { try { System.out.println("开启事务...." ); Object[] args = proceedingJoinPoint.getArgs(); Object result = proceedingJoinPoint.proceed(args); System.out.println("提交事务..." ); return result; } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("回滚事务..." ); throw new RuntimeException(throwable.getMessage()); } finally { System.out.println("将连接恢复默认状态,归还连接..." ); } }
6. 切面的优先级(了解) 6.1 优先级的规则 相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套 顺序。
使用@Order注解可以控制切面的优先级:
@Order(较小的数):优先级高
@Order(较大的数):优先级低
6.2 实际意义 实际开发时,如果有多个切面嵌套的情况,要慎重考虑。例如:如果事务切面优先级高,那么在缓存中命中数据的情况下,事务切面的操作都浪费了。
此时应该将缓存切面的优先级提高,在事务操作之前先检查缓存中是否存在目标数据。
7. CGLIB的动态代理 7.1 动态代理的分类 动态代理分成两种:
第一种是JDK内置的动态代理,这种动态代理需要被代理者实现接口,如果被代理者没有实现接口,那么则无法使用JDK的动态代理
第二种是CGLIB的动态代理,在被代理类没有实现任何接口的情况下,Spring会自动使用cglib技术实现代理。
7.2 Debug查看 7.2.1 没有实现接口情况
7.2.2 有实现接口的情况
7.3 Spring中到底使用哪种动态代理 如果要创建代理对象的类实现了接口,那么就使用JDK的动态代理;如果要创建代理对象的类没有实现接口,那么就使用CGLIB的动态代理
第三节 基于XML方式配置AOP 1. 准备工作 1.1 加入依赖 和基于注解的AOP时一样。
1.2 准备代码 把基于注解的Module复制一份,修改Module名,并导入到工程中,然后去除所有AOP注解。
2. 配置Spring配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:context ="http://www.springframework.org/schema/context" xmlns:aop ="http://www.springframework.org/schema/aop" 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" > <context:component-scan base-package ="com.atguigu" /> <aop:config > <aop:aspect id ="myAspect" ref ="logAspect" > <aop:before method ="printLogBeforeCore" pointcut-ref ="calculatorPoint" /> <aop:after-returning method ="printLogAfterReturning" pointcut-ref ="calculatorPoint" returning ="result" /> <aop:after-throwing method ="printLogAfterThrowing" pointcut-ref ="calculatorPoint" throwing ="throwable" /> <aop:after method ="printLogFinallyEnd" pointcut-ref ="calculatorPoint" /> <aop:around method ="printLogAround" pointcut-ref ="calculatorPoint" /> <aop:pointcut id ="calculatorPoint" expression ="execution(* com.atguigu.component.CalculatorPureImpl.*(..))" /> </aop:aspect > </aop:config > </beans >
3. 测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.atguigu;import com.atguigu.component.Calculator;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:spring-application.xml") public class TestAop { @Autowired private Calculator calculator; @Test public void testAdd () { System.out.println("调用完目标方法之后获取返回值是:" +calculator.sub(5 , 3 )); } }
第四节 AOP总结 目标:
将目标方法中的非核心业务抽取出来制作成通知
在调用目标方法的核心业务的时候,自动将非核心业务套在核心业务上执行
实现目标:
具备一双慧眼:能识别出来哪里可以抽取
准备一个切面类: 2.1 IOC 2.2 让它成为切面类
将抽取出来的代码封装成方法,方法放在切面类中
放切面类中的方法成为通知
前置通知: Before
返回通知:AfterReturning
异常通知:AfterThrowing
后置通知:After
环绕通知Around
指定通知的作用位置(切入点): 就是引用切入点
如果切入点和和通知在同一个类中:根据方法名引用
如果切入点跟通知不在同一个类中: 根据类的全限定名.方法名来引用
声明切入点以及切入点的语法