AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
spring aop与aspectJ的关系 1. Eclipse AspectJ,一种基于Java平台的面向切面编程语言 2. Spring Aop使用AspectJWeaver实现类与方法的匹配 3. Spring Aop利用代理模式实现对象运行时功能拓展 关键概念 1. Aspect 切面,去提的可插拔组件功能类,通常一个切面只实现一个通用功能 2. Target Class/Method 目标类、目标方法,指真正要执行与业务相关的方法 3. PointCut 切入点,使用execution表达式说明切面要作用在系统的那些类上 4. JoinPoint 连接点,切面运行过程中是包含了目标类/方法元数据的对象 5. Advice 通知,说明具体的切面的执行时机,Spring包含了五种不同类型通知 aop配置过程 1. 依赖AspectJ
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.13</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
2. 实现切面类/方法
package pers.thc.aop.aspect;
import org.aspectj.lang.JoinPoint;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Aspect {
public void printExecutionTime(JoinPoint joinPoint){
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
String dateStr = format.format(date);
String className = joinPoint.getTarget().getClass().getName();//获取目标类的名称
String methodName = joinPoint.getSignature().getName();//获取目标方法名称
System.out.println("--->执行时间:"+dateStr+" 执行类:"+className+" 执行方法:"+methodName);
Object[] args = joinPoint.getArgs();
System.out.println("--->参数个数:" + args.length);
for (Object arg : args) {
System.out.println("--->参数:" + arg);
}
}
public void doAfterReturning(JoinPoint joinPoint,Object ret){
System.out.println("<---返回后通知,返回值:"+ret);
}
public void doAfterThrowing(JoinPoint joinPoint, Throwable throwable) {
System.out.println("<---异常通知:"+throwable);
}
public void doAfter(JoinPoint joinPoint) {
System.out.println("<---触发后置通知");
}
}
3. 配置Aspect Bean
<bean id="aspect" class="pers.thc.aop.aspect.Aspect"/>
4. 定义PointCut
<aop:config>
<!--PointCut切点,用execution来描述切面作用范围-->
<!--<aop:pointcut id="pointcut" expression="execution(public * pers.thc..*.*(..))"/>-->
<aop:pointcut id="pointcut" expression="execution(* pers.thc..*Service.*(..))"/>
<!--public可去-->
</aop:config>
5. 配置Advice
<aop:config>
<!--PointCut切点,用execution来描述切面作用范围-->
<!--<aop:pointcut id="pointcut" expression="execution(public * pers.thc..*.*(..))"/>-->
<aop:pointcut id="pointcut" expression="execution(* pers.thc..*Service.*(..))"/>
<!--public可去-->
<!--定义切面-->
<aop:aspect ref="aspect">
<!--before通知(Advice),代表目标方法运行前执行切面类的切面方法-->
<aop:before method="printExecutionTime" pointcut-ref="pointcut"/>
<aop:after method="doAfter" pointcut-ref="pointcut"/>
<aop:after-returning method="doAfterReturning" returning="ret" pointcut-ref="pointcut"/>
<aop:after-throwing method="doAfterThrowing" throwing="throwable" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
JoinPoint核心方法 1. Objet getTarget():获取IoC容器内的目标对象 2. Signature getSignature():获取目标方法 3. Object[] getArgs():获取目标方法参数 PointCut切点表达式 1. 例子: public void pers.thc.aop.service.UserService.generateRandomPassword(形参1,形参2,...) 2. execution(public * pers.thc .. * . * ( .. )) 3. * -通配符 .. -包通配符 (..) -参数通配符 五种通知类型 1. Before Advice:前置通知,目标方法运行前执行 2. After Returning Advice:返回后通知,目标方法返回数据后执行 3. After Throwing Advice:异常通知,目标方法抛出异常后执行 4. After Advice:后置通知,目标方法运行后执行 5. Around Advice:最强大通知,自定义通知执行事迹,可决定目标方法是否运行 特殊的“通知”--引介增强 1. 引介增强(IntroductionInterceptor)是对类的增强,而非方法 2. 引介增强允许再运行时为目标类增加新属性或方法 3. 引介增强允许再运行时改变类的行为,让类随运行环境动态变更
Spring AOP 环绕通知实现步骤 1. 引入AspectJ依赖 2. 实现切面类:切面类引入ProceedingJoinPoint类对象,调用proceed()方法执行目标方法并获取Object类返回对象,return 返回对象,利用try-catch块环绕,catch块中将异常上抛
package pers.thc.aop.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MethodChecker {
//ProceedingJoinPoint是JoinPoint的升级版,在原有功能外,还可以控制目标方法是否执行
public Object check(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
try {
long startTime = new Date().getTime();
Object ret = proceedingJoinPoint.proceed();//执行目标方法
long endTime = new Date().getTime();
long duration = endTime - startTime;
if (duration >= 1000) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String methodName = proceedingJoinPoint.getSignature().getName();
String className = proceedingJoinPoint.getTarget().getClass().getName();
String now = format.format(new Date());
System.out.println("========"+now+":"+className+"."+methodName+"("+duration+" ms)");
}
return ret;
} catch (Throwable throwable) {
System.out.println("Exception message:" + throwable.getMessage());
throw throwable;
}
}
}
3. 配置Aspect Bean 4. 定义pointcut 5. 配置Advice:选择环绕通知类型,例子:<aop:around method="check" pointcut-ref="pointcut"/>
<bean id="methodChecker" class="pers.thc.aop.aspect.MethodChecker"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* pers.thc..*.*(..))"/>
<aop:aspect ref="methodChecker">
<!--环绕通知-->
<aop:around method="check" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
Spring Aop注解开发 1. 引入spring-context、aspectjweaver依赖 2. 在spring配置文件中添加组件扫描配置与aspectj自动代理配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="pers.thc"/>
<!--启用Spring Aop注解模式-->
<aop:aspectj-autoproxy/>
</beans>
3. 在切面类上添加@Component与@Aspect注解 4. 在切面类方法上添加advice注解,并在其中填写切点表达式(如@Around(execution(......)))
package pers.thc.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 {
@Around("execution(public * pers.thc..*Service.*(..))")
public Object check(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
try {
long startTime = new Date().getTime();
Object ret = proceedingJoinPoint.proceed();
long endTime=new Date().getTime();
long duration=endTime-startTime;
if(duration>1000){
String className=proceedingJoinPoint.getTarget().getClass().getName();
String methodName=proceedingJoinPoint.getSignature().getName();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String now = format.format(new Date());
System.out.println("==========" + now + ":" + className + "." + methodName);
}
return ret;
} catch (Throwable throwable) {
System.out.println("Exception message:" + throwable.getMessage());
throw throwable;
}
}
}