【Spring Framework】Spring AOP 常用案例

Spring AOP(Aspect-Oriented Programming)通过在代码中插入横切关注点来增强应用程序的功能,而无需修改业务逻辑代码。AOP 可以广泛应用于日志记录、事务管理、权限控制、性能监控等方面。下面是一些 Spring AOP 的常用案例,以及详细的代码实现和讲解。

1. 日志记录

日志记录是 AOP 的一个经典应用场景。通过 AOP 可以在方法调用之前和之后插入日志,记录方法的执行时间、输入参数和返回结果。

1.1 实现日志记录切面

代码示例
package com.example.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Aspect
@Component
public class LoggingAspect {

    // 定义切点,匹配com.example.service包及其子包下的所有方法
    @Pointcut("execution(* com.example.service..*(..))")
    public void serviceLayer() {}

    // 前置通知,在目标方法执行之前执行
    @Before("serviceLayer()")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Executing method: " + joinPoint.getSignature());
        System.out.println("Method arguments: " + Arrays.toString(joinPoint.getArgs()));
    }

    // 后置通知,在目标方法执行之后执行
    @After("serviceLayer()")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("Completed method: " + joinPoint.getSignature());
    }

    // 环绕通知,在目标方法执行之前和之后执行
    @Around("serviceLayer()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        System.out.println("Start execution of method: " + joinPoint.getSignature());
        Object result = joinPoint.proceed();  // 执行目标方法
        long endTime = System.currentTimeMillis();
        System.out.println("End execution of method: " + joinPoint.getSignature());
        System.out.println("Execution time: " + (endTime - startTime) + "ms");
        return result;
    }

    // 返回通知,在目标方法正常返回后执行
    @AfterReturning(pointcut = "serviceLayer()", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        System.out.println("Method " + joinPoint.getSignature() + " returned: " + result);
    }

    // 异常通知,在目标方法抛出异常时执行
    @AfterThrowing(pointcut = "serviceLayer()", throwing = "error")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
        System.out.println("Method " + joinPoint.getSignature() + " threw: " + error);
    }
}
业务逻辑类示例
package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {

    public void addUser(String name) {
        System.out.println("Adding user: " + name);
    }

    public void deleteUser(String name) {
        System.out.println("Deleting user: " + name);
    }

    public String findUser(String name) {
        System.out.println("Finding user: " + name);
        return "User: " + name;
    }

    public void throwError() {
        throw new RuntimeException("Simulated error");
    }
}
测试程序
package com.example;

import com.example.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService = context.getBean(UserService.class);

        userService.addUser("Alice");
        userService.deleteUser("Bob");
        System.out.println(userService.findUser("Charlie"));

        try {
            userService.throwError();
        } catch (Exception e) {
            // handle exception
        }
    }
}
输出结果
Executing method: void com.example.service.UserService.addUser(String)
Method arguments: [Alice]
Start execution of method: void com.example.service.UserService.addUser(String)
Adding user: Alice
Completed method: void com.example.service.UserService.addUser(String)
End execution of method: void com.example.service.UserService.addUser(String)
Execution time: 5ms
Method void com.example.service.UserService.addUser(String) returned: null
Executing method: void com.example.service.UserService.deleteUser(String)
Method arguments: [Bob]
Start execution of method: void com.example.service.UserService.deleteUser(String)
Deleting user: Bob
Completed method: void com.example.service.UserService.deleteUser(String)
End execution of method: void com.example.service.UserService.deleteUser(String)
Execution time: 3ms
Method void com.example.service.UserService.deleteUser(String) returned: null
Executing method: String com.example.service.UserService.findUser(String)
Method arguments: [Charlie]
Start execution of method: String com.example.service.UserService.findUser(String)
Finding user: Charlie
Completed method: String com.example.service.UserService.findUser(String)
End execution of method: String com.example.service.UserService.findUser(String)
Execution time: 2ms
Method String com.example.service.UserService.findUser(String) returned: User: Charlie
User: Charlie
Executing method: void com.example.service.UserService.throwError()
Method arguments: []
Start execution of method: void com.example.service.UserService.throwError()
Completed method: void com.example.service.UserService.throwError()
Method void com.example.service.UserService.throwError() threw: java.lang.RuntimeException: Simulated error

