Java外功精要(5)——Spring AOP

Java外功精要(5)——Spring AOP

1.概述

面向切面编程(Aspect Orient Programming,AOP):是一种编程范式,旨在将 横切关注点(Cross-Cutting Concerns,如日志、事务、安全等) 从业务逻辑中分离出来,通过模块化的方式增强代码的可维护性和复用性。核心思想是通过“切面”定义通用功能,并在运行时动态织入到目标代码中

横切关注点(Cross-Cutting Concerns):指的是在系统中"横向"跨越多个模块、多个层次的功能需求,它们无法很好地被封装在单个类或模块中

1.1 场景举例:监控业务性能

1.1.1 硬编码实现

@Slf4j
public class HardCoding {
    public void demo() {
        long startTime = System.currentTimeMillis();

        //业务代码

        log.info("消耗时间:{}", System.currentTimeMillis() - startTime);
    }

    public static void main(String[] args) {
        new HardCoding().demo();
    }
}

使用这种硬编码方式监控业务性能主要有以下缺点:

  • 代码侵入性强:业务代码与监控代码耦合,修改监控代码会影响业务代码
  • 重复代码多:每个方法都要重复编写监控代码,维护困难
  • 不利于管理:监控逻辑分散,难以统一管理
  • 容易遗漏:开发人员可能忘记添加监控代码

1.1.2 AOP实现

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>3.4.8</version> <!-- 应与SpringBoot版本一致 -->
</dependency>
@Slf4j
@RestController
@RequestMapping("/demo")
public class Controller {

    @RequestMapping("/a")
    public void methodA(){
        log.info("执行methodA");
    }

    @RequestMapping("/b")
    public void methodB(){
        log.info("执行methodB");
    }

    @RequestMapping("/c")
    public void methodC(){
        log.info("执行methodC");
    }
}
@***ponent
@Slf4j
@Aspect
public class AOP {
    @Around("execution(* org.example.springaop.blog_demo.Controller.*(..))")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        //记录开始时间
        long startTime = System.currentTimeMillis();
        //执行业务
        Object result = joinPoint.proceed();
        //计算时间
        log.info("消耗时间:{}", System.currentTimeMillis() - startTime);
        return result;
    }
}

执行结果

相较于硬编码的方式,使用Spring AOP监控业务性能有很多显著好处:

  • 代码解耦:业务代码与监控代码完全分离,易于维护
  • 统一管理:所有监控逻辑集中处理,保持一致性和规范性
  • 无侵入性:对现有代码零侵入,新增监控不影响业务逻辑
  • 复用性强:一套监控方案可以应用到整个项目中的所有方法

1.2 核心术语

  • 切入点(Pointcut):提供一个表达式,用于指定对哪些方法进行增强。例如,上图箭头切到的ABC业务的集合为一个切入点,具体能切到哪些方法与表达式有关
  • 连接点(Join Point):满足切点表达式的方法。上图中处理业务A/B/C的执行前后均为连接点(切入点包含连接点)
  • 通知(Advice):连接点的共性功能(重复的逻辑)。如上图箭头执行的逻辑
  • 切面(Aspect):定义了在何处(切入点)和何时(通知)执行额外逻辑,即切入点+通知

2.Spring AOP

2.1 @Aspect

作用:用于标识一个类为切面(Aspect)。切面包含切入点(Pointcut)和通知(Advice),用于模块化横切关注点(如日志、事务、权限等)

2.2 切入点

execution:是Spring AOP中定义切点的一种表达式,用于指定在哪些方法或类上应用通知(Advice)。它将横切关注点(如日志、事务)与业务逻辑分离,通过表达式匹配目标方法或类

//语法结构
execution(<访问限定修饰符> <返回类型> <包名.类名.方法(方法参数)> <异常>)

*:匹配任意字符(除包分隔符外)
..:匹配任意子包或多级目录;匹配任意数量参数

