AOP简介
AOP:面向切面编程,是通过预编译方式和运行期动态代理,实现在不修改源代码的情况下给程序动态统一添加功能的一种技术,同时是对OOP(面向对象编程)的补充和完善,常被用来在spring中实现日志记录、性能监控等功能。
面向对象实现日志记录,性能监控这些功能时,需要在每个对象中都添加相同的方法,这样就产生了较大的重复工作量和大量的重复代码,不利于维护,使用AOP,可以大大减少代码数量,方便维护。
AOP实现原理
Spring 实现AOP思想使⽤的是动态代理技术。默认状况下,Spring会根据被代理对象是否实现接⼝来选择使⽤JDK仍是CGLIB。当被代理对象没有实现任何接⼝时, Spring会选择CGLIB。当被代理对象实现了接⼝, Spring会选择JDK官⽅的代理技术,不过能够经过配置的⽅式,让Spring强制使⽤CGLIB。
AOP相关概念
Aspect(切面)
Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
Joint point(连接点)
表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
Pointcut(切点)
表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
Advice(增强)
Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
Target(目标对象)
织入 Advice 的目标对象.。
Weaving(织入)
将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
AOP常用注解
@Component
: 将当前类注入到Spring容器内@Aspect
:表明是一个切面类@Before
:前置通知,在方法执行之前执行@After
:后置通知,在方法执行之后执行@AfterRuturning
:返回通知,在方法返回结果之后执行@AfterThrowing
:异常通知,在方法抛出异常之后执行@Around
:环绕通知,围绕着方法执行@Pointcut
:切入点,PointCut(切入点)表达式有很多种,其中execution用于使用切面的连接点。
Springboot整合AOP
实例一(execution表达式)
1.导入依赖
<!--aop相关的依赖引入-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
完整的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring-boot-aop</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.14</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${project.parent.version}</version>
</plugin>
</plugins>
</build>
</project>
2.创建一个UserService及其实现
public interface UserService {
String save(String user);
void testAnnotationAop();
void testIntroduction();
}
实现类
@Slf4j
@Service
public class UserServiceImpl implements UserService {
@Override
public String save(String user) {
log.info("保存用户信息");
if ("a".equals(user)) {
throw new RuntimeException();
}
return user;
}
@Override
public void testAnnotationAop() {
log.info("testAnnotationAop");
}
@Override
public void testIntroduction() {
log.info("do testIntroduction");
}
}
3.创建一个切面类
package com.dhx.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 第一步:明一个切面类
*/
@Slf4j
@Aspect
@Component
public class LogAspect {
/**
* 第二步:定义一个切入点,含义:(只对com.dhx..service.*Service.save的方法起作用)
*/
@Pointcut("execution(* com.dhx..service.*Service.save*(String))")
public void logPointcut() {}
/**
* 第三步:定义处理事件
* @param joinPoint: 连接点(可以在这个类中获取对应的注解参数和方法参数)
*/
@Before(value ="logPointcut()")
public void logTest(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
Signature signature = joinPoint.getSignature();
String declaringTypeName = signature.getDeclaringTypeName();
String name = signature.getName();
log.info("@Before"+"方法参数:{}",args);
log.info("@Before"+"调用类名:{}",declaringTypeName);
log.info("@Before"+"方法名称: {}",name);
}
/**
* 返回通知
* @param ret
* @throws Throwable
*/
@AfterReturning(returning = "ret", pointcut = "logPointcut()")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
log.info("@AfterReturning"+"返回通知:方法的返回值 : [{}]",ret);
}
/**
* 异常通知
* @param jp
* @param ex
*/
@AfterThrowing(throwing = "ex", pointcut = "logPointcut()")
public void throwss(JoinPoint jp,Exception ex){
log.info("@AfterThrowing"+"异常通知:方法异常时执行.....");
log.info("@AfterThrowing"+"产生异常的方法:[{}]",jp);
log.info("@AfterThrowing"+"异常种类:[{}]",ex);
}
/**
* 后置通知
*/
@After("logPointcut()")
public void after(){
log.info("@After"+"后置通知.....");
}
}
4.测试
编写单元测试类进行测试
@SpringBootTest
public class SpringBootAopApplicationTests {
@Resource
private UserService userService;
@Test
public void testAop() {
userService.save("张三");
}
}
控制台输出:
2023-03-19 19:55:46.839 INFO 8032 --- [ main] com.dhx.aspect.LogAspect : @Before方法参数:张三
2023-03-19 19:55:46.841 INFO 8032 --- [ main] com.dhx.aspect.LogAspect : @Before调用类名:com.dhx.service.impl.UserServiceImpl
2023-03-19 19:55:46.841 INFO 8032 --- [ main] com.dhx.aspect.LogAspect : @Before方法名称: save
2023-03-19 19:55:46.852 INFO 8032 --- [ main] com.dhx.service.impl.UserServiceImpl : 保存用户信息
2023-03-19 19:55:46.853 INFO 8032 --- [ main] com.dhx.aspect.LogAspect : @AfterReturning返回通知:方法的返回值 : [张三]
2023-03-19 19:55:46.853 INFO 8032 --- [ main] com.dhx.aspect.LogAspect : @After后置通知.....
抛出异常时控制台输出
@Test()
public void testAop2() {
userService.save("a");
}
2023-03-19 20:00:26.119 INFO 16092 --- [ main] com.dhx.aspect.LogAspect : @Before方法参数:a
2023-03-19 20:00:26.121 INFO 16092 --- [ main] com.dhx.aspect.LogAspect : @Before调用类名:com.dhx.service.impl.UserServiceImpl
2023-03-19 20:00:26.121 INFO 16092 --- [ main] com.dhx.aspect.LogAspect : @Before方法名称: save
2023-03-19 20:00:26.144 INFO 16092 --- [ main] com.dhx.service.impl.UserServiceImpl : 保存用户信息
2023-03-19 20:00:26.145 INFO 16092 --- [ main] com.dhx.aspect.LogAspect : @AfterThrowing异常通知:方法异常时执行.....
2023-03-19 20:00:26.145 INFO 16092 --- [ main] com.dhx.aspect.LogAspect : @AfterThrowing产生异常的方法:[execution(String com.dhx.service.impl.UserServiceImpl.save(String))]
2023-03-19 20:00:26.151 INFO 16092 --- [ main] com.dhx.aspect.LogAspect : @AfterThrowing异常种类:[{}]
java.lang.RuntimeException: null
省略.....
2023-03-19 20:00:26.158 INFO 16092 --- [ main] com.dhx.aspect.LogAspect : @After后置通知.....
实例二 (@annotation方式)
1.声明一个自定义注解
/**
* @Target: 表示次注解可以标注在方法上
* @Retention: 运行时生效
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyLog {
String value() default "";
}
2.新增AnnotationAop类:
package com.dhx.aspect;
import com.dhx.annotation.MyLog;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 第一步:明一个切面类
*/
@Slf4j
@Aspect
@Component
public class AnnotationAop {
/**
* 切入点:增强标有ApiLog注解的方法
*/
@Pointcut(value = "@annotation(myLog)", argNames = "myLog")
public void pointcut(MyLog myLog) {
}
/**
* 第三步:定义处理事件
* @param joinPoint: 连接点(可以在这个类中获取对应的注解参数和方法参数)
*/
@Before("@annotation(com.dhx.annotation.MyLog)")
public void before(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
MyLog myLog = method.getAnnotation(MyLog.class);
log.info("class=AnnotationAop"+"Before注解式拦截 " + myLog.value());
}
/**
* 返回通知
* @param ret
* @throws Throwable
*/
@AfterReturning(returning = "ret", pointcut = "@annotation(com.dhx.annotation.MyLog)")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
log.info("class=AnnotationAop"+"@AfterReturning"+"返回通知:方法的返回值 : [{}]",ret);
}
/**
* 异常通知
* @param jp
* @param ex
*/
@AfterThrowing(throwing = "ex", pointcut = "@annotation(com.dhx.annotation.MyLog)")
public void throwss(JoinPoint jp,Exception ex){
log.info("class=AnnotationAop"+"@AfterThrowing"+"异常通知:方法异常时执行.....");
log.info("class=AnnotationAop"+"@AfterThrowing"+"产生异常的方法:[{}]",jp);
log.info("class=AnnotationAop"+"@AfterThrowing"+"异常种类:[{}]",ex);
}
/**
* 后置通知
*/
@After("@annotation(com.dhx.annotation.MyLog)")
public void after(){
log.info("class=AnnotationAop"+"@After"+"后置通知.....");
}
}
3.在接口方法上加自定义注解
@Slf4j
@Service
public class UserServiceImpl implements UserService {
@MyLog(value = "test")
@Override
public String save(String user) {
log.info("保存用户信息");
if ("a".equals(user)) {
throw new RuntimeException();
}
return user;
}
@MyLog(value = "test")
@Override
public void testAnnotationAop() {
log.info("testAnnotationAop");
}
@Override
public void testIntroduction() {
log.info("do testIntroduction");
}
}
4.测试
测试方法
@Test
public void testAop3() {
userService.testAnnotationAop();
}
控制台输出
2023-03-19 20:30:03.843 INFO 13692 --- [ main] com.dhx.aspect.AnnotationAop : class=AnnotationAop: Before注解式拦截 test
2023-03-19 20:30:03.856 INFO 13692 --- [ main] com.dhx.service.impl.UserServiceImpl : testAnnotationAop
2023-03-19 20:30:03.856 INFO 13692 --- [ main] com.dhx.aspect.AnnotationAop : class=AnnotationAop: @AfterReturning返回通知:方法的返回值 : [null]
2023-03-19 20:30:03.858 INFO 13692 --- [ main] com.dhx.aspect.AnnotationAop : class=AnnotationAop: @After后置通知.....
测试方法
@Test
public void testAop() {
userService.save("张三");
}
控制台输出
2023-03-19 20:31:09.926 INFO 12636 --- [ main] com.dhx.aspect.AnnotationAop : class=AnnotationAop: Before注解式拦截 test
2023-03-19 20:31:09.926 INFO 12636 --- [ main] com.dhx.aspect.LogAspect : @Before方法参数:张三
2023-03-19 20:31:09.928 INFO 12636 --- [ main] com.dhx.aspect.LogAspect : @Before调用类名:com.dhx.service.impl.UserServiceImpl
2023-03-19 20:31:09.928 INFO 12636 --- [ main] com.dhx.aspect.LogAspect : @Before方法名称: save
2023-03-19 20:31:09.939 INFO 12636 --- [ main] com.dhx.service.impl.UserServiceImpl : 保存用户信息
2023-03-19 20:31:09.939 INFO 12636 --- [ main] com.dhx.aspect.LogAspect : @AfterReturning返回通知:方法的返回值 : [张三]
2023-03-19 20:31:09.939 INFO 12636 --- [ main] com.dhx.aspect.LogAspect : @After后置通知.....
2023-03-19 20:31:09.939 INFO 12636 --- [ main] com.dhx.aspect.AnnotationAop : class=AnnotationAop: @AfterReturning返回通知:方法的返回值 : [张三]
2023-03-19 20:31:09.939 INFO 12636 --- [ main] com.dhx.aspect.AnnotationAop : class=AnnotationAop: @After后置通知.....
@Order(1) 可以在类上加@Order注解来定义切面的执行顺序,越小越在前面执行
实例三 (环绕通知方式):
1. 改造AnnotationAop类:
package com.dhx.aspect;
import com.dhx.annotation.MyLog;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 第一步:明一个切面类
*/
@Slf4j
@Aspect
@Component
public class AnnotationAop {
@Pointcut(value = "@annotation(myLog)", argNames = "myLog")
public void pointcut(MyLog myLog) {
}
/**
* 环绕通知,环绕增强,相当于MethodInterceptor
* @param joinPoint
* @param myLog
* @return
* @throws Throwable
*/
@Around(value = "pointcut(myLog)", argNames = "joinPoint,myLog")
public Object aroundAdvice(ProceedingJoinPoint joinPoint, MyLog myLog) throws Throwable {
try {
//得到方法执行所需的参数
Object[] args = joinPoint.getArgs();
log.info(myLog.value());
log.info("通知类中的aroundAdvice方法执行了。。前置");
//明确调用切入点方法(切入点方法)
Object proceed = joinPoint.proceed();
log.info("通知类中的aroundAdvice方法执行了。。返回");
log.info("返回通知:"+proceed);
return proceed;
} catch (Throwable throwable) {
log.info("通知类中的aroundAdvice方法执行了。。异常");
throw throwable;
} finally {
log.info("通知类中的aroundAdvice方法执行了。。后置");
}
}
}
2.测试
测试方法
@Test
public void testAop3() {
userService.testAnnotationAop();
}
控制台输出
2023-03-19 20:50:13.401 INFO 3340 --- [ main] com.dhx.aspect.AnnotationAop : test
2023-03-19 20:50:13.401 INFO 3340 --- [ main] com.dhx.aspect.AnnotationAop : 通知类中的aroundAdvice方法执行了。。前置
2023-03-19 20:50:13.411 INFO 3340 --- [ main] com.dhx.service.impl.UserServiceImpl : testAnnotationAop
2023-03-19 20:50:13.411 INFO 3340 --- [ main] com.dhx.aspect.AnnotationAop : 通知类中的aroundAdvice方法执行了。。返回
2023-03-19 20:50:13.411 INFO 3340 --- [ main] com.dhx.aspect.AnnotationAop : 返回通知:null
2023-03-19 20:50:13.411 INFO 3340 --- [ main] com.dhx.aspect.AnnotationAop : 通知类中的aroundAdvice方法执行了。。后置
实例四(DeclareParents)
1.创建一个DoSthService及其实现
public interface DoSthService {
void doSth();
}
@Slf4j
@Service
public class DoSthServiceImpl implements DoSthService {
@Override
public void doSth() {
log.info("do sth ....");
}
}
2.新增一个IntroductionAop类
package com.dhx.aspect;
import com.dhx.service.DoSthService;
import com.dhx.service.impl.DoSthServiceImpl;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class IntroductionAop {
@DeclareParents(value = "com.dhx..service..*", defaultImpl = DoSthServiceImpl.class)
public DoSthService doSthService;
}
3.测试
测试方法
@Test
public void testIntroduction() {
userService.testIntroduction();
//Aop 让UserService方法拥有 DoSthService的方法
DoSthService doSthService = (DoSthService) userService;
doSthService.doSth();
}
控制台输出
2023-03-19 20:55:51.669 INFO 14700 --- [ main] com.dhx.service.impl.UserServiceImpl : do testIntroduction
2023-03-19 20:55:51.669 INFO 14700 --- [ main] com.dhx.service.impl.DoSthServiceImpl : do sth ....
完整的测试类
package com.dhx;
import com.dhx.service.DoSthService;
import com.dhx.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
public class SpringBootAopApplicationTests {
@Resource
private UserService userService;
@Test
public void testAop() {
userService.save("张三");
}
@Test()
public void testAop2() {
userService.save("a");
}
@Test
public void testAop3() {
userService.testAnnotationAop();
}
@Test
public void testIntroduction() {
userService.testIntroduction();
//Aop 让UserService方法拥有 DoSthService的方法
DoSthService doSthService = (DoSthService) userService;
doSthService.doSth();
}
}