AOP面向切面编程

如何理解面向切面编程

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

举例理解:

面向切面编程(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.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.3.9</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.9</version>
    </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 -->
    <bean id="loggingAspect" class="com.example.aspect.LoggingAspect"/>

    <!-- 其他bean定义 -->
    <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>

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

  • 15
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的AOP面向切面编程的测试代码示例,使用Spring框架实现: 首先,创建一个切面类 `LoggingAspect`,用于定义切面逻辑: ```java import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class LoggingAspect { @Before("execution(* com.example.service.*.*(..))") public void beforeAdvice(JoinPoint joinPoint) { System.out.println("Before method: " + joinPoint.getSignature().getName()); } @After("execution(* com.example.service.*.*(..))") public void afterAdvice(JoinPoint joinPoint) { System.out.println("After method: " + joinPoint.getSignature().getName()); } } ``` 然后,创建一个测试服务类 `UserService`,用于演示AOP的应用: ```java import org.springframework.stereotype.Service; @Service public class UserService { public void createUser(String username) { System.out.println("Creating user: " + username); } public void deleteUser(String username) { System.out.println("Deleting user: " + username); } } ``` 最后,创建一个Spring Boot应用程序,并在启动类中进行配置: ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.EnableAspectJAutoProxy; @SpringBootApplication @EnableAspectJAutoProxy public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); UserService userService = context.getBean(UserService.class); userService.createUser("John"); userService.deleteUser("John"); } } ``` 在上述示例中,`LoggingAspect` 切面类使用 `@Before` 和 `@After` 注解分别定义了在目标方法执行前和执行后的逻辑。切面逻辑会应用于 `UserService` 类中的所有方法。 当运行应用程序时,可以看到切面逻辑在方法执行前和执行后打印了相应的日志消息。 这是一个简单的AOP面向切面编程的示例,你可以根据实际需求进行更复杂的切面逻辑定义和应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值