@Pointcut:是Spring AOP中的一个注解,用于定义一个可重用的切点表达式

2.3 通知类型

  • ①Around注解·:最强大的通知类型,可以在目标方法执行前后完全控制其行为。它需要接收一个ProceedingJoinPoint类型参数,通过调用proceedingJoinPoint.proceed()来执行目标方法。Around通知可以修改返回值、处理异常或完全阻止目标方法执行
  • ②Before注解:在目标方法执行前触发,无法阻止方法执行(除非抛出异常)
  • ③After注解:在目标方法完成后执行,无论方法是正常返回还是抛出异常
  • ④AfterReturning注解:在目标方法正常返回后执行,可以通过访问(不能修改)返回值
    • returning属性:指定接收返回值的参数名,参数类型必须与目标方法返回类型兼容
  • ⑤AfterThrowing注解:只在目标方法抛出异常时执行
    • throwing属性:用于绑定目标方法抛出的异常对象
@Slf4j
@RestController
@RequestMapping("/demo")
public class Controller {

    @RequestMapping("/a")
    public Object methodA(Integer id){
        log.info("执行methodA");
        return id;
    }

    @RequestMapping("/b")
    public void methodB(){
        log.info("执行methodB");
        throw new RuntimeException("发生异常");
    }
}
@***ponent
@Slf4j
@Aspect
public class AOP {

    @Pointcut("execution(* org.example.springaop.blog_demo.Controller.*(..))")
    public void pointcut(){}

    @Around("pointcut()")
    //@Around("execution(* org.example.springaop.blog_demo.Controller.*(..))")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("doAround:业务执行前");
        //执行业务
        Object result = joinPoint.proceed();
        log.info("doAround:业务执行后,result:{}", result);
        return "su***ess";
    }

    @Before("pointcut()")
    //@Before("execution(* org.example.springaop.blog_demo.Controller.*(..))")
    public void doBefore(){
        log.info("doBefore");
    }

    @After("pointcut()")
    //@After("execution(* org.example.springaop.blog_demo.Controller.*(..))")
    public void doAfter(){
        log.info("doAfter");
    }

    @AfterReturning(value = "pointcut()",returning = "id")
    //@AfterReturning("execution(* org.example.springaop.blog_demo.Controller.*(..))")
    public void doAfterReturning(Integer id){
        //发生异常时不执行
        log.info("doAfterReturning,id:{}", id);
    }

    @AfterThrowing(value = "pointcut()",throwing = "throwable")
    //@AfterThrowing("execution(* org.example.springaop.blog_demo.Controller.*(..))")
    public void doAfterThrowing(Throwable throwable){
        log.info("doAfterThrowing,throwable:{}", throwable.getMessage());
    }
}
  • 1.无异常抛出时,URL:127.0.0.1:8080/demo/a?id=1
  • 2.有异常抛出时,URL:127.0.0.1:8080/demo/b

2.4 @annotation

作用:@annotation表达式用于匹配带有指定注解的方法,是AOP中实现精准切入的关键方式之一。通过该表达式,可以拦截被特定注解标记的方法,实现逻辑增强

1.创建自定义注解

@Target(ElementType.METHOD)//注解级别:方法注解
@Retention(RetentionPolicy.RUNTIME)//生命周期:运行时
public @interface CustomizeAspect {

}

2.使用@annotation表达式描述切点

//添加到切面类中
@Around("@annotation(org.example.springaop.config.CustomizeAspect)")
public Object customize(ProceedingJoinPoint joinPoint) throws Throwable {
    log.info("customize before");
    Object result = joinPoint.proceed();
    log.info("customize after,result:{}", result);
    return "su***ess";
}

3.将自定义注解添加到连接点上

//添加到Controller类中
@RequestMapping("/c")
@CustomizeAspect
public Object methodC(Integer id){
    log.info("执行methodC");
    return id;
}

