二、AOP
1. 切面(Aspect)
被抽取出来的公共模块,可以用来会横切多个对象多个方法。Aspect切面可以看成Pointcut切点和Advice通知的结合,一个切面可以由多个切点和通知组成。
2. 连接点(Join point)
指程序运行过程中所执行的方法。在Spring AOP中,一个连接点总代表一个方法的执行。
3. 切点(Pointcut)
切点用于定义要对哪些连接点或者方法的执行前后(Join point)进行拦截。
4. 通知(Advice)
指要在连接点(Join Point) 上执行的动作,即增强的逻辑。比如权限校验和、日志记录,事务控制等。通知有各种类型,包括Around、Before、After、After returning、After throwing。
如何使用AOP
首先增加Spring AOP的相关依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring-version}</version> </dependency> 复制代码
新增aspect包,增加一个LogAspect切面类,@Component注解表示要将切面类注入到Spring容器中,@Aspect表示这是一个切面类,针对div方法定义了异常通知,对add方法定义了前置通知,后置通知返回通知和环绕通知
@Component @Aspect public class LogAspect { @Pointcut("execution(public * com.citi.util.impl.AppleCalculator.add(int, int))") public void addPointCut(){ } @Pointcut("execution(public * com.citi.util.impl.AppleCalculator.div(int, int))") public void divPointCut(){ } @Before("addPointCut()") public void logStart(JoinPoint joinPoint ){ System.out.println("方法调用前的输出"); System.out.println("方法名:" + joinPoint.getSignature()); System.out.println("方法参数:" + Arrays.toString(joinPoint.getArgs())); } @After("addPointCut()") public void logEnd(){ System.out.println("方法调用后的输出"); } @AfterReturning("addPointCut()") public void logReturn(){ System.out.println("方法输出返回后输出"); } @AfterThrowing("divPointCut()") public void logException(){ System.out.println("方法抛出异常后输出"); } @Around("addPointCut()") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("环绕通知:方法执行前"); Object o = joinPoint.proceed(); System.out.println("环绕通知:方法执行后输出" + o.toString()); return o; } } 复制代码
编写测试类,注意注入的是Calculator接口类,不是AppleCalculator实现类
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:application.xml") public class AppleCalculatorTest { @Autowired private Calculator calculator; @Test public void add() { calculator.add(2,1); } @Test public void div() { calculator.div(2,0); } } 复制代码
增加xml配置,开启AOP
<!--开启AOP--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> 复制代码
执行测试,div的异常通知可以正常输出,add方法的前置后置返回通知和环绕通知也都可以正常输出
1.实现接口的情况下使用jdk动态代理创建动态代理对象
执行测试方法
@Test public void testGetBean(){ System.out.println(calculator); System.out.println(calculator.getClass()); } 复制代码
从Spring容器中获取目标对象一定要自动导入接口类,不要用本类,AOP底层是动态代理,容器中保存的组件是目标对象的代理对象
如果AppleCalculator不实现接口的情况下,使用cglib创建动态代理,修改AppleCalculator
@Component public class AppleCalculator { public int add(int x, int y) { int result = x + y; return result; } public int sub(int x, int y) { int result = x - y; return result; } public int mul(int x, int y) { int result = x * y; return result; } public int div(int x, int y) { int result = x / y; return result; } } 复制代码
测试类中注入
@Autowired private AppleCalculator calculator; 复制代码
执行testGetBean()测试方法,可以看出是CGLIB创建的代理对象
2.切入点表达式
固定格式: execution(访问权限符 返回值类型 方法全类名(参数表))
通配符:
*:
1)匹配一个或者多个字符:execution(public int com.citi.util.impl.AppleCar.(int, int))
2)匹配任意一个参数:第一个是int类型,第二个参数任意类型;(匹配两个参数) execution(public int com.citi.util.impl.AppleCa*.*(int, *))
3)*只能匹配一层路径
4)权限位置不能使用*,权限位置不写就行或者public(可选)
..:
1)匹配任意多个参数,任意类型参数
2)匹配任意多层路径:execution(public int com.citi..AppleCa*.*(..));
记住两种:
最精确的:execution(public int com.citi.util.impl.AppleCalculator.add(int,int))
最模糊的:execution(* .(..)),不要使用这种最模糊的切点表达式
"&&”、“||”、“!"
&&:我们要切入的位置同时满足这两个表达式 execution(public int com.citi.util.impl.AppleCalculator.add(..))&&execution(* .(int,int))
||:满足任意一个表达式即可 execution(public int com.citi..AppleC*.(..))&&execution(.(int,int))
!:只要不是这个位置都切入!execution(public int com.citi..AppleC*.*(..))
3.通知方法的执行顺序
没有环绕通知的情况下,使用try/catch包裹:
try{ @Before // 方法执行 method.invoke(obj,args); // 正常执行后返回 @AfterReturning }catch(){ // 异常执行 @AfterThrowing }finally{ @After } 复制代码
正常执行:@Before前置通知->@AfterReturning正常返回->@After后置通知
异常执行:@Before前置通知->@After后置通知->@AfterThrowing异常通知