文章目录:
1.3 在Spring配置文件中声明AspectJ的自动代理生成器
1.引子
承接了上一篇文章:https://blog.csdn.net/weixin_43823808/article/details/114637706
这篇文章中,主要介绍一下通过 AspectJ 框架更好的理解和使用AOP的5大通知注解。
在 AspectJ 实现 AOP 时,要引入 AOP 的约束。配置文件中使用的 AOP 约束中的标签,均是 AspectJ 框架使用的,而非 Spring 框架本身在实现 AOP 时使用的。AspectJ 对于 AOP 的实现有注解和配置文件两种方式,常用是注解方式。
1.1 大致步骤
使用apsectj框架的注解,实现前置通知,步骤如下:
1.新建Maven项目
2.修改pom.xml,加入依赖
spring-context依赖、spring-aspects依赖、junit
3.创建业务接口和实现类
4.创建一个切面类(普通类)
1) 在类的上面加入@Aspect
2) 在类中定义方法,方法表示切面的功能。在方法的上面加入AspectJ框架中的通知注解
例如:@Before(value="切入点表达式")
5.创建spring配置文件
1) 声明目标对象
2) 声明切面类对象
3) 声明自动代理生成器
6.创建测试类,测试目标方法执行时,增加切面的功能
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency>
1.2.2 spring-aspects依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.5.RELEASE</version> </dependency>
1.2.3 单元测试依赖
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency>
1.3 在Spring配置文件中声明AspectJ的自动代理生成器
<?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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 声明自动代理生成器,目的是创建目标对象的代理 --> <aop:aspectj-autoproxy /> </beans>
2.AOP中的5大通知注解
/** * 前置通知方法的定义 * 1) 方法是public * 2) 返回值是void * 3) 方法名称自定义 * 4) 可以有参数,也可以无参数。如果有,参数是JoinPoint * * @Before: 前置通知 * 属性: value 切入点表达式,表示切面的执行位置。在这个方法执行时,会同时执行切面的功能 * 位置: 放在方法的上面 * 特点: 1) 执行时间在目标方法之前先执行 * 2) 不会影响目标方法的执行 * 3) 不会修改目标方法的执行结果 * 切面类中的通知方法,可以有参数。必须是JoinPoint * JoinPoint: 表示正在执行的业务方法,相当于反射中的Method * 使用要求: 必须是参数列表的第一个 * 作用: 获取方法执行时的信息,例如方法名称、方法的参数集合 */
package com.bjpowernode.service; /** * */ public interface SomeService { void doSome(String name,Integer age); void doOther(); }
package com.bjpowernode.service.impl; import com.bjpowernode.service.SomeService; public class SomeServiceImpl implements SomeService { @Override public void doSome(String name, Integer age) { System.out.println("业务方法doSome(),创建商品的订单"); } @Override public void doOther() { System.out.println("业务方法doOther()"); } }
package com.bjpowernode.handle; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import java.util.Date; /** * @Aspect: 切面类的注解 * 位置: 放在某个类的上面 * 作用: 表示当前类是切面类 */ @Aspect public class MyAspect { @Before(value = "execution(public void com.bjpowernode.service.impl.SomeServiceImpl.do*(..))") public void myBefore(JoinPoint joinPoint) { //获取方法的完整定义 System.out.println("前置通知,获取目标方法的定义: " + joinPoint.getSignature()); //获取方法的名称 System.out.println("前置通知,获取目标方法的名称: " + joinPoint.getSignature().getName()); //获取方法执行时的参数 Object[] args=joinPoint.getArgs(); for(Object obj : args) { System.out.println("前置通知,获取目标方法的参数: " + obj); } String methodName=joinPoint.getSignature().getName(); if(methodName.equals("doSome")) { //切面的代码 System.out.println("doSome输出日志=========前置通知,切面的功能,在目标方法之前先执行: " + new Date()); }else if(methodName.equals("doOther")) { //切面的代码 System.out.println("doOther输出日志========前置通知,作为方法名称、参数的记录"); } } }
<!-- 声明目标对象 --> <bean id="someService" class="com.bjpowernode.service.impl.SomeServiceImpl"/> <!-- 声明切面类对象 --> <bean id="myAspect" class="com.bjpowernode.handle.MyAspect"/> <!-- 声明自动代理生成器,目的是创建目标对象的代理 --> <aop:aspectj-autoproxy />
@Test public void test01() { String config="applicationContext.xml"; ApplicationContext ctx=new ClassPathXmlApplicationContext(config); SomeService service= (SomeService) ctx.getBean("someService"); System.out.println("service === " + service.getClass().getName()); service.doSome("张起灵",20); //service.doOther(); }
2.2 @AfterReturning:后置通知
/** * 后置通知方法的定义 * 1) 方法是public * 2) 返回值是void * 3) 方法名称自定义 * 4) 方法有参数,推荐使用Object * * @AfterReturning: 后置通知 * 属性: value 切入点表达式 * returning 自定义的变量,表示目标方法的返回值的返回值。 * 自定义变量的名称必须和通知方法的形参名一样 * 位置: 放在方法的上面 * 特点: 1) 在目标方法之后执行 * 2) 能获取到目标方法的执行结果 * 3) 不会影响目标方法的执行 * 方法的参数Object res,表示目标方法的返回值,使用res接收doOther的调用结果 */
package com.bjpowernode.service; /** * */ public interface SomeService { String doOther(String name,Integer age); }
package com.bjpowernode.service.impl; import com.bjpowernode.service.SomeService; public class SomeServiceImpl implements SomeService { @Override public String doOther(String name, Integer age) { System.out.println("执行业务方法doOther(),处理库存"); return "abcd"; } }
package com.bjpowernode.handle; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; /** * @Aspect: 切面类的注解 * 位置: 放在某个类的上面 * 作用: 表示当前类是切面类 */ @Aspect public class MyAspect { @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))", returning = "res") public void myAfterReturning(Object res) { System.out.println("后置通知,在目标方法之后执行的,可以拿到执行结果" + res); } }
<!-- 声明目标对象 --> <bean id="someService" class="com.bjpowernode.service.impl.SomeServiceImpl"/> <!-- 声明切面类对象 --> <bean id="myAspect" class="com.bjpowernode.handle.MyAspect"/> <!-- 声明自动代理生成器,目的是创建目标对象的代理 --> <aop:aspectj-autoproxy />
@Test public void test01() { String config="applicationContext.xml"; ApplicationContext ctx=new ClassPathXmlApplicationContext(config); SomeService service= (SomeService) ctx.getBean("someService"); System.out.println("service === " + service.getClass().getName()); service.doOther("张三",25); }
2.3 @Around:环绕通知
/** * 环绕通知方法的定义 * 1) 方法是public * 2) 必须有返回值,推荐使用Object类型 * 3) 方法名称自定义 * 4) 必须有ProceedingJoinPoint参数 * * @Around: 环绕通知 * 属性: value 切入点表达式 * 位置: 在方法定义的上面 * 测试方法中,调用目标方法doFirst(String name)去执行, * 实际上目标方法doFirst(String name)并未执行,而是执行了myAround(ProceedingJoinPoint proceedingJoinPoint) * 特点: 1) 在目标方法的前和后都能增强功能 * 2) 控制目标方法是否执行 * 3) 修改目标方法的执行结果 */
package com.bjpowernode.service; /** * */ public interface SomeService { String doFirst(String name); }
package com.bjpowernode.service.impl; import com.bjpowernode.service.SomeService; public class SomeServiceImpl implements SomeService { @Override public String doFirst(String name) { System.out.println("执行业务方法doFirst(),处理库存"); return "doFirst"; } }
package com.bjpowernode.handle; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import java.util.Date; /** * @Aspect: 切面类的注解 * 位置: 放在某个类的上面 * 作用: 表示当前类是切面类 */ @Aspect public class MyAspect { @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))") public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //获取方法执行时的参数值 String name=""; Object[] args=proceedingJoinPoint.getArgs(); if(args!=null && args.length>0) { Object arg=args[0]; if(arg!=null) { name=(String)arg; } } System.out.println("执行了环绕通知,在目标方法之前,输出日志时间=== " + new Date()); Object methodReturn=null; if(name.equals("李四")) { //相当于执行目标方法doFirst(String name) methodReturn = proceedingJoinPoint.proceed(); } System.out.println("执行了环绕通知,在目标方法之后,增加了事务功能"); return methodReturn; } }
<!-- 声明目标对象 --> <bean id="someService" class="com.bjpowernode.service.impl.SomeServiceImpl"/> <!-- 声明切面类对象 --> <bean id="myAspect" class="com.bjpowernode.handle.MyAspect"/> <!-- 声明自动代理生成器,目的是创建目标对象的代理 --> <aop:aspectj-autoproxy />
@Test public void test01() { String config="applicationContext.xml"; ApplicationContext ctx=new ClassPathXmlApplicationContext(config); SomeService service= (SomeService) ctx.getBean("someService"); System.out.println("service === " + service.getClass().getName()); String ret=service.doFirst("李四"); System.out.println("ret调用目标方法的结果 === " + ret); }
2.4 @AfterThrowing:异常通知
/** * 异常通知方法的定义 * 1) 方法是public * 2) 返回值是void * 3) 方法名称自定义 * 4) 方法有参数Exception * * @AfterThrowing: 异常通知 * 属性: value 切入点表达式 * throwing 自定义变量,表示目标方法抛出的异常。变量名必须和通知方法的形参名一样 * 位置: 在方法上面 * 特点: 1) 在目标方法抛出异常之后执行的,若没有异常 则不执行 * 2) 能获取到目标方法的异常信息 * 3) 不是异常处理程序,可以得到发生异常的通知,可以发送邮件、短信通知开发人员 * 可以看作是目标方法的监控程序 * 可以看作以下语句块: * try { * SomeServiceImpl.doSecond(..) * }catch(Exception ex) { * myAfterThrowing(Exception ex) * } */
package com.bjpowernode.service; /** * */ public interface SomeService { void doSecond(String name); }
package com.bjpowernode.service.impl; import com.bjpowernode.service.SomeService; public class SomeServiceImpl implements SomeService { @Override public void doSecond(String name) { System.out.println("执行业务方法doSecond(),处理库存" + (10/0)); } }
package com.bjpowernode.handle; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; /** * @Aspect: 切面类的注解 * 位置: 放在某个类的上面 * 作用: 表示当前类是切面类 */ @Aspect public class MyAspect { @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "ex") public void myAfterThrowing(Exception ex) { System.out.println("异常通知,在目标方法抛出异常时执行的,异常原因是:" + ex.getMessage()); } }
<!-- 声明目标对象 --> <bean id="someService" class="com.bjpowernode.service.impl.SomeServiceImpl"/> <!-- 声明切面类对象 --> <bean id="myAspect" class="com.bjpowernode.handle.MyAspect"/> <!-- 声明自动代理生成器,目的是创建目标对象的代理 --> <aop:aspectj-autoproxy />
2.5 @After:最终通知
/** * 最终通知方法的定义 * 1) 方法是public * 2) 返回值是void * 3) 方法名称自定义 * 4) 方法无参数 * * @After: 最终通知 * 属性: value 切入点表达式 * 位置: 在方法上面 * 特点: 1) 在目标方法之后执行的 * 2) 总是会被执行 * 3) 可以用来做程序最后的收尾工作,例如清除临时数据、变量,清理内存 * 可以看作如下语句块: * try { * SomeServiceImpl.doThird(..) * }finally { * myAfter() * } */
package com.bjpowernode.service; /** * */ public interface SomeService { void doThird(); }
package com.bjpowernode.service.impl; import com.bjpowernode.service.SomeService; public class SomeServiceImpl implements SomeService { @Override public void doThird() { System.out.println("执行业务方法doThird()"); } }
package com.bjpowernode.handle; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; /** * @Aspect: 切面类的注解 * 位置: 放在某个类的上面 * 作用: 表示当前类是切面类 */ @Aspect public class MyAspect { @After(value = "execution(* *..SomeServiceImpl.doThird(..))") public void myAfter() { System.out.println("最终通知,总是会被执行的"); } }
<!-- 声明目标对象 --> <bean id="someService" class="com.bjpowernode.service.impl.SomeServiceImpl"/> <!-- 声明切面类对象 --> <bean id="myAspect" class="com.bjpowernode.handle.MyAspect"/> <!-- 声明自动代理生成器,目的是创建目标对象的代理 --> <aop:aspectj-autoproxy />
@Test public void test01() { String config="applicationContext.xml"; ApplicationContext ctx=new ClassPathXmlApplicationContext(config); SomeService service= (SomeService) ctx.getBean("someService"); System.out.println("service === " + service.getClass().getName()); service.doThird(); }
3.@Pointcut 定义切入点
当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。AspectJ 提供了 @Pointcut 注解,用于定义 execution 切入点表达式。
其用法是,将 @Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用 @Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。
下面的代码只给出切面类:👇👇👇
package com.bjpowernode.handle; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; /** * @Aspect: 切面类的注解 * 位置: 放在某个类的上面 * 作用: 表示当前类是切面类 */ @Aspect public class MyAspect { @Before(value = "mypt()") public void myBefore() { System.out.println("前置通知,在目标方法之前先执行的"); } @After(value = "mypt()") public void myAfter() { System.out.println("最终通知,总是会被执行的"); } /** * @Pointcut: 定义和管理切入点,不是通知注解 * 属性: value 切入点表达式 * 位置: 在一个自定义方法的上面,此方法被看作是切入点表达式的别名 * 在其他通知注解中,可以使用此方法名称,表示使用这个切入点表达式 */ @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))") private void mypt() { //无需代码 } }