1.2 解析

  1. 切点execution(* com.example.service..*(..)),表示匹配 com.example.service 包及其子包下的所有方法。

  2. 前置通知@Before 注解,用于在目标方法执行之前输出方法签名和参数。

  3. 后置通知@After 注解,用于在目标方法执行之后输出方法签名。

  4. 环绕通知@Around 注解,用于记录方法执行的开始时间和结束时间,并计算执行时间。

  5. 返回通知@AfterReturning 注解,用于在方法正常返回后输出返回值。

  6. 异常通知@AfterThrowing 注解,用于在方法抛出异常时输出异常信息。

2. 事务管理

事务管理是 Spring AOP 的另一个重要应用,可以通过 AOP 来确保方法的执行具有原子性、一致性、隔离性和持久性(ACID 属性)。

2.1 实现事务管理切面

代码示例
package com.example.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TransactionAspect {

    // 定义切点,匹配com.example.repository包及其子包下的所有方法
    @Pointcut("execution(* com.example.repository..*(..))")
    public void repositoryLayer() {}

    // 环绕通知,在目标方法执行之前和之后执行
    @Around("repositoryLayer()")
    public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Starting transaction for method: " + joinPoint.getSignature());
        Object result;
        try {
            // 执行目标方法
            result = joinPoint.proceed();
            System.out.println("Committing transaction for method: " + joinPoint.getSignature());
        } catch (Exception e) {
            System.out.println("Rolling back transaction for method: " + joinPoint.getSignature());
            throw e;
        }
        return result;
    }
}
业务逻辑类示例
package com.example.repository;

import org.springframework.stereotype.Repository;

@Repository
public class UserRepository {

    public void saveUser(String name) {
        System.out.println("Saving user: " + name);
        if (name == null) {
            throw new IllegalArgumentException("User name cannot be null");
        }
    }

    public void deleteUser(String name) {
        System.out.println("Deleting user: " + name);
    }
}
测试程序
package com.example;

import com.example.repository.UserRepository;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        UserRepository userRepository = context.getBean(UserRepository.class);

        try {
            userRepository.saveUser("Alice");
            userRepository.saveUser(null);
        } catch (Exception e) {
            System.out.println("Exception: " + e.getMessage());
        }

        userRepository.deleteUser("Bob");
    }
}
输出结果
Starting transaction for method: void com.example.repository.UserRepository.saveUser(String)
Saving user: Alice
Committing transaction for method: void com.example.repository.UserRepository.saveUser(String)
Starting transaction for method: void com.example.repository.UserRepository.saveUser(String)
Saving user: null
Rolling back transaction for method: void com.example.repository.UserRepository.saveUser(String)
Exception: User name cannot be null


Starting transaction for method: void com.example.repository.UserRepository.deleteUser(String)
Deleting user: Bob
Committing transaction for method: void com.example.repository.UserRepository.deleteUser(String)

2.2 解析

  1. 切点execution(* com.example.repository..*(..)),表示匹配 com.example.repository 包及其子包下的所有方法。

  2. 环绕通知:使用环绕通知来在目标方法执行之前开始事务,执行成功后提交事务,抛出异常时回滚事务。

3. 权限控制

权限控制可以通过 AOP 来实现,确保某些方法只能由具有特定权限的用户调用。

3.1 实现权限控制切面

代码示例
package com.example.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class SecurityAspect {

    // 定义切点,匹配com.example.service包及其子包下的所有方法
    @Pointcut("execution(* com.example.service..*(..))")
    public void serviceLayer() {}

    // 前置通知,在目标方法执行之前检查权限
    @Before("serviceLayer()")
    public void checkPermission(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().toShortString();
        if (!hasPermission()) {
            throw new SecurityException("You do not have permission to execute " + methodName);
        }
    }

    private boolean hasPermission() {
        // 这里可以实现更复杂的权限检查逻辑,例如检查用户角色、权限等
        return true; // 默认允许执行
    }
}
业务逻辑类示例
package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class AccountService {

    public void transferMoney(String fromAccount, String toAccount, double amount) {
        System.out.println("Transferring " + amount + " from " + fromAccount + " to " + toAccount);
    }

    public double getBalance(String account) {
        System.out.println("Getting balance for account: " + account);
        return 1000.0; // 示例返回值
    }
}
测试程序
package com.example;

import com.example.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        AccountService accountService = context.getBean(AccountService.class);

        try {
            accountService.transferMoney("Alice", "Bob", 500.0);
            System.out.println("Balance: " + accountService.getBalance("Alice"));
        } catch (SecurityException e) {
            System.out.println("Security Exception: " + e.getMessage());
        }
    }
}
输出结果
Transferring 500.0 from Alice to Bob
Getting balance for account: Alice
Balance: 1000.0

