Spring AOP概念及原理解析

 更新时间:2024年07月31日 08:58:08   作者:Si_wuxie  
这篇文章主要介绍了Spring AOP概念及原理 ,通过使用 Spring AOP 实现日志管理,我们可以将日志记录的逻辑从业务逻辑中分离出来,简化了代码的维护,需要的朋友可以参考下

Spring AOP(面向切面编程)

以下内容由ChatGPT生成

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在通过分离关注点来提高程序的模块化。Spring AOP 主要用于横切关注点(如日志记录、安全、事务管理等)的实现。在 Spring 中,AOP 的主要功能是为 Bean 增强功能,如添加额外的行为。

1. 静态代理与动态代理

静态代理动态代理是实现 AOP 的两种主要方式。

静态代理

  • 在编译时就已经知道代理的目标类,代理类在代码中显式地定义。
  • 静态代理的缺点是需要为每个代理的类手动编写代理类,导致代码冗余且难以维护。

动态代理

  • 动态代理是在运行时生成代理类的,Java 中有两种实现动态代理的方式:JDK 动态代理和 CGLIB
  • 动态代理的优点是可以为任意接口生成代理,不需要手动编写代理类。

JDK 动态代理

  • JDK 动态代理只代理实现了接口的类。它通过 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口实现。
  • InvocationHandler 接口中定义了 invoke 方法,当代理对象调用方法时,会执行该方法。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkProxyExample {
public static void main(String[] args) {
Foo foo = new FooImpl();
Foo proxyFoo = (Foo) Proxy.newProxyInstance(
Foo.class.getClassLoader(),
new Class<?>[]{Foo.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强
System.out.println("Before method: " + method.getName());
Object result = method.invoke(foo, args);
// 后置增强
System.out.println("After method: " + method.getName());
return result;
}
});
proxyFoo.doSomething();
}
}
interface Foo {
void doSomething();
}
class FooImpl implements Foo {
public void doSomething() {
System.out.println("Doing something...");
}
}

CGLIB 动态代理

  • CGLIB 动态代理通过生成目标类的子类来实现代理,因此可以代理没有接口的类。CGLIB 使用 ASM 字节码操作库来生成代理类。
  • CGLIB 的代理类重写目标类的方法,通过调用父类的 super 方法来实现对目标方法的调用。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxyExample {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Foo.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 前置增强
System.out.println("Before method: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
// 后置增强
System.out.println("After method: " + method.getName());
return result;
}
});
Foo fooProxy = (Foo) enhancer.create();
fooProxy.doSomething();
}
}
class Foo {
public void doSomething() {
System.out.println("Doing something...");
}
}

2. Spring AOP 实现原理

Spring AOP 支持 JDK 动态代理和 CGLIB 两种代理机制。

  • JDK 动态代理:当目标类实现了一个或多个接口时,Spring 默认使用 JDK 动态代理来为目标类创建代理对象。
  • CGLIB:如果目标类没有实现任何接口,Spring 则会使用 CGLIB 来生成目标类的代理对象。

Spring 使用 AopProxy 接口和其两个实现类 JdkDynamicAopProxy 和 CglibAopProxy 来分别处理这两种代理机制。

Bean 被包装成 Proxy

  • Spring 容器启动时,解析配置文件或注解,生成 Bean 定义信息。
  • 在 Bean 初始化后,Spring AOP 的 BeanPostProcessor 之一(如 AbstractAutoProxyCreator 的子类)会检查该 Bean 是否需要 AOP 增强。
  • 如果需要增强,则会生成一个代理对象,替换掉原始的 Bean。这一过程是通过调用 getProxy() 方法来完成的。

创建 Proxy 对象

AopProxy 接口定义了 getProxy() 方法:

  • JdkDynamicAopProxy:通过 JDK 动态代理的 Proxy.newProxyInstance() 方法创建代理对象。
  • CglibAopProxy:通过 CGLIB 的 Enhancer 类创建代理对象。

获取代理对象

getProxy() 方法返回代理对象。代理对象的创建是在调用 getProxy() 方法时动态生成的,并且在这个方法中处理了所有的 AOP 增强逻辑。

