AOP面向切面编程

AOP面向切面编程

  • 如何理解面向切面编程
    • 举例理解:
    • 核心概念:
  • Spring AOP
    • 两种配置风格
      • 基于注解的`@AspectJ`风格
      • 基于XML的Schema风格
    • 代理机制
      • JDK动态代理
      • CGLIB代理
      • 编程方式创建代理
    • 配置AspectJ加载时织入

如何理解面向切面编程

面向切面编程(Aspect-Oriented Programming,简称AOP)是一种编程范式,它允许开发者将横切关注点(如日志记录、事务管理等)与业务逻辑分离,从而提高代码的模块化和可维护性。AOP 通过引入“切面”的概念来实现这一点,切面可以看作是封装了横切关注点的模块。
AOP面向切面编程_第1张图片

举例理解:

面向切面编程(AOP)的一个经典例子是日志记录。在一个应用程序中,你可能有多个服务层方法需要记录每次调用的日志。如果使用传统的面向对象编程(OOP),你需要在每个服务层方法中手动添加日志代码,这会导致代码重复和难以维护。

通过AOP,你可以定义一个日志切面,它将日志记录逻辑封装在一个单独的模块中。这个切面可以配置为在特定方法调用的前后自动执行日志记录,而不需要修改这些方法的代码。下面是一个简化的示例来说明这个过程:

  1. 定义一个日志切面:首先,你创建一个切面类,使用@Aspect注解标记它。
@Aspect
public class LoggingAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        // 获取被调用的方法名和参数
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        // 打印日志
        System.out.println("Before method: " + methodName + ", with args: " + Arrays.toString(args));
    }

    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        // 打印返回结果
        System.out.println("After method returned: " + result);
    }
}
  1. 配置Spring AOP:在你的Spring配置中,启用@AspectJ自动代理,以便Spring可以识别上面定义的切面,并自动将日志逻辑织入到应用程序中。
<aop:aspectj-autoproxy/>
  1. 业务逻辑实现:现在,你可以在服务层实现业务逻辑,而不需要关心日志记录的代码。
@Service
public class UserService {
    public User findUserById(Long id) {
        // 业务逻辑
        return userRepository.findById(id);
    }
}

在这个例子中,无论何时调用UserServicefindUserById方法,LoggingAspect中的日志记录逻辑都会自动执行,无需在UserService中添加任何日志代码。这样,日志记录就作为一个横切关注点被模块化了,符合AOP的原则。

核心概念:

  • 切面(Aspect):封装了横切关注点的模块,可以是事务管理、日志记录等。
  • 连接点(Joinpoint):程序执行过程中的特定点,如方法调用或异常处理。
  • 通知(Advice):在切面的连接点上执行的动作,包括前置(Before)、后置(After returning)、异常(After throwing)、最终(After finally)和环绕(Around)通知。
  • 切入点(Pointcut):用来匹配连接点的表达式,决定通知何时执行。
  • 引入(Introduction):允许为类添加新的方法或属性。
  • 目标对象(Target Object):被一个或多个切面所通知的对象。
  • AOP代理(AOP Proxy):AOP框架创建的对象,用于实现切面契约。
  • 织入(Weaving):将切面应用到目标对象上,创建被通知的对象。

Spring AOP

Spring框架中的AOP是一个重要的组成部分,它提供了声明式企业服务和自定义切面的支持。Spring AOP使用纯Java实现,不需要特殊的编译过程,适用于J2EE web容器或应用服务器。

两种配置风格

Spring AOP支持两种配置风格:基于注解的@AspectJ风格和基于XML的schema风格。@AspectJ风格使用Java 5的注解,可以声明切面为普通的Java类,而XML风格则使用aop命名空间来定义切面和通知。

基于注解的@AspectJ风格

首先,需要引入必要的Spring依赖,确保项目中有Spring AOP相关的库。

  1. 依赖配置(Maven)
<dependencies>
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-aopartifactId>
        <version>5.3.9version>
    dependency>
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-contextartifactId>
        <version>5.3.9version>
    dependency>
dependencies>
  1. 定义切面类
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    // 定义一个切入点,匹配service包下的所有方法
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore() {
        System.out.println("A method in the service package is being called.");
    }
}
  1. 配置Spring上下文
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "com.example")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}
  1. 使用注解配置的Spring AOP
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        MyService service = context.getBean(MyService.class);
        service.doSomething();
    }
}

@Component
public class MyService {
    public void doSomething() {
        System.out.println("Executing service method...");
    }
}

基于XML的Schema风格

  1. Spring AOP配置(XML)
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd">

    
    <aop:config>
        <aop:aspect ref="loggingAspect">
            <aop:pointcut id="serviceMethods" expression="execution(* com.example.service.*.*(..))"/>
            <aop:before pointcut-ref="serviceMethods" method="logBefore"/>
        aop:aspect>
    aop:config>

    
    <bean id="loggingAspect" class="com.example.aspect.LoggingAspect"/>

    
    <bean id="myService" class="com.example.service.MyService"/>
beans>
  1. 使用XML配置的Spring AOP
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        MyService service = context.getBean(MyService.class);
        service.doSomething();
    }
}

代理机制

Spring AOP的代理机制可以是基于JDK动态代理或CGLIB代理。如果目标对象实现了至少一个接口,则使用JDK动态代理;如果没有实现任何接口,则使用CGLIB代理。

JDK动态代理

使用JDK动态代理(假设MyService实现了一个接口)

import org.springframework.aop.framework.ProxyFactory;

public class Main {
    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new MyServiceImpl());
        factory.addAdvice(new LoggingAdvice());
        MyService proxy = (MyService) factory.getProxy();
        proxy.doSomething();
    }
}

CGLIB代理

使用CGLIB代理(MyService不实现任何接口)

import org.springframework.aop.framework.ProxyFactory;

public class Main {
    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new MyService());
        factory.setProxyTargetClass(true);  // 强制使用CGLIB代理
        factory.addAdvice(new LoggingAdvice());
        MyService proxy = (MyService) factory.getProxy();
        proxy.doSomething();
    }
}

编程方式创建代理

此外,Spring AOP还支持以编程方式创建代理,可以使用AspectJProxyFactory类来为一个或多个@AspectJ切面通知的目标对象创建代理。

  • 使用AspectJProxyFactory
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;

public class Main {
    public static void main(String[] args) {
        MyService target = new MyService();
        AspectJProxyFactory factory = new AspectJProxyFactory(target);
        factory.addAspect(LoggingAspect.class);
        MyService proxy = factory.getProxy();
        proxy.doSomething();
    }
}

配置AspectJ加载时织入

最后,Spring框架还支持使用AspectJ的加载时织入(LTW),这允许在虚拟机载入字节码文件时动态织入AspectJ切面,提供了更细粒度的控制。

META-INF/aop.xml中配置:

<aspectj>
    <weaver>
        <include within="com.example..*"/>
    weaver>
    <aspects>
        <aspect name="com.example.aspect.LoggingAspect"/>
    aspects>
aspectj>

在Spring配置中启用LTW:

<beans>
    <context:load-time-weaver/>
beans>

还在学习中,内容有误请联系作者,本内容仅供各位大佬了解与讨论。

你可能感兴趣的:(java,spring,java,后端)