自学内容网 自学内容网

Spring AOP

目录

前言

一、AOP 概述

二、Spring AOP 介绍

1. 引入依赖

2. 编写切面类

1. 切面类

2. 通知类型

3. 切点

4. 切面优先级

5. 切点表达式

三、Spring AOP 原理

1. 代理模式

2. 静态代理

3. 动态代理

1. JDK 动态代理

2. CGLIB 代理

3. 代理工厂


前言

Spring AOP是一种面向切面编程的实现,通过切面(Aspect)对特定方法进行统一处理,如日志、事务管理等。AOP的核心包括切点(Pointcut)和通知(Advice),支持多种通知类型(@Around、@Before等)。Spring AOP基于动态代理实现,包括JDK动态代理和CGLIB代理。AOP的优势在于减少冗余代码,增强功能而不侵入原有逻辑。


一、AOP 概述

Spring 框架的两大核心:IoC 和 AOP;

AOP 是 Aspect Oriented Programming(面向切面编程);

切面指的是一类特定的问题,比如使用拦截器实现网站用户的强制登录,统一数据返回格式,统一异常处理等,都是 AOP 思想的应用;

AOP 是一种思想,用于对某一类事情进行集中处理;这种思想的方法有多种实现,Spring AOP 是其中一种实现,初次之外还有 AspectJ,CGLIB等;

AOP 的优势:

减少了冗余代码,提高了效率;可以在不侵入原有方法的基础上,对原有功能实现增强;

例如:针对某个复杂的接口,记录接口中每个方法的运行时间:

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class RuntimeAspect {
    /**
     * 记录方法耗时
     */
    @Around("execution(* com.example.demo.controller.*.*(..))")
    public Object getRuntime(ProceedingJoinPoint joinPoint) throws Throwable {
        // 1. 方法执行前
        long start = System.currentTimeMillis();
        // 2. 方法执行
        Object object = joinPoint.proceed();
        // 3. 方法执行后
        long end = System.currentTimeMillis();
        log.info("方法运行时间:{}ms", end - start);

        return object;
    }
}

并没有修改原有的代码,就能实现统计所有接口运行的时间;

二、Spring AOP 介绍

1. 引入依赖

        <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2. 编写切面类

1. 切面类

切面(Aspect) = 切点(Pointcut) + 通知(Advice);

通过切面可以描述 AOP 程序,需要针对哪些方法,在什么时候执行什么操作;

切面所在的类就是切面类,也是被 @Aspect 注解修饰的类;

切面中定义的方法就是一个切面,方法体是通知,通知注解中的切点表达式是切点;

切面类示例:

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class AspectDemo1 {

    @Around("execution(* com.example.aop.controller.*.*(..))")
    public Object doAround(ProceedingJoinPoint joinPoint){
        log.info("do around before...");
        Object object;
        try {
           object  = joinPoint.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }

        log.info("do around after");
        return object;
    }
}

2. 通知类型

@Around 注解,表示环绕通知,在目标方法(ProceedingJointPoint.proceed())执行前和执行后,都会执行,对原有方法增强;

ProceedingJointPoint.proceed():表示执行原始方法;

环绕通知的返回值必须是 Object 类型,否则目标方法执行完,接收不到返回值;

@Before 注解,表示前置通知,目标方法执行前被执行;

@After 注解:表示后置通知,目标方法执行后被执行,无论是否有异常,都会被执行;

@AfterReturning:返回后通知,目标方法执行后被执行,有异常不会被执行;

@AfterThrowing:异常后通知,发生异常后执行;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class AspectDemo1 {

    @Before("execution(* com.example.aop.controller.*.*(..))")
    public void doBefore(){
        log.info("do before...");
    }

    @Around("execution(* com.example.aop.controller.*.*(..))")
    public Object doAround(ProceedingJoinPoint joinPoint){
        log.info("do around before...");
        Object object;
        try {
           object  = joinPoint.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }

        log.info("do around after");
        return object;
    }

    @After("execution(* com.example.aop.controller.*.*(..))")
    public void doAfter(){
        log.info("do after...");
    }

    @AfterReturning("execution(* com.example.aop.controller.*.*(..))")
    public void doAfterReturning(){
        log.info("do after returning...");
    }

    @AfterThrowing("execution(* com.example.aop.controller.*.*(..))")
    public void doAfterThrowing(){
        log.info("do after throwing...");
    }
}

