1.什么是AOP ?
AOP((Aspect-Oriented Programming)即面向切面编程,它是一种编程范式,是一种思想.它是Spring框架的重要组成部分
Spring整体架构图
2.AOP能用来干嘛?
它可以在不改变原有代码的情况下,动态地将额外的代码(称之为“切面”)横向地“切入”到原有代码流程中的特定位置,从而达到增强原有代码的目的。AOP的目的是将通用的横切逻辑抽象出来,将它们与核心业务逻辑分离,以提高系统的可维护性和可扩展性。
用通俗易懂的话来讲 就是 在不改变原有代码的基础上添加新的功能.
在实际运用中 AOP通常被应用在日志记录、性能统计、事务管理、异常处理、权限控制等地方.
3.AOP的主要组成部分
切面(Aspect):定义了一个横切关注点和与之相关的一组切入点,在这些切入点上定义了横切逻辑。例如,日志、安全、事务等。
切入点(Join Point):程序执行过程中的特定位置,例如方法调用、异常抛出等。切入点是AOP中的关键概念,可以看作是AOP中的方法。
通知(Advice):切面在切入点处执行的具体逻辑,包括前置通知、后置通知、返回通知、异常通知、环绕通知等。
目标对象(Target Object):要应用横切逻辑的对象,即被切面增强的对象。
AOP代理(AOP Proxy):AOP框架生成的代理对象,用于在目标对象方法执行前后织入切面逻辑。AOP代理可以通过JDK动态代理、CGLIB代理等方式实现。
4.AspectJ
在Java中,AspectJ是实现AOP的主要框架之一。AspectJ支持以下AOP概念:
切点(Pointcut):指定要拦截的方法或类。
通知(Advice):在拦截点上执行的操作,如前置通知(Before Advice)、后置通知(After Advice)、返回通知(After Returning Advice)、异常通知(After Throwing Advice)和环绕通知(Around Advice)。
切面(Aspect):切点和通知的组合。
连接点(Join Point):程序执行过程中可能发生的某个点,如方法调用、方法抛出异常等。
织入(Weaving):将切面应用到应用程序的过程。
在Spring框架中,也提供了AOP的支持。Spring AOP与AspectJ类似,但它提供了更轻量级的AOP解决方案,并允许开发人员将切面应用于Spring IoC容器中的Bean。Spring AOP支持以下通知类型:前置通知、后置通知、返回通知、异常通知和环绕通知。
总的来说,AOP是一种强大的编程范式,可以使代码更加模块化和可重用。它提供了一种分离关注点的方法,使开发人员可以更轻松地管理应用程序的复杂性。
5.AOP里所有通知?
在AOP中,通知(Advice)是织入目标对象中的代码,它会在特定的连接点(Join Point)执行。AOP的通知包括以下几种类型:
前置通知(Before Advice):在目标方法执行之前执行的通知。
后置通知(After Advice):在目标方法执行之后执行的通知。
返回通知(After Returning Advice):在目标方法返回结果后执行的通知。
异常通知(After Throwing Advice):在目标方法抛出异常后执行的通知。
环绕通知(Around Advice):可以在目标方法执行前后执行特定的逻辑,还可以控制目标方法的执行,即可以选择是否执行目标方法或者如何执行目标方法。
最终通知(After Finally Advice):在目标方法执行完毕后,无论是否抛出异常都会执行的通知。
1.前置通知
@Before("execution(* com.example.service.UserService.*(..))")
public void beforeAdvice() {
// 在目标方法执行前执行的逻辑
}
2.后置通知示例:
@After("execution(* com.example.service.UserService.*(..))")
public void afterAdvice() {
// 在目标方法执行后执行的逻辑
}
3.返回通知示例:
@AfterReturning(pointcut = "execution(* com.example.service.UserService.*(..))", returning = "result")
public void afterReturningAdvice(Object result) {
// 在目标方法返回结果后执行的逻辑,result 是目标方法的返回值
}
4.异常通知示例:
@AfterThrowing(pointcut = "execution(* com.example.service.UserService.*(..))", throwing = "ex")
public void afterThrowingAdvice(Exception ex) {
// 在目标方法抛出异常后执行的逻辑,ex 是目标方法抛出的异常
}
5.环绕通知示例:
@Around("execution(* com.example.service.UserService.*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 在目标方法执行前后执行特定的逻辑,可以控制目标方法的执行
// 执行目标方法
Object result = joinPoint.proceed();
// 在目标方法执行后执行的逻辑
return result;
}
6.最终通知示例:
@AfterFinally("execution(* com.example.service.UserService.*(..))")
public void afterFinallyAdvice() {
// 在目标方法执行完毕后,无论是否抛出异常都会执行的逻辑
}
在上述示例中,execution(* com.example.service.UserService.*(..)) 是一个切点表达式,用于匹配 UserService 类中的所有方法。其中的 * 表示匹配任意方法名,.. 表示匹配任意参数列表。在这里可见性只能为public 可省略不写
切点表达式的基本语法:
execution([可见性] 返回类型 [声明类型].方法名(参数) [异常类型])
其中:
execution:切入点表达式关键字;
[可见性]:可选,指定方法的可见性,如 public、private、protected 或 *;
返回类型:指定方法的返回类型,如 void、int、String 等;
[声明类型]:可选,指定方法所属的类、接口、注解等声明类型;
方法名:指定方法的名称,支持通配符 *;
参数:指定方法的参数类型列表,用逗号分隔,支持通配符 *;
[异常类型]:可选,指定方法可能抛出的异常类型。
例:
execution(public * com.example.service.UserService.addUser(..)):指定 com.example.service.UserService 类中的 addUser 方法;
execution(* com.example.service.*.*(..)):指定 com.example.service 包下的所有方法;
execution(* com.example.service..*.*(..)):指定 com.example.service 包及其子包下的所有方法;
execution(* com.example.service.UserService.*(String)):指定 com.example.service.UserService 类中所有参数类型为 String 的方法。
此外,切入点表达式还支持 &&(逻辑与)、||(逻辑或)和 !(逻辑非)等运算符,以及 @annotation、@within、@args 等注解限定符。
6.AOP在SpringBoot中的使用
导入依赖
一.注解+切点表达式
1.首先,在主程序上加入 @EnableAspectJAutoProxy 注解来启用 AspectJ 自动代理。
@SpringBootApplication
@EnableAspectJAutoProxy
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
2.然后,在切面类上加入 @Aspect 注解,标识这个类是一个切面类。
@Aspect
@Component
public class MyAspect {
// 切入点和通知的定义
}
3.在切面类中,定义切入点表达式和通知,可以使用 @Pointcut、@Before、@After、@AfterReturning、@AfterThrowing 和 @Around 注解。
@Aspect
@Component
public class MyAspect {
@Pointcut("execution(* com.example.demo.service..*.*(..))")
private void serviceMethod() {}
@Before("serviceMethod()")
public void beforeAdvice() {
// 在目标方法执行之前执行的逻辑
}
@After("serviceMethod()")
public void afterAdvice() {
// 在目标方法执行之后执行的逻辑
}
@AfterReturning(pointcut = "serviceMethod()", returning = "result")
public void afterReturningAdvice(Object result) {
// 在目标方法返回结果后执行的逻辑
}
@AfterThrowing(pointcut = "serviceMethod()", throwing = "e")
public void afterThrowingAdvice(Exception e) {
// 在目标方法抛出异常后执行的逻辑
}
@Around("serviceMethod()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 环绕通知的逻辑
Object result = joinPoint.proceed();
// 环绕通知的逻辑
return result;
}
}
上述示例中,使用了 execution() 表达式定义了一个切入点,匹配了 com.example.demo.service 包及其子包中所有的方法。在切面类中,使用不同的注解实现了不同类型的通知。
二.纯注解用法
纯注解形式的 AOP 在 Spring Boot 中使用也非常简单,具体步骤如下:
1.在需要增强的方法上添加注解 @LogExecutionTime,该注解可以自定义一个别名,比如 @MyLog。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}
2.创建一个切面类,使用注解 @Aspect 表示该类是一个切面类,使用注解 @Component 将该类交给 Spring 管理。
@Aspect
@Component
public class LogAspect {
@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
return proceed;
}
}
在上面的代码中,使用了注解 @Around 表示这是一个环绕通知,括号内的参数 @annotation(LogExecutionTime) 表示切入点是所有被注解 @LogExecutionTime 标记的方法。
3.在启动类中开启 AOP 功能,只需要在启动类上添加注解 @EnableAspectJAutoProxy 即可。
@SpringBootApplication
@EnableAspectJAutoProxy
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4.最后,在需要增强的方法上加上 @LogExecutionTime 注解即可。
@RestController
public class MyController {
@LogExecutionTime
@GetMapping("/hello")
public String hello() throws InterruptedException {
Thread.sleep(new Random().nextInt(1000));
return "hello";
}
}
以上就是使用纯注解方式实现 AOP 的步骤和示例。
演示代码仓库下载
测试结果
通知的执行顺序
1.
环绕前通知----> 2 前置通知--> 3 核心方法 ----->4 返回通知/异 ----> 5 后置通知 ---->6 环绕后通知
问题:
在学习AOP的过程中遇到了一个问题 关于切点(Pointcut),连接点(Join Point),切点(Pointcut)傻傻分不清!
在查找了诸多资料后 关于它们之间的关系我进行了如下总结:
在Spring AOP中,切入点(Pointcut)是连接点(Join Point)的集合,它定义了一个或多个连接点来进行通知。切点(Pointcut)是一个表达式,它描述了哪些连接点将会被选中进行拦截。
在AspectJ中,切点(Pointcut)也是连接点(Join Point)的集合,用于匹配方法或者其他Join Point。连接点(Join Point)表示应用程序在其执行期间可以插入通知的位置。因此,AspectJ中的切点(Pointcut)和连接点(Join Point)具有相同的含义。
可以说,切入点(Pointcut)和切点(Pointcut)是同义词,而连接点(Join Point)是切点(Pointcut)或切入点(Pointcut)匹配的目标点。在AOP中,切入点(Pointcut)和切点(Pointcut)的作用是一样的,而连接点(Join Point)是在运行时匹配到的符合切入点(Pointcut)或切点(Pointcut)的目标点。