AOP概念
AOP(Aspect-Oriented Programming,面向切面编程),简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或业务封装起来,再把封装功能整合到业务中。
AOP的核心思想就是“将应用程序中的业务逻辑同对其提供支持的通用服务进行分离"
好处:
1,便于减少系统的重复代码
2,降低模块间的耦合度
3,有利于未来的可操作性和可维护性。
例如:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
aop:切面 = 切点 + 增强(通知)
@Aspect:作用是把当前类标识为一个切面供容器读取 @Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。 @Around:环绕增强,相当于MethodInterceptor @AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行 @Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有 @AfterThrowing:异常抛出增强,相当于ThrowsAdvice @After: final增强,不管是抛出异常或者正常退出都会执行
环绕通知具体实现方法 相当于前置和后置通知的整合 但是也有区别: 1.前后置通知无法决定是否调用业务方法 而环绕通知可以决定是否调用业务方法 2.前后置通知无法获取业务方法的返回值,而环绕通知可以获取执行业务方法的返回值 3.目标方法的调用由环绕通知决定,即你可以决定是否调用目标方法,而前置和后置通知是不能决定的, 它们只是在方法的调用前后执行通知而已,即目标方法肯定是要执行的。joinPoint.proceed()就是执行目标方法的代码。 4.环绕通知可以控制返回对象,即可以返回一个与目标对象完全不同的返回值。虽然这很危险,但是却可以做到。
aop有两种方式:1.手动配置xml文件 2.使用注解
一:手动配置xml文件
首先要先导包(这里可以直接复制)
<!--公共版本 -->
<properties>
<spring.version>5.3.16</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<!--aop包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.18</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
创建一个util工具类(这里模拟一个日志记录)
package com.qyq.utils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
@Component
// 在使用springboot写aop的时候,有个JoinPoint类,用来获取代理类和被代理类的信息。
public class LogrecordUtil {
public void logRecord(JoinPoint joinPoint ){
//获取目标对象
String targeName = joinPoint.getTarget().getClass().getName();
//获取方法名称
String methodName = joinPoint.getSignature().getName();
// 获取执行方法参数
Object[] args = joinPoint.getArgs();
if (args!= null && args.length>0){
args[0].getClass().getName();
}
System.out.println("在"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) +"执行"+targeName+"中的:"+methodName+",该方法参数为:"+args);
}
/**
* 前置通知
* @param joinPoint
*/
public void beforeNotice(JoinPoint joinPoint){
System.out.println("----------在业务方法执行之前做的事--------------");
}
/**
* 后置通知
* @param joinPoint
*/
public void beafter(JoinPoint joinPoint){
System.out.println("----------在业务方法执行之后做的事--------------");
}
/** 环绕通知具体实现方法 相当于前置和后置通知的整合 但是也有区别:
* 1,前后置通知无法决定是否调用业务方法 而环绕通知可以决定是否调用业务方法
* 2,前后置通知无法获取业务方法的返回值,而环绕通知可以获取执行业务方法的返回值
*
* 3.目标方法的调用由环绕通知决定,即你可以决定是否调用目标方法,而前置和后置通知是不能决定的,
* 它们只是在方法的调用前后执行通知而已,即目标方法肯定是要执行的。joinPoint.proceed()就是执行目标方法的代码。
* 4.环绕通知可以控制返回对象,即可以返回一个与目标对象完全不同的返回值。虽然这很危险,但是却可以做到。
*
* JoinPoint是proceedingJoinPoint的父类
* @param proceedingJoinPoint
* @return
*/
public Object aroundNotice(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("-----环绕通知,在执行业务之前做的事--------");
Object result = null;
try {
//调用业务方法
result = proceedingJoinPoint.proceed();
}catch (Throwable throwable){
throwable.printStackTrace();
}
System.out.println("业务方法的返回值:"+result);
System.out.println("-----环绕通知,在执行业务之后做的事--------");
return result;
}
/**
* 异常通知
* @param joinPoint
* @param e
*/
public void exceptionNotice(JoinPoint joinPoint,Exception e){
System.out.println("---当业务出现异常时,才执行此异常方法---在执行"+joinPoint.getSignature().getName()+"出现了"+e.getClass().getName()+"异常描述:"+e.getMessage());
}
/**
* 最终通知
* @param joinPoint
*/
public void finallyNotice(JoinPoint joinPoint){
System.out.println("after无论包不报错都会执行此方法");
}
}
手动配置xml文件:
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.qyq"></context:component-scan>
<!--开启切面自动代理功能 扫描-->
<!--<aop:aspectj-autoproxy></aop:aspectj-autoproxy>-->
<!-- spring aop 配置
切面 = 切点 + 增强(通知)
-->
<aop:config>
<!-- 切入点配置 让aop配置喝业务联系-->
<aop:pointcut id="pointcutA" expression="execution(* com.qyq.service.impl.*.*(..))"/>
<aop:pointcut id="pointcutB" expression="execution(* com.qyq.service.impl.*.add*(..))
||execution(* com.qyq.service.impl.*.update*(..))
||execution(* com.qyq.service.impl.*.del*(..))"/>
<!--切面配置 -->
<aop:aspect ref="logrecordUtil">
<!--前置通知-->
<!-- <aop:before method="beforeNotice" pointcut-ref="pointcutA"></aop:before>-->
<!--后置通知-->
<aop:after-returning method="logRecord" pointcut-ref="pointcutB"></aop:after-returning>
<!--环绕通知-->
<!-- <aop:around method="aroundNotice" pointcut-ref="pointcutA"></aop:around>-->
<!--异常通知-->
<aop:after-throwing method="exceptionNotice" pointcut-ref="pointcutB" throwing="e" ></aop:after-throwing>
<!--最终通知-->
<aop:after method="finallyNotice" pointcut-ref="pointcutB"></aop:after>
</aop:aspect>
</aop:config>
</beans>
提前创建好一个实体类
配置完后就可以进行测试了
import com.qyq.entity.Dept;
import com.qyq.service.DeptService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AopTest {
@Test
public void testAop(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:SpringAop.xml");
//获取的首字母一定要小写 deptServiceImpl
DeptService deptService = (DeptService) applicationContext.getBean("deptServiceImpl");
Dept dept = new Dept("张三");
deptService.findAll();
deptService.addDept(dept);
deptService.delById(1);
deptService.updateDept(1);
}
}
二:使用注解开启aop
使用注解的话只需要把上面手动配置xml中的 <aop:config></aop:config> 中的代码注释掉,开启aop自动代理扫描即可:
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
package com.qyq.utils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LogrecordUtil02 {
// 配置切入点
@Pointcut(value = "execution(* com.qyq.service.impl.*.add*(..))" +
"||execution(* com.qyq.service.impl.*.update*(..))" +
"||execution(* com.qyq.service.*.del*(..))")
public void poinCutA() {
}
//后置通知 统一记录日志
// @AfterReturning("poinCutA()")
// public void logRecord(JoinPoint joinPoint) {
// //获取目标对象
// String targetNaem = joinPoint.getTarget().getClass().getName();
// //获取方法名称
// String methodName = joinPoint.getSignature().getName();
// //获取参数
// Object[] args = joinPoint.getArgs();
// String argsType = null;
// if (args != null && args.length > 0) {
// argsType = args[0].getClass().getName();
// }
// System.out.println("在" + new SimpleDateFormat("yyyy-MM-dd HH-mm-ss").format(new Date()) + "执行" + targetNaem + "中的方法" + methodName + "该方法的参数有:" + argsType);
// }
//前置通知
// @Before("poinCutA()")
// public void beforeNotice(JoinPoint joinPoint) {
// System.out.println("--------在程序前运行的代码-------------");
// }
//环绕通知
@Around("poinCutA()")
public Object aroundNotice(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("------前置通知------");
Object result = null;
try {
result = proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("业务方法的返回值是:" + result);
System.out.println("-------执行的后置通知------");
return result;
}
//异常通知
@AfterThrowing(value = "poinCutA()", throwing = "e")
public void throwingNotice(JoinPoint joinPoint, Exception e) {
System.out.println("代码异常执行的方法--" + joinPoint.getSignature().getName() + ",出现的异常为:" + e.getClass().getName() + "报的错为:" + e.getMessage());
}
// 最终通知
@After("poinCutA()")
public void afterNotice(JoinPoint joinPoint) {
System.out.println("最终执行的方法After,不管无论有没有异常都会执行");
}
}