定义测试类:

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
    @RequestMapping("/t1")
    public String t1(){
        log.info("执行t1方法...");
        return "t1";
    }

    @RequestMapping("/t2")
    public Integer t2(){
        log.info("执行t2方法...");
        Integer n = 10 / 0;
        return 1;
    }
}

执行 t1:

执行 t2:

3. 切点

切点可以使用 @Pointcut 注解进行定义;

上述代码存在大量重复的切点表达式,可以单独定义一个公共的切点解决重复问题:

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class AspectDemo1 {

    @Pointcut("execution(* com.example.aop.controller.*.*(..))")
    public void pt(){}

    @Before("pt()")
    public void doBefore(){
        log.info("do before...");
    }

    @Around("pt()")
    public Object doAround(ProceedingJoinPoint joinPoint){
        log.info("do around before...");
        Object object;
        try {
           object  = joinPoint.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }

        log.info("do around after");
        return object;
    }

    @After("pt()")
    public void doAfter(){
        log.info("do after...");
    }

    @AfterReturning("pt()")
    public void doAfterReturning(){
        log.info("do after returning...");
    }

    @AfterThrowing("pt()")
    public void doAfterThrowing(){
        log.info("do after throwing...");
    }
}

public 修饰的切点也可以在其它的切面类中使用(注意写全限定类名加切点名):

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;


@Slf4j
@Aspect
@Component
public class AspectDemo2 {
    @Before("com.example.aop.aspect.AspectDemo1.pt()")
    public void doBefore(){
        log.info("aspect2 do before...");
    }

    @Around("com.example.aop.aspect.AspectDemo1.pt()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("aspect2 do around before...");

        Object object = joinPoint.proceed();

        log.info("aspect2 do around after...");
        return object;
    }

    @After("com.example.aop.aspect.AspectDemo1.pt()")
    public void doAfter(){
        log.info("aspect2 do after...");
    }
}

4. 切面优先级

不同切面的优先级可以使用 @Order 注解定义,括号里写整数,数值越大,优先级越低;

定义优先级示例:

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Order(4)
@Slf4j
@Aspect
@Component
public class AspectDemo1 {

    @Pointcut("execution(* com.example.aop.controller.*.*(..))")
    public void pt(){}

    @Before("pt()")
    public void doBefore(){
        log.info("do before...");
    }
}

运行测试类中的 t1 方法,测试上述两个切面的优先级:

AspectDemo1 的默认优先级更高;

更改 AspectDemo1 的优先级为 4,AspectDemo2 的优先级为 2,运行 t1 方法:

5. 切点表达式

使用切点表达式来描述切点,常用的有两种方式:

  • execution(...):根据方法签名来匹配;
  • @annotation(...):根据注解匹配;

execution 表达式:

execution(< 访问修饰符 > < 返回类型 > < 包名 . 类名 . ⽅法 ( ⽅法参数 )> < 异常 >)

以 execution(* com.example.aop.controller.*.*(..)) 为例:

* 表示通配符;

访问修饰限定符省略,返回类型为 * ,表示匹配所有返回类型;

匹配 com.example.aop.controller 这个包;

.* 表示匹配所有类;

.*(..) 表示匹配所有方法;

@annotation 注解:

execution 适用于定义有规则的切点,如果想定义多个无规则的切点,使用 @annotation 更合适;

实现步骤:

  • 1. 编写自定义注解;
  • 2. 使用 @annotation 表达式描述切点;
  • 3. 在连接点的方法上添加自定义注解;

编写自定义注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MyAspect {

}

