概念
- Spring AOP - Aspect Oriented Programming 面向切面编程
- AOP的做法是将通用、与业务无关的功能抽象封装为切面类
- 切面可配置在目标方法的执行前、后运行,真正做到即插即用
- 可以在不修改源码的情况下对程序进行扩展
AOP配置过程——基于XML配置
0. 添加依赖创建配置文件
添加依赖-aspectjweaver
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.25.RELEASE</version>
</dependency>
<!--Spring AOP的底层依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
SpringAOP和AspectJ的关系
- Eclipse AspectJ,一种基于Java平台的面向切面编程的语言
- SpringAOP使用AspectJWeaver实现类与方法匹配
- SpringAOP利用代理模式实现对象运行时功能扩展
创建配置文件: 新增context和aop的xmlns
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
</beans>
具体的schema可以在spring官网找到,步骤如下:
几个关键概念
1. 实现切面类/方法
- Aspect,切面,具体的可插拔组件功能类,通常一个切面只实现一个通用功能
- Target Class/Method:目标类、目标方法,指真正要执行与业务相关的方法
JoinPoint
- 连接点,切面运行过程中包含了目标类/方法元数据的对象
2. 配置Aspect Bean
3. 定义PointCut
- PointCut,切入点,使用execution表达式说明切面要作用在系统的哪些类上
- 使用execution表达式描述切面的作用范围
4. 配置Advice通知
- Advice,通知,说明具体切面的执行时机,Spring包含了五种不同类型的通知
Around Advice环绕通知
- 实现切面类/方法
ProceedingJoinPoint是JoinPoint的升级版,在原有功能外,还可以控制目标方法是否执行,需要强制返回目标方法返回值
package com.EveX.spring.aop.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import java.text.SimpleDateFormat;
import java.util.Date;
/*检查程序运行时间的切面类*/
public class MethodChecker {
public Object check(ProceedingJoinPoint pjp) throws Throwable {
try {
long stime = new Date().getTime(); //开始执行时间
Object ret = pjp.proceed();// 执行目标方法
long etime = new Date().getTime(); //结束执行时间
long duration = etime - stime; //程序执行时长
if(duration > 1000) {
String className = pjp.getTarget().getClass().getName(); //获取目标对象的类名
String methodName = pjp.getSignature().getName(); //获取目标方法名
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss SS");
String now = dateFormat.format(new Date());
System.out.println("=====>" + now + ":" + className + "." + methodName + "(" + duration + "ms)<=====");
}
return ret;
} catch (Throwable e) {
System.out.println("Exception message:" + e.getMessage());
throw e;
}
}
}
- 配置Aspect Bean
<bean id="methodChecker" class="com.EveX.spring.aop.aspect.MethodChecker"></bean>
- 定义PointCut
- 配置Advice通知
<aop:config>
<!--定义切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.EveX..*.*(..))"/>
<!--定义切面-->
<aop:aspect ref="methodChecker">
<!--配置around advice通知-->
<aop:around method="check" pointcut-ref="pointcut"></aop:around>
</aop:aspect>
</aop:config>
AOP配置过程——基于注解配置
- 开启context包扫描初始化Ioc容器+启动Spring AOP注解模式
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--初始化IoC容器-->
<context:component-scan base-package="com.EveX"></context:component-scan>
<!--启动Spring AOP注解模式-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
- 在切面类中添加注解
package com.EveX.spring.aop.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
/*检查程序运行时间的切面类*/
@Component
@Aspect // 标记当前类是一个切面类
public class MethodChecker {
/*环绕通知,参数为execution表达式*/
@Around("execution(* com.EveX..*.*(..))")
public Object check(ProceedingJoinPoint pjp) throws Throwable {
try {
long stime = new Date().getTime(); //开始执行时间
Object ret = pjp.proceed();// 执行目标方法
long etime = new Date().getTime(); //结束执行时间
long duration = etime - stime; //程序执行时长
if(duration > 1000) {
String className = pjp.getTarget().getClass().getName(); //获取目标对象的类名
String methodName = pjp.getSignature().getName(); //获取目标方法名
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss SS");
String now = dateFormat.format(new Date());
System.out.println("=====>" + now + ":" + className + "." + methodName + "(" + duration + "ms)<=====");
}
return ret;
} catch (Throwable e) {
System.out.println("Exception message:" + e.getMessage());
throw e;
}
}
}
Spring AOP实现原理
Spring 基于代理模式实现功能动态扩展,包含两种形式:
- 目标类拥有接口,通过JDK动态代理实现功能扩展
- 目标类没有接口,通过CGLib组件通过继承的方式实现功能扩展
CGLib实现代理类:
- CGLib是运行时字节码增强技术
- AOP会在运行时生成目标继承类字节码的方式进行行为扩展