文章目录:
1.3 在Spring配置文件中声明AspectJ的自动代理生成器
1.引子
承接了上一篇文章:Spring——AOP基本概念的理解_spring aop概念-CSDN博客
这篇文章中,主要介绍一下通过 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.创建测试类,测试目标方法执行时,增加切面的功能
1.2 pom.xml文件中加入maven依赖
1.2.1 spring依赖
<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大通知注解
2.1 @Before:前置通知
/** * 前置通知方法的定义 * 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() {
//无需代码
}
}