@Retention 表示注解的生命周期:

  • RententionPolicy.SOURCE:表示注解存在于源代码阶段;
  • RententionPolicy.CLASS:表示注解存在于源代码和字节码文件阶段;
  • RententionPolicy.RUNTIME 表示注解存在于源代码,字节码和运行阶段;

@Target 表示注解的作用范围:

  • ElementType.TYPE:表示用于描述类,接口或者枚举类型;
  • ElementType.METHOD:表示用于描述方法;
  • ElementType.PARAMETER:用于描述参数;
  • ElementType.TYPE_USE:用于描述任意类型;

使用 @annotation 表达式描述切点:

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class MyAspectDemo {

    @Around("@annotation(com.example.aop.config.MyAspect)")
    public Object doAround(ProceedingJoinPoint joinPoint){
        log.info("my aspect do around before...");
        Object object = null;
        try {
            object = joinPoint.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
        log.info("my aspect do around after...");
        return object;
    }
}

@annotation(com.example.aop.config.MyAspect) 切点表达式定义的切点,只对 @MyAspect 生效;

测试:

import com.example.aop.config.MyAspect;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
    @MyAspect
    @RequestMapping("/t1")
    public String t1(){
        log.info("执行t1方法...");
        return "t1";
    }

    @RequestMapping("/t2")
    public Integer t2(){
        log.info("执行t2方法...");
        Integer n = 10 / 0;
        return 1;
    }
}
import com.example.aop.config.MyAspect;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
    @MyAspect
    @RequestMapping("/u1")
    public String u1(){
        log.info("执行u1方法...");
        return "u1";
    }

    @RequestMapping("/u2")
    public String u2(){
        log.info("执行u2方法...");
        Integer i = 10 / 0;
        return "u2";
    }
}

测试 t1 和 u1 方法:

测试 t2 方法:

三、Spring AOP 原理

Spring AOP 是基于动态代理实现的;

1. 代理模式

代理模式也叫做委托模式;

通过为某个对象提供一种代理以控制这个对象的访问;

通过提供一个代理类,调用目标对象的方法的时候,不直接调用,而是通过代理对象间接调用;

代理的作用:目标对象提供的功能不够,通过代理对原有功能进行增强;

代理模式的主要角色:

  • Subject:业务接口类;
  • RealSubject:业务实现类;
  • Proxy:代理类;

2. 静态代理

程序运行之前,代理类的 .class 文件已经存在了;

业务接口类:

package com.example.aop.proxy;

public interface HouseSubject {
    void rentHouse();

    void saleHouse();
}

业务实现类:

package com.example.aop.proxy;

public class RealHouseSubject implements HouseSubject{
    public void rentHouse(){
        System.out.println("我是房东,出租房屋");
    }

    public void saleHouse(){
        System.out.println("我是房东,出售房屋");
    }
}

代理类:

import com.example.aop.proxy.RealHouseSubject;

public class HouseProxy implements HouseSubject {
    // 静态代理
    private RealHouseSubject realHouseSubject;

    public HouseProxy(RealHouseSubject realHouseSubject) {
        this.realHouseSubject = realHouseSubject;
    }

    @Override
    public void rentHouse() {
        System.out.println("我是代理,开始代理");
        realHouseSubject.rentHouse();
        System.out.println("我是代理,结束代理");
    }

    @Override
    public void saleHouse() {
        System.out.println("我是代理,开始代理");
        realHouseSubject.saleHouse();
        System.out.println("我是代理,结束代理");
    }
}

测试:

package com.example.aop.proxy.staticc;

import com.example.aop.proxy.RealHouseSubject;

public class Main {
    public static void main(String[] args) {
        HouseProxy proxy = new HouseProxy(new RealHouseSubject());

        proxy.rentHouse();

        proxy.saleHouse();
    }
}

通过上面的例子可以看出,静态代理的方式不够灵活,假如业务实现类发生变化,代理类就需要发生变化,否则就没办法实现对新增业务的增强;

3. 动态代理

动态代理相对于静态代理,更加灵活;

不需要针对每一个目标对象都创建一个代理对象,而是把这个创建代理对象的时机推迟到程序运行时,由 JVM 来实现;程序在运行时,根据需要,动态创建;