3.2 解析

  1. 切点execution(* com.example.service..*(..)),表示匹配 com.example.service 包及其子包下的所有方法。

  2. 前置通知:在目标方法执行之前检查权限,如果没有权限则抛出 SecurityException 异常。

4. 性能监控

性能监控可以通过 AOP 来实现,记录方法的执行时间,帮助识别性能瓶颈。

4.1 实现性能监控切面

代码示例
package com.example.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class PerformanceAspect {

    // 定义切点,匹配com.example.service包及其子包下的所有方法
    @Pointcut("execution(* com.example.service..*(..))")
    public void serviceLayer() {}

    // 环绕通知,在目标方法执行之前和之后记录执行时间
    @Around("serviceLayer()")
    public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed();  // 执行目标方法
        long endTime = System.currentTimeMillis();
        System.out.println("Execution time of " + joinPoint.getSignature() + ": " + (endTime - startTime) + "ms");
        return result;
    }
}
业务逻辑类示例
package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class ReportService {

    public void generateReport() {
        System.out.println("Generating report...");
        // 模拟耗时操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
测试程序
package com.example;

import com.example.service.ReportService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        ReportService reportService = context.getBean(ReportService.class);

        reportService.generateReport();
    }
}
输出结果
Generating report...
Execution time of void com.example.service.ReportService.generateReport(): 1001ms

4.2 解析

  1. 切点execution(* com.example.service..*(..)),表示匹配 com.example.service 包及其子包下的所有方法。

  2. 环绕通知:在目标方法执行之前和之后记录开始时间和结束时间,计算方法的执行时间。

5. 缓存管理

缓存管理可以通过 AOP 来实现,避免重复计算,提升性能。

5.1 实现缓存管理切面

代码示例
package com.example.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Aspect
@Component
public class CacheAspect {

    private final Map<String, Object> cache = new HashMap<>();

    // 定义切点,匹配com.example.service包及其子包下的所有方法
    @Pointcut("execution(* com.example.service..*(..))")
    public void serviceLayer() {}

    // 环绕通知,使用缓存管理
    @Around("serviceLayer()")
    public Object cacheMethodCall(ProceedingJoinPoint joinPoint) throws Throwable {
        String key = joinPoint.getSignature().toShortString() + "-" + Arrays.toString(joinPoint.getArgs());
        if (cache.containsKey(key)) {
            System.out.println("Returning cached result for: " + key);
            return cache.get(key);
        }

        System.out.println("Executing method: " + joinPoint.getSignature());
        Object result = joinPoint.proceed();  // 执行目标方法
        cache.put(key, result);
        return result;
    }
}
业务逻辑类示例
package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class CalculationService {

    public int fibonacci(int n) {
        if (n <= 1) {
            return n;
        }
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
}
测试程序
package com.example;

import com.example.service.CalculationService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        CalculationService calculationService = context.getBean(CalculationService.class);

        System.out.println("Fibonacci(5): " + calculationService.fibonacci(5));
        System.out.println("Fibonacci(5): " + calculationService.fibonacci(5)); // 使用缓存
    }
}
输出结果
Executing method: int com.example.service.CalculationService.fibonacci(int)
Fibonacci(5): 5
Returning cached result for: int com.example.service.CalculationService.fibonacci(int)-[5]
Fibonacci(5): 5

5.2 解析

  1. 切点execution(* com.example.service..*(..)),表示匹配 com.example.service 包及其子包下的所有方法。

  2. 环绕通知:在方法调用之前检查缓存,如果存在缓存结果则直接返回,否则执行方法并缓存结果。

6. 访问控制

访问控制可以通过 AOP 来实现,限制某些方法只能由具有特定权限的用户调用。

6.1 实现访问控制切面

代码示例
package com.example.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AccessControlAspect {

    // 定义切点,匹配com.example.service包及其子包下的所有方法
    @Pointcut("execution(* com.example

.service..*(..))")
    public void serviceLayer() {}

    // 前置通知,在目标方法执行之前检查权限
    @Before("serviceLayer()")
    public void checkAccess(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().toShortString();
        if (!hasAccess()) {
            throw new SecurityException("Access denied to execute " + methodName);
        }
    }

    private boolean hasAccess() {
        // 这里可以实现更复杂的权限检查逻辑,例如检查用户角色、权限等
        return true; // 默认允许访问
    }
}
业务逻辑类示例
package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class PaymentService {

    public void processPayment(String account, double amount) {
        System.out.println("Processing payment of " + amount + " for account: " + account);
    }

    public void refundPayment(String account, double amount) {
        System.out.println("Refunding payment of " + amount + " for account: " + account);
    }
}
测试程序
package com.example;

