文章目录
aspectj实现AOP
-
在Spring中进行AOP的操作,使用的是AspectJ来具体实现。AspectJ是一个面向切面的编程框架,它扩展了Java语言。AspectJ定义了AOP语法,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
-
AspectJ不是Spring的内部集成,而是和Spring一起配合使用来实现AOP操作。
-
Spring2.0以后才增加了对AspectJ的支持。
-
Spring中使用AspectJ实现AOP操作,需要导入额外的aspectj的jar包。
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.20</version>
</dependency>
说明:
spring出现Caused by: java.lang.NoClassDefFoundError:
org/aspectj/weaver/BCException的错误时,可能是因为Spring缺少aspectjweaver.jar包
aspectjrt.jar包是在使用jdk时所需要的jar文件,经测试,支持jdk1.7,
一般使用spring语句调入时会用到
aopalliance-1.0.jar的作用
这个包是AOP联盟的API包,里面包含了针对面向切面的接口。
通常Spring等其它具备动态织入功能的框架依赖此包。
-
使用AspectJ实现AOP有二种方式:
—基于AspectJ的XML配置
—基于AspectJ的注解
基于AspectJ的XML配置
package com.sin.pojo;
/**
* @author sin
* @date 2022/10/27
* @apiNote
*/
public class User1 {
private String userName;
public User1() {
}
public User1(String userName) {
super();
this.userName = userName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public void login() {
System.out.println("【" + this.userName + "】" + "用户正在登录。");
}
}
package com.sin.service.impl;
import org.aspectj.lang.JoinPoint;
/**
* @author sin
* @date 2022/10/27
* @apiNote
*/
public class MySINAspect {
//此方法作为前置通知
public void before(JoinPoint joinPoint){
//获取被调用的类名
String targetClassName=joinPoint.getTarget().getClass().getName();
//获取被调用的方法名
String targetMethodName=joinPoint.getSignature().getName();
//日志格式字符串
System.out.println("【前置通知】"+targetClassName+"类的"+targetMethodName+"方法开始执行。");
}
}
配置文件中导入aop schema约束
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://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">
基于XML的声明式AspectJ
- 配置切面
配置切面使用的是aop:aspect元素,该元素会将一个定义好事务Spring Bean转换成切面Bean,所以要在配置文件中先定义一个普通的Spring Bean。
属性名称 | 描述 |
---|---|
id | 用于定义该切面的唯一标识 |
ref | 用于引用普通的Spring Bean |
- 配置切入点
切入点是通过aop:pointcut元素来定义的。当aop:pointcut元素作为aop:config元素的子元素定义时,标识切入点是全局切入点。它可以被多个切面所共享;当aop:pointcut元素作为aop:aspect元素的子元素定义时,表示该切入点值对当前切面有效。aop:pointcut元素的属性及其描述如下:
属性名称 | 描述 |
---|---|
id | 用于指定切入点的唯一标识 |
ref | 用于指定切入点关联的切入点表达式 |
- 配置通知
分别使用aop:aspect的子元素配置5中常用通知,
属性名称 | 描述 |
---|---|
pointcut | 该属性用于指定一个切入点,Spring将在匹配该表达式的连接点时织入该通知 |
pointcut-ref | 该属性用于指定一个已经存在的切入点名称,如配置该代码中myPointCut。通常pointcut和pointcut-ref两个属性只需要使用其中之一 |
method | 该属性指定一个方法名,指定将切面Bean中的该方法转换为增强 |
throwing | 该属性只对 元素有效,它用与指定一个形象名,异常通知方法可以通过该形参访问目标方法所抛出的异常 |
returning | 该属性只对元素有效,它用于指定一个形参名,后置通知方法可以该形参访问目标方法的返回值 |
配置切面和业务bean
<!-- 配置切面bean -->
<bean id="user1" class="com.sin.pojo.User1"></bean>
<!-- 配置业务bean -->
<bean id="mySINAspect" class="com.sin.service.impl.MySINAspect"></bean>
aop配置
<!-- 使用xml配置方式完成aop的相关配置 -->
<aop:config>
<!-- 首先配置切入点(针对目标对象中的目标方法定义相关的切入规则) -->
<aop:pointcut id="pointCut" expression="execution(* com.sin.pojo.*.*(..))"/>
<!-- 再配置切面 -->
<aop:aspect ref="mySINAspect">
<!-- 针对切入点进行相关通知的配置 -->
<aop:before method="before" pointcut-ref="pointCut"/>
</aop:aspect>
</aop:config>
提示! Spring AOP中expression表达式的语法格式:
//匹配指定包中所有类中的所有方法
execution(* com.sin.service.*.*(..))
//匹配指定包及子包中所有类中的所有方法
execution(* com.sin.service..*(..))")
//匹配UserServiceImpl类中的所有方法
execution(* com.sin.service.UserServiceImpl.*(..))")
//匹配UserServiceImpl类中的所有公共的方法
execution(public * com.sin.service.UserServiceImpl.*(..))")
//匹配UserServiceImpl类中的所有公共方法并且返回值为int类型
execution(public int com.sin.service.UserServiceImpl.*(..))")
//匹配UserServiceImpl类中第一个参数为int类型的所有公共的方法
execution(public * com.sin.service.UserServiceImpl.*(int , ..))")
//多个表达式的关联使用
execution (* com.sin.servicei.UserServiceImple.save*(..)) || execution (* com.sin.servicei.UserServiceImple.update*(..))
测试
@Test
public void test1(){
//加载SpringAop.xml配置
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
//获取配置中的user实例
User1 user=(User1) context.getBean("user1");
user.setUserName("张三");
user.login();
}
测试结果:
基于AspectJ的注解配置
- 目标类接口
package com.sin.service;
/**
* @author sin
* @date 2022/10/27
* @apiNote
*/
public interface SomeService {
public String doSomeSimple(String str, Integer num);
public String doSomeAround(String str, Integer num);
public Integer doSomeAfterThrowing(Integer num0, Integer num1);
}
- 目标类实现类
package com.sin.service.impl;
import com.sin.service.SomeService;
/**
* @author sin
* @date 2022/10/27
* @apiNote
*/
public class SomeServiceImpl implements SomeService {
@Override
public String doSomeSimple(String str, Integer num) {
System.out.println("===目标方法doSomeSimple(str=" + str + ", num=" + num + ")===");
return str + num;
}
@Override
public String doSomeAround(String str, Integer num) {
System.out.println("===目标方法doSomeAround(str=" + str + ", num=" + num + ")===");
return str + num;
}
@Override
public Integer doSomeAfterThrowing(Integer num0, Integer num1) {
System.out.println("===目标方法doSomeAfterThrowing(num0="
+ num0 + ", num1=" + num1 + ")===");
return num0 / num1;
}
}
package com.sin.service.impl;
public class CglibServiceImpl {
public void doSome(){
System.out.println("------CglibServiceImpl.doSome()------");
}
}
- MyAspect.java切面类
package com.sin.service.impl;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Arrays;
import java.util.Date;
/**
* @Aspect:aspectJ框架中的注解
* 表示:当前类是切面类(用来给业务代码增强功能的类,这里写有切面的功能代码)
* 位置:类上
*/
@Aspect
public class MyAspect {
/**
* @Before:前置增强
* 属性:
* value:切入点表达式
* 要求:
* 1、公共方法 public
* 2、方法没有返回值
* 3、方法名称自定义
* 4、可以有参数也可以没有参数(如果有参数,必须使用给定的参数类型:JoinPoint)
*/
@Before(value = "execution(public String com.sin.service.impl.SomeServiceImpl.doSomeSimple(String,Integer))")
public void myBefore(){
// 书写切面要执行的功能增强代码
System.out.println("切面前置增强:输出执行开始时间:" + new Date());
}
/**
* 指定参数中的给定参数:JoinPoint(业务方法)
* 作用:可在增强里获得业务方法的信息
* 使用须知:JoinPoint是由框架赋予的,必须放在参数列表第一位
*/
@Before(value = "execution(String do*Simple(..))")
public void myBefore_02(JoinPoint point){
// 书写切面要执行的功能增强代码
// 获取业务方法完整定义
System.out.println("业务方法定义:" + point.getSignature());
System.out.println("业务方法名称:" + point.getSignature().getName());
System.out.println("业务方法参数列表:");
Arrays.stream(point.getArgs()).forEach(System.out::println);
System.out.println("切面前置增强02:输出执行开始时间:" + new Date());
}
/**
* @AfterReturning:后置增强
* 属性:
* 1、value:切入点表达式
* 2、returning:自定义变量表示方法返回值(变量名必须和增强方法的形参名一致)
* 要求:
* 1、公共方法 public
* 2、方法没有返回值
* 3、方法名称自定义
* 4、有参数(推荐Object)
*/
@AfterReturning(value = "execution(* do*Simple(..))", returning = "res555")
public void myAfterReturning(Object res555){
// 书写切面要执行的功能增强代码
System.out.println("切面后置增强:业务方法执行后返回值为:" + res555);
}
/**
* @Around:环绕增强
* 属性:
* 1、value:切入点表达式
* 要求:
* 1、公共方法 public
* 2、方法必须有返回值(推荐Object)
* 3、方法名称自定义
* 4、有固定参数(ProceedingJoinPoint extends JoinPoint)
*
* 环绕增强等同于JDK动态代理的InvocationHandler接口
* 参数ProceedingJoinPoint等同于Method
*/
@Around(value = "execution(* do*Around(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
// 定义返回值
Object result = null;
// 1、环绕增强之前置增强
System.out.println("环绕增强之前置增强:业务方法执行前记录的时间:" + new Date());
System.out.println("环绕增强之前置增强:业务方法入参:[");
Arrays.stream(pjp.getArgs()).forEach(System.out::println);
System.out.println("]");
// 2、目标方法调用
result = pjp.proceed(); // 相当于method。invoke()
// 3、环绕增强之后置增强
System.out.println("环绕增强之后置增强:业务方法执行结束提交事务");
// 返回执行结果
return result;
}
/**
* @AfterThrowing:异常增强
* 属性:
* 1、value:切入点表达式
* 2、throwing:自定义变量表示方法抛出的异常(变量名必须和增强方法的形参名一致)
* 要求:
* 1、公共方法 public
* 2、方法没有返回值
* 3、方法名称自定义
* 4、有一个参数是Exception,如果还有就是JoinPoint
* 特点:
* 1、在目标方法抛出异常时执行
* 2、可作异常监控
*/
@AfterThrowing(value = "execution(* do*AfterThrowing(..))", throwing = "exception666")
public void myAfterThrowing(Exception exception666){
// 书写切面要执行的功能增强代码
System.out.println("切面异常增强:业务方法运行时抛出异常:" + exception666.getMessage());
}
/**
* @After:最终增强
* 属性:
* 1、value:切入点表达式
* 2、throwing:自定义变量表示方法抛出的异常(变量名必须和增强方法的形参名一致)
* 要求:
* 1、公共方法 public
* 2、方法没有返回值
* 3、方法名称自定义
* 4、可以有参数也可以没有参数(如果有参数,必须使用给定的参数类型:JoinPoint)
* 特点:
* 1、总是执行
* 2、可作异常监控
*/
@After(value = "execution(* do*After*(..))")
public void myAfter(){
// 书写切面要执行的功能增强代码
System.out.println("切面最终增强:总是会被执行");
// 一般做资源清除工作的
}
/**
* @Pointcut:定义和管理切入点(复用:多处使用同一个切入点时定义一个公用的切入点)
* 属性:
* 1、value:切入点表达式
* 特点:
* 当使用@Pointcut定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名
* 其它的通知中, value属性就可以使用这个方法名称,代替切入点表达式
*/
@Pointcut(value = "execution(* do*(..))")
private void myPointcutComm(){
//无需代码
}
// 使用@Pointcut定义的切入点
@After(value = "myPointcutComm()")
public void myAfterCommon(){
// 书写切面要执行的功能增强代码
System.out.println("***通用的切面最终增强:总是会被执行***");
}
}
applicationContext.xml
<!--对象交给Spring容器,由Spring容器统一创建、管理-->
<!--目标对象-->
<bean id="someService" class="com.sin.service.impl.SomeServiceImpl" />
<bean id="cglibService" class="com.sin.service.impl.CglibServiceImpl" />
<!--切面对象-->
<bean id="myAspect" class="com.sin.service.impl.MyAspect" />
<!--声明自动代理生成器:使用aspectJ框架内部功能创建目标对象的代理对象
创建代理对象是在内存中实现的,修改目标对象在内存中的结构来创建代理对象
所以目标对象就是被修改后的代理对象
aspectj-autoproxy:会把Spring容器中所有目标对象,一次性都生成代理对象
默认没有接口使用cglib,有接口使用jdk
-->
<aop:aspectj-autoproxy />
<!--
如果期望目标类有接口还依然使用cglib动态代理
proxy-target-class="true":告诉框架死活给我用cglib代理
-->
<!--<aop:aspectj-autoproxy proxy-target-class="true" />-->
测试类
@Test
public void test(){
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("==============没有接口时是CGLIB动态代理=====================");
CglibServiceImpl cglib =
(CglibServiceImpl) applicationContext.getBean("cglibService");
System.out.println("没有接口时Spring框架自动选择の动态代理对象(Proxy=JDK/*CGLIB*=CGLIB)==" + cglib.getClass().getName());
SomeService proxy = (SomeService) applicationContext.getBean("someService");
System.out.println("存在接口时Spring框架自动选择の动态代理对象(Proxy=JDK/*CGLIB*=CGLIB)==" + proxy.getClass().getName());
System.out.println("\n====================前置+后置===========================");
System.out.println("proxy.doSomeSimple()最终执行结果==" + proxy.doSomeSimple("哦吼吼", 20));
System.out.println("\n=====================环---绕===========================");
System.out.println("proxy.doSomeAround()最终执行结果==" + proxy.doSomeAround("咚恰恰", 666));
System.out.println("\n=====================异常+最终===========================");
System.out.println("proxy.doSomeAfterThrowing()最终执行结果==" + proxy.doSomeAfterThrowing(12, 1));
}
测试结果
第一步 @Around 环绕前执行
第二步 @Before 目标方法之前执行
第三步 执行目标方法
第四步:
1、假如执行目标出现异常 执行@AfterThrowing
2、假如执行目标正常,而且有返回值 执行@AfterReturning
第五步 @After 目标方法之后执行(始终执行),相当于finally部分
第六步 @Around 环绕后执行
重要提示!
Spring5.2.2 GA版中针对通知执行的顺序问题,在XML配置中和Spring4系列是一致的,但是在使用注解的时候,就是上面的这个顺序过程。