1. JDK 动态代理

JDK 动态代理类,需要实现一个 InvocationHandler 接口,通过调用 invoke() 方法实现功能增强;

invoke() 方法类似Spring AOP 中通知(Advice)的实现方式,通过连接点,调用 invoke() 方法,将目标对象和参数作为参数传入,返回结果;

package com.example.aop.proxy.dynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class HouseInvocationHandler implements InvocationHandler {
    private Object target;

    public HouseInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("我是代理,开始代理");
        Object o = method.invoke(target, args);
        System.out.println("我是代理,结束代理");
        return o;
    }
}

测试:

创建代理对象的时候,通过 Proxy.newInstance() 方法创建;

分别传入类加载器,接口类型和代理类实例作为参数;

使用 JDK 代理,目标对象必须要实现接口,否则接口类型参数将不匹配,出现报错;

import com.example.aop.proxy.HouseSubject;
import com.example.aop.proxy.RealHouseSubject;
import org.springframework.cglib.proxy.Enhancer;

import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        HouseSubject houseSubject = new RealHouseSubject();

        // 使用 JDK 动态代理
        HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
                houseSubject.getClass().getClassLoader(),
                new Class[]{HouseSubject.class},
                new HouseInvocationHandler(houseSubject));

        proxy.rentHouse();

        proxy.saleHouse();
}

相比静态代理,业务发生变化后,也可以使用动态代理调用对象的业务,而不需要修改代理类;

2. CGLIB 代理

需要实现 MethodInterceptor 接口,重写 interceptor() 方法;

通过调用 invoke() 方法实现功能增强;

类似 Spring AOP 通知的实现,传入目标对象和参数,返回结果;

package com.example.aop.proxy.dynamic;

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CGLIBMethodInterceptor implements MethodInterceptor {
    private Object target;

    public CGLIBMethodInterceptor(Object target) {
        this.target = target;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("我是中介,开始代理");
        Object object = method.invoke(target, args);
        System.out.println("我是中介,结束代理");

        return object;
    }
}

测试:

创建代理对象的时候,通过Enhancer.create() 创建;

传入目标对象类型以及代理类实例作为参数;

package com.example.aop.proxy.dynamic;

import com.example.aop.proxy.HouseSubject;
import com.example.aop.proxy.RealHouseSubject;
import org.springframework.cglib.proxy.Enhancer;

import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        HouseSubject houseSubject = new RealHouseSubject();


        // 使用 CGLIB 动态代理 - 代理接口
        HouseSubject cglibProxy = (HouseSubject) Enhancer.create(
                houseSubject.getClass(), new CGLIBMethodInterceptor(houseSubject));

        cglibProxy.rentHouse();
        cglibProxy.saleHouse();

        // 使用 CGLIB 动态代理 - 代理实体类
        RealHouseSubject realHouseSubject = new RealHouseSubject();
        RealHouseSubject cglibProxy2 = (RealHouseSubject) Enhancer.create(
                realHouseSubject.getClass(), new CGLIBMethodInterceptor(realHouseSubject));

        cglibProxy2.rentHouse();
        cglibProxy2.saleHouse();
}

可以看到,CGLIB 代理可以代理实现接口的类,也可以代理不实现接口的类;

3. 代理工厂

Spring 源码中是通过工厂模式创建代理,代理工厂有一个重要的属性:proxyTargetClass,可以通过 application.yml 文件配置;

proxyTargetClass 为 false,如果业务实现类实现了接口,使用 JDK 动态代理,没实现接口,使用 CGLIB 代理;

proxyTargetClass 为 true,使用 CGLIB 代理;

Spring 默认 proxyTargetClass 是 false,默认使用 JDK 代理,当业务实现类没实现接口的情况,使用 CGLIB代理;

SpringBoot 从 2.x 开始,默认 proxyTargetClass 是 true,默认使用 CGLIB 代理;



原文地址:https://blog.csdn.net/weixin_41965831/article/details/152791140

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!