Spring AOP

AOP

说说什么是 AOP

AOP,也就是 Aspect-oriented Programming,译为面向切面编程。

简单点说,就是把一些业务逻辑中的相同代码抽取到一个独立的模块中,让业务逻辑更加清爽。

三分恶面渣逆袭:横向抽取

三分

举个例子,假如我们现在需要在业务代码开始前进行参数校验,在结束后打印日志,该怎么办呢?

我们可以把日志记录数据校验这两个功能抽取出来,形成一个切面,然后在业务代码中引入这个切面,这样就可以实现业务逻辑和通用逻辑的分离。

三分恶面渣逆袭:AOP应用示例

业务代码不再关心这些通用逻辑,只需要关心自己的业务实现,这样就实现了业务逻辑和通用逻辑的分离。

我们来回顾一下 Java 语言的执行过程:

三分恶面渣逆袭:Java 执行过程

AOP 的核心是动态代理,可以使用 JDK 动态代理来实现,也可以使用 CGLIB 来实现。

有哪些核心概念?
  • 切面(Aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象

  • 连接点(Join Point):被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring 中,连接点指的是被拦截到的方法,实际上连接点还可以是字段或者构造方法

  • 切点(Pointcut):对连接点进行拦截的定位

  • 通知(Advice):指拦截到连接点之后要执行的代码,也可以称作增强

  • 目标对象 (Target):代理的目标对象

  • 引介(introduction):一种特殊的增强,可以动态地为类添加一些属性和方法

  • 织入(Weabing):织入是将增强添加到目标类的具体连接点上的过程。可以分为 3 种类型的织入:

①、编译期织入:切面在目标类编译时被织入。

②、类加载期织入:切面在目标类加载到 JVM 时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。

③、运行期织入:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP 容器会为目标对象动态地创建一个代理对象。Spring AOP 就是以这种方式织入切面。

Spring 采用运行期织入,而 AspectJ 采用编译期织入和类加载器织入。

有哪些环绕方式?

AOP 一般有 5 种环绕方式:

  • 前置通知 (@Before)

  • 返回通知 (@AfterReturning)

  • 异常通知 (@AfterThrowing)

  • 后置通知 (@After)

  • 环绕通知 (@Around)

多个切面的情况下,可以通过 @Order 指定先后顺序,数字越小,优先级越高。

总结

总结一下:

AOP,也就是面向切面编程,是一种编程范式,旨在提高代码的模块化。比如说可以将日志记录、事务管理等分离出来,来提高代码的可重用性。

AOP 的核心概念包括切面(Aspect)、连接点(Join Point)、通知(Advice)、切点(Pointcut)和织入(Weaving)等。

① 像日志打印、事务管理等都可以抽离为切面,可以声明在类的方法上。

② 在 Spring AOP 中,连接点总是表示方法的执行。

③ Spring AOP 支持五种类型的通知:前置通知、后置通知、环绕通知、异常通知、最终通知等。

④ 在 AOP 中,切点用于指定我们想要在哪些连接点上执行通知的规则。

⑤ 织入是指将切面应用到目标对象并创建新的代理对象的过程。Spring AOP 默认在运行时通过动态代理方式实现织入。

@Transactional 注解,就是一个典型的 AOP 应用,它就是通过 AOP 来实现事务管理的。我们只需要在方法上添加 @Transactional 注解,Spring 就会在方法执行前后添加事务管理的逻辑。

使用 AOP 吗

利用 AOP 实现过日志记录,数据检验和时间记录。

下面是一个基本例子:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
​
@Aspect
public class LoggingAspect {
​
    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
​
    // 定义一个切入点,表示在哪些方法执行前插入切面代码
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethod() {}
​
    // 定义一个通知,在切入点方法执行前调用
    @Before("serviceMethod()")
    public void logBeforeServiceMethod(JoinPoint joinPoint) {
        logger.info("Executing method: " + joinPoint.getSignature().getName());
    }
}

说说 JDK 动态代理和 CGLIB 代理?

JDK动态代理

JDK动态代理是Java标准库提供的一种动态代理机制,它允许在运行时创建一个实现了一组给定接口的新代理类。这种代理类可以用来代理接口的实例,从而在不修改原始类的情况下,为对象的方法调用添加额外的处理逻辑。

JDK动态代理的核心是java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。下面是JDK动态代理的基本使用步骤:

  1. 定义接口: 首先,定义一个或多个接口,这些接口定义了代理类将要实现的方法。

  2. 实现InvocationHandler: 创建一个实现了InvocationHandler接口的类,这个类将负责处理代理类上的方法调用。在invoke方法中,可以添加额外的逻辑,比如日志记录、事务管理、性能监控等。

  3. 创建代理对象: 使用Proxy类的newProxyInstance方法创建代理对象。这个方法需要三个参数:类加载器、代理类要实现的接口数组、以及一个InvocationHandler实例。

  4. 调用代理对象的方法: 通过代理对象调用其方法时,实际上会调用InvocationHandlerinvoke方法,并将方法调用转发给原始对象(如果需要)。

// 步骤1: 定义接口
public interface Subject {
    void doSomething();
}
​
// 步骤2: 实现InvocationHandler
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
​
public class SubjectInvocationHandler implements InvocationHandler {
    private Object target; // 原始对象
​
    public SubjectInvocationHandler(Object target) {
        this.target = target;
    }
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method invocation");
        Object result = method.invoke(target, args); // 调用原始对象的方法
        System.out.println("After method invocation");
        return result;
    }
}
​
// 步骤3: 创建代理对象
import java.lang.reflect.Proxy;
​
public class Client {
    public static void main(String[] args) {
        // 创建原始对象
        Subject realSubject = new RealSubject();
​
        // 创建InvocationHandler
        InvocationHandler handler = new SubjectInvocationHandler(realSubject);
​
        // 创建代理对象  一般这个创建代理要写个方法
        Subject proxy = (Subject) Proxy.newProxyInstance(
            realSubject.getClass().getClassLoader(),
            realSubject.getClass().getInterfaces(),
            handler
        );
​
        // 调用代理对象的方法
        proxy.doSomething();
    }
}
​
// 步骤4: 调用代理对象的方法
class RealSubject implements Subject {
    @Override
    public void doSomething() {
        System.out.println("RealSubject doing something");
    }
}
cglib动态代理
  1. 添加CGLIB依赖: 首先,需要在项目中添加CGLIB的依赖。如果使用Maven,可以在pom.xml中添加如下依赖:

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>
  2. 创建MethodInterceptor: 创建一个实现了net.sf.cglib.proxy.MethodInterceptor接口的类,这个类将负责处理代理类上的方法调用。在intercept方法中,可以添加额外的逻辑,比如日志记录、事务管理、性能监控等。

  3. 创建Enhancer: 使用net.sf.cglib.proxy.Enhancer类来创建代理对象。设置目标类的类加载器、设置回调接口(即MethodInterceptor实例),并指定要代理的目标类。

  4. 创建代理对象: 调用Enhancercreate方法来创建代理对象。

  5. 调用代理对象的方法: 通过代理对象调用其方法时,实际上会调用MethodInterceptorintercept方法,并将方法调用转发给原始对象(如果需要)