执行结果

2.5 @Order

作用:用于指定Bean的加载顺序,其核心作用是通过数值定义优先级,数值越小优先级越高

@***ponent
@Slf4j
@Aspect
@Order(1)
public class AOP1 {

    @Pointcut("execution(* org.example.springaop.controller.Controller.*(..))")
    public void pointcut(){}

    @Around("pointcut()")
    //@Around("execution(* org.example.springaop.controller.Controller.*(..))")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("doAround1:业务执行前");
        //执行业务
        Object result = joinPoint.proceed();
        //计算时间
        log.info("doAround1:业务执行后,result:{}", result);
        return "su***ess1";
    }

    @Before("pointcut()")
    public void doBefore(){
        log.info("doBefore1");
    }

    @After("pointcut()")
    public void doAfter(){
        log.info("doAfter1");
    }

    @AfterReturning(value = "pointcut()",returning = "id")
    public void doAfterReturning(Integer id){
        //发生异常时不执行
        log.info("doAfterReturning1,id:{}", id);
    }

    @AfterThrowing(value = "pointcut()",throwing = "throwable")
    public void doAfterThrowing(Throwable throwable){
        log.info("doAfterThrowing1,throwable:{}", throwable.getMessage());
    }

    @Around("@annotation(org.example.springaop.config.CustomizeAspect)")
    public Object customize(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("customize before1");
        Object result = joinPoint.proceed();
        log.info("customize after1,result:{}", result);
        return "su***ess1";
    }
}
@***ponent
@Slf4j
@Aspect
@Order(2)
public class AOP2 {

    @Pointcut("execution(* org.example.springaop.controller.Controller.*(..))")
    public void pointcut(){}

    @Around("pointcut()")
    //@Around("execution(* org.example.springaop.controller.Controller.*(..))")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("doAround2:业务执行前");
        //执行业务
        Object result = joinPoint.proceed();
        //计算时间
        log.info("doAround2:业务执行后,result:{}", result);
        return "su***ess2";
    }

    @Before("pointcut()")
    public void doBefore(){
        log.info("doBefore2");
    }

    @After("pointcut()")
    public void doAfter(){
        log.info("doAfter2");
    }

    @AfterReturning(value = "pointcut()",returning = "id")
    public void doAfterReturning(Integer id){
        //发生异常时不执行
        log.info("doAfterReturning2,id:{}", id);
    }

    @AfterThrowing(value = "pointcut()",throwing = "throwable")
    public void doAfterThrowing(Throwable throwable){
        log.info("doAfterThrowing2,throwable:{}", throwable.getMessage());
    }

    @Around("@annotation(org.example.springaop.config.CustomizeAspect)")
    public Object customize(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("customize before2");
        Object result = joinPoint.proceed();
        log.info("customize after2,result:{}", result);
        return "su***ess2";
    }
}

执行结果

3.AOP底层原理

3.1 代理模式

代理模式(Proxy Pattern)通过创建一个代理对象来控制对目标对象的访问。代理对象作为目标对象的替代品,可以在访问前后添加额外逻辑(如权限控制、性能监控等)

3.2 静态代理

代理类在编译期确定,需要手动为每个目标类编写代理类

以租房为例:租客(调用方)、房东(目标对象)、中介(代理对象)

public interface IHouse {
    void rent();
    void sell();
}
//房东
public class RealHouse implements IHouse {
    @Override
    public void rent() {
        System.out.println("房东出租房子");
    }

    @Override
    public void sell() {
        System.out.println("房东售卖房子");
    }
}
//中介
public class HouseProxy implements IHouse {

    private final IHouse realHouse;

    public HouseProxy(IHouse realHouse) {
        this.realHouse = realHouse;
    }

    @Override
    public void rent() {
        System.out.println("开始代理");
        realHouse.rent();
        System.out.println("结束代理");
    }