InvocationHandler 的实现

在 JDK 动态代理中,InvocationHandler 的 invoke() 方法包含了拦截器链的逻辑。CglibAopProxy 通过 Callback 和 MethodInterceptor 实现类似的功能。

public class MyInvocationHandler implements InvocationHandler {
private final Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置处理
System.out.println("Before method: " + method.getName());
// 调用目标对象的方法
Object result = method.invoke(target, args);
// 后置处理
System.out.println("After method: " + method.getName());
return result;
}
}

在 invoke() 方法中:

  • 执行前置增强逻辑。
  • 使用反射调用目标对象的方法。
  • 执行后置增强逻辑。

3. 拦截器链与方法链的执行

Spring AOP 中的拦截器链是由 Advisor 和 MethodInterceptor 组成的。Advisor 包含切点(Pointcut)和通知(Advice),切点定义了哪些方法需要拦截,通知则定义了拦截时执行的逻辑。

在代理对象调用方法时:

  • AopProxy 调用链会依次调用拦截器链中的拦截器。
  • 拦截器链通过责任链模式处理每一个拦截器。
  • 如果拦截器链中的某个拦截器决定执行目标方法,则会调用 MethodInvocation.proceed() 方法。
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 前置处理
System.out.println("Before method: " + invocation.getMethod().getName());
// 调用目标对象的方法
Object result = invocation.proceed();
// 后置处理
System.out.println("After method: " + invocation.getMethod().getName());
return result;
}
}

在 invoke() 方法中,proceed() 方法用于继续执行下一个拦截器或目标方法。如果没有其他拦截器,则执行目标方法。

总结

Spring AOP 使用代理模式实现横切关注点的管理,主要通过 JDK 动态代理和 CGLIB 动态代理实现。代理对象是通过 AopProxy 创建的,其中的 InvocationHandler 和 MethodInterceptor 负责执行拦截器链的逻辑。Spring AOP 提供了强大的功能来增强 Bean 的行为,使得切面逻辑与核心业务逻辑分离,提升了代码的模块化和可维护性。

以日志管理为例,Spring项目中如何使用AOP?

在 Spring 项目中使用 AOP 实现日志管理是一种常见的实践。通过 AOP,我们可以将日志记录等横切关注点与业务逻辑分离,使代码更加清晰、模块化。下面是一个使用 Spring AOP 实现日志管理的示例,包括如何定义切面、配置切面以及如何在应用中使用它。

1. 引入依赖

首先,需要在项目中引入 Spring AOP 相关的依赖。如果使用的是 Maven,可以在 pom.xml 中添加以下依赖:

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

2. 定义切面(Aspect)

切面是包含横切逻辑的类。在日志管理的示例中,我们将创建一个切面类来拦截方法调用并记录日志。

package com.example.logging;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.JoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
    // 定义切点,拦截所有com.example.service包及其子包下的所有方法
    @Pointcut("execution(* com.example.service..*(..))")
    public void serviceMethods() {}
    // 方法执行之前调用
    @Before("serviceMethods()")
    public void logBefore(JoinPoint joinPoint) {
        logger.info("Before method: " + joinPoint.getSignature().getName() + " - Arguments: " + joinPoint.getArgs());
    }
    // 方法正常返回之后调用
    @AfterReturning(pointcut = "serviceMethods()", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        logger.info("After method: " + joinPoint.getSignature().getName() + " - Result: " + result);
    }
    // 方法抛出异常时调用
    @AfterThrowing(pointcut = "serviceMethods()", throwing = "error")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
        logger.error("Exception in method: " + joinPoint.getSignature().getName() + " - Exception: " + error);
    }
}

3. 解释切面中的注解和方法

  • @Aspect: 标注该类为一个切面类。
  • @Component: 将该切面类作为 Spring 的 Bean 进行管理。
  • @Pointcut: 定义一个切点,execution(* com.example.service..*(..)) 表示匹配 com.example.service 包及其子包下的所有方法。
  • @Before: 表示在目标方法执行之前执行 logBefore() 方法。
  • @AfterReturning: 表示在目标方法正常返回之后执行 logAfterReturning() 方法,其中 returning 属性指定了返回值的变量名。
  • @AfterThrowing: 表示在目标方法抛出异常时执行 logAfterThrowing() 方法,其中 throwing 属性指定了异常的变量名。