import com.example.service.PaymentService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        PaymentService paymentService = context.getBean(PaymentService.class);

        try {
            paymentService.processPayment("Alice", 100.0);
            paymentService.refundPayment("Bob", 50.0);
        } catch (SecurityException e) {
            System.out.println("Security Exception: " + e.getMessage());
        }
    }
}
输出结果
Processing payment of 100.0 for account: Alice
Refunding payment of 50.0 for account: Bob

6.2 解析

  1. 切点execution(* com.example.service..*(..)),表示匹配 com.example.service 包及其子包下的所有方法。

  2. 前置通知:在目标方法执行之前检查访问权限,如果没有权限则抛出 SecurityException 异常。

7. 数据验证

数据验证可以通过 AOP 来实现,在方法执行之前验证输入参数的合法性。

7.1 实现数据验证切面

代码示例
package com.example.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class ValidationAspect {

    // 定义切点,匹配com.example.service包及其子包下的所有方法
    @Pointcut("execution(* com.example.service..*(..))")
    public void serviceLayer() {}

    // 前置通知,在目标方法执行之前验证输入参数
    @Before("serviceLayer()")
    public void validateArgs(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            if (arg == null) {
                throw new IllegalArgumentException("Null argument passed to method: " + joinPoint.getSignature());
            }
        }
    }
}
业务逻辑类示例
package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class ProductService {

    public void addProduct(String name, double price) {
        System.out.println("Adding product: " + name + " with price: " + price);
    }

    public void removeProduct(String name) {
        System.out.println("Removing product: " + name);
    }
}
测试程序
package com.example;

import com.example.service.ProductService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        ProductService productService = context.getBean(ProductService.class);

        try {
            productService.addProduct("Laptop", 1200.0);
            productService.addProduct(null, 1500.0); // 测试非法输入
        } catch (IllegalArgumentException e) {
            System.out.println("Illegal Argument Exception: " + e.getMessage());
        }

        productService.removeProduct("Laptop");
    }
}
输出结果
Adding product: Laptop with price: 1200.0
Illegal Argument Exception: Null argument passed to method: void com.example.service.ProductService.addProduct(String, double)
Removing product: Laptop

7.2 解析

  1. 切点execution(* com.example.service..*(..)),表示匹配 com.example.service 包及其子包下的所有方法。

  2. 前置通知:在目标方法执行之前验证输入参数是否合法,如果参数为 null 则抛出 IllegalArgumentException 异常。

8. 应用程序配置

通过 AOP,可以在运行时动态配置应用程序参数。

8.1 实现应用程序配置切面

代码示例
package com.example.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class ConfigurationAspect {

    // 定义切点,匹配com.example.service包及其子包下的所有方法
    @Pointcut("execution(* com.example.service..*(..))")
    public void serviceLayer() {}

    // 前置通知,在目标方法执行之前配置应用程序参数
    @Before("serviceLayer()")
    public void configureApp() {
        System.out.println("Configuring application parameters...");
        // 这里可以实现复杂的配置逻辑,例如从文件、数据库中加载配置
    }
}
业务逻辑类示例
package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class ConfigService {

    public void loadConfig() {
        System.out.println("Loading application configuration...");
    }
}
测试程序
package com.example;

import com.example.service.ConfigService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        ConfigService configService = context.getBean(ConfigService.class);

        configService.loadConfig();
    }
}
输出结果
Configuring application parameters...
Loading application configuration...

8.2 解析

  1. 切点execution(* com.example.service..*(..)),表示匹配 com.example.service 包及其子包下的所有方法。

  2. 前置通知:在目标方法执行之前动态配置应用程序参数。

总结

通过这些常见的 Spring AOP 案例,我们可以看出 AOP 的强大和灵活性。Spring AOP 通过将横切关注点从业务逻辑中分离出来,实现了代码的模块化、解耦和可重用性,提升了应用程序的可维护性和扩展性。

这些案例展示了如何利用 AOP 解决日志记录、事务管理、权限控制、性能监控、缓存管理、访问控制、数据验证和应用程序配置等常见问题。在实际应用中,可以根据需要灵活调整和扩展这些功能,为应用程序带来更高效的开发体验和更出色的性能表现。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值