    @Override
    public void sell() {
        System.out.println("开始代理");
        realHouse.sell();
        System.out.println("结束代理");
    }

    public static void main(String[] args) {
        IHouse iHouse = new HouseProxy(new RealHouse());
        iHouse.rent();
        iHouse.sell();
    }
}

执行结果
开始代理
房东出租房子
结束代理
开始代理
房东售卖房子
结束代理

3.3 动态代理

AOP的底层原理依赖于动态代理:不需要针对每一个目标对象创建一个代理对象,而是将代理对象的创建时机推迟到程序运行时交由JVM完成

3.3.1 JDK动态代理

JDK动态代理是Java标准库提供的方式,要求目标对象必须实现接口。通过java.lang.reflect.Proxy类java.lang.reflect.InvocationHandler接口动态生成代理类,生成的代理对象和目标对象实现自同一接口(而非与目标类本身有直接继承关系),与上述静态代理类似,但把代理对象的生成时机推迟到程序运行时

Proxy.newProxyInstance()是Java动态代理的核心方法,用于在运行时创建代理对象。该方法接收三个参数:

  • ClassLoader loader:用于加载代理类的类加载器。通常传入目标类的类加载器,确保代理类与目标类在同一个类加载器环境中
    • 代理对象需要实现目标接口,其字节码由Proxy工具类动态生成。通过目标类加载器创建代理类,可保证类型系统的一致性
  • Class<?>[] interfaces:目标对象实现的接口数组。代理类会实现这些接口,并将方法调用转发到InvocationHandler
  • InvocationHandler h:负责处理代理对象的方法调用
@Slf4j
public class JDKInvocationHandler implements InvocationHandler {

    private final Object realHouse;
    public JDKInvocationHandler(Object realHouse) {
        this.realHouse = realHouse;
    }

    @Override
    //Object proxy:生成的代理对象实例,Method method:被调用的目标方法对象,Object[] args:调用目标方法时传入的参数数组
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("JDK开始代理");
        //调用realHouse的方法
        Object invoke = method.invoke(realHouse, args);
        log.info("JDK结束代理,invoke:{}", invoke);
        return invoke;
    }
}
public class Main {
    public static void main(String[] args) {
        IHouse target = new RealHouse();
        //JDK动态代理,只能代理接口
        IHouse iHouse = (IHouse)Proxy.newProxyInstance(
                RealHouse.class.getClassLoader(),
                new Class[]{IHouse.class},
                new JDKInvocationHandler(target)
        );
        iHouse.rent();
        iHouse.sell();
    }
}

运行结果

3.3.2 CGLIB动态代理

CGLIB(Code Generation Library)是第三方库,基于字节码增强(动态生成目标类的子类字节码,重写方法逻辑),通过继承方式实现代理,不要求目标对象实现接口。通过org.springframework.cglib.proxy.Enhancer类动态生成目标类的子类作为代理

//interface MethodInterceptor extends Callback
@Slf4j
public class CGLibMethodInterceptor implements MethodInterceptor {

    private final Object realHouse;

    public CGLibMethodInterceptor(Object realHouse) {
        this.realHouse = realHouse;
    }

    @Override
    //Object obj:动态生成的代理对象实例,Method method:当前被拦截的目标方法对象,Object[] args:方法调用时传入的参数数组,MethodProxy proxy:方法代理对象
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        log.info("开始代理");
        IHouse result = (IHouse)method.invoke(realHouse, args);
        log.info("结束代理");
        return result;
    }
}
public class Main {

    public static void main(String[] args) {
        RealHouse target = new RealHouse();
        RealHouse realHouse = (RealHouse)Enhancer.create(
                RealHouse.class,
                new CGLibMethodInterceptor(target)
        );
        realHouse.rent();
        realHouse.sell();
    }
}

执行结果

转载请说明出处内容投诉
AJ教程_站长资源网 » Java外功精要(5)——Spring AOP

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买