4. 配置切面扫描

确保 Spring 能够扫描到定义的切面类。可以在主配置类(通常是启动类)中添加 @EnableAspectJAutoProxy 注解,启用 AOP 功能:

package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication
@EnableAspectJAutoProxy
public class MyApp {
    public static void main(String[] args) {
        SpringApplication.run(MyApp.class, args);
    }
}

5. 使用日志管理的示例

假设有一个服务类 UserService,日志切面将记录其方法调用:

package com.example.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
    public String getUserInfo(String userId) {
        // 模拟获取用户信息的操作
        return "User info for " + userId;
    }
    public void createUser(String userId, String name) {
        // 模拟创建用户的操作
        System.out.println("User created: " + userId + ", " + name);
    }
}

在 UserService 的方法调用之前、正常返回之后,以及抛出异常时,日志切面将分别记录相关信息。

6. 总结

通过使用 Spring AOP 实现日志管理,我们可以将日志记录的逻辑从业务逻辑中分离出来,简化了代码的维护。Spring AOP 提供了一种强大的方式来处理横切关注点,使得业务逻辑更为简洁和集中。

到此这篇关于Spring AOP概念及原理解析的文章就介绍到这了,更多相关Spring AOP原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java连接数据库JDBC技术之prepareStatement的详细介绍

    Java连接数据库JDBC技术之prepareStatement的详细介绍

    这篇文章主要介绍了Java连接数据库JDBC技术之prepareStatement的详细介绍,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • 超全面的SpringBoot面试题含答案

    超全面的SpringBoot面试题含答案

    这篇文章主要收录了44道面试中经常被问的SpringBoot问题,不管你是正在求职的新手还是已经工作很久的高手,这篇关于SpringBoot的面试题总结一定会让你有新的理解,让我们一起来看看吧
    2023-03-03
  • mysql数据库忘记密码时如何修改

    mysql数据库忘记密码时如何修改

    本文主要介绍了mysql数据库忘记密码时如何修改的步骤方法,具有很好的参考价值,下面跟着小编一起来看下吧
    2017-02-02
  • spring-boot-starter-web更换默认Tomcat容器的方法

    spring-boot-starter-web更换默认Tomcat容器的方法

    Spring Boot支持容器的自动配置,默认是Tomcat,当然我们也是可以进行修改的。下面小编给大家带来了spring-boot-starter-web更换默认Tomcat容器的方法,感兴趣的朋友跟随小编一起看看吧
    2019-04-04
  • Java多线程中的CountDownLatch详细解读

    Java多线程中的CountDownLatch详细解读

    这篇文章主要介绍了Java多线程中的CountDownLatch详细解读,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待,用给定的计数 初始化 CountDownLatch,需要的朋友可以参考下
    2023-11-11
  • Java面向对象程序设计多态性示例

    Java面向对象程序设计多态性示例

    这篇文章主要介绍了Java面向对象程序设计多态性,结合实例形式分析了java多态性的概念、原理、定义与使用方法及相关注意事项,需要的朋友可以参考下
    2018-03-03
  • Java的Synchronized关键字学习指南(全面 & 详细)

    Java的Synchronized关键字学习指南(全面 & 详细)

    这篇文章主要给大家介绍了关于Java的Synchronized关键字的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • 关于Java中反射的作用义及使用方法

    关于Java中反射的作用义及使用方法

    这篇文章主要介绍了关于Java中反射的作用义及使用方法,反射使得程序可以在运行时对类进行检查和操作,而不需要在编译时知道类的完整信息,需要的朋友可以参考下
    2023-07-07
  • springboot如何接收application/x-www-form-urlencoded类型的请求

    springboot如何接收application/x-www-form-urlencoded类型的请求

    这篇文章主要介绍了springboot如何接收application/x-www-form-urlencoded类型的请求,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • MyBatis实现留言板的示例代码

    MyBatis实现留言板的示例代码

    本文主要介绍了MyBatis实现留言板的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-08-08

最新评论