简单例子:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
​
import java.lang.reflect.Method;
​
// 目标类
public class TargetClass {
    public void doSomething() {
        System.out.println("TargetClass doing something");
    }
}
​
// MethodInterceptor实现
public class TargetInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method invocation");
        Object result = proxy.invokeSuper(obj, args); // 调用原始对象的方法
        System.out.println("After method invocation");
        return result;
    }
}
​
// 客户端代码
public class Client {
    public static void main(String[] args) {
        // 创建Enhancer 一般创建代理要写个方法
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(TargetClass.class);
        enhancer.setCallback(new TargetInterceptor());
​
        // 创建代理对象
        TargetClass proxy = (TargetClass) enhancer.create();
​
        // 调用代理对象的方法
        proxy.doSomething();
    }
}

在这个示例中,TargetClass是目标类,它没有实现任何接口。TargetInterceptor实现了MethodInterceptor接口,并在intercept方法中添加了额外的逻辑。客户端代码创建了Enhancer实例,设置了目标类和回调接口,并创建了代理对象。当调用代理对象的doSomething()方法时,实际上会调用TargetInterceptorintercept方法,并在调用原始对象的方法前后添加了日志输出。

CGLIB动态代理在需要代理没有实现接口的类时非常有用,但它也有一些限制,比如不能代理final类和final方法。

选择 CGLIB 还是 JDK 动态代理?
  • 如果目标对象没有实现任何接口,则只能使用 CGLIB 代理。如果目标对象实现了接口,通常首选 JDK 动态代理。

  • 虽然 CGLIB 在代理类的生成过程中可能消耗更多资源,但在运行时具有较高的性能。对于性能敏感且代理对象创建频率不高的场景,可以考虑使用 CGLIB。

  • JDK 动态代理是 Java 原生支持的,不需要额外引入库。而 CGLIB 需要将 CGLIB 库作为依赖加入项目中

说说 Spring AOP 和 AspectJ AOP 区别?

Spring AOP和AspectJ对比

  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值