AOP在Java应用程序中的六个常见场景

目录

1.权限控制

1.1.定义权限注解

1.2.编写切面逻辑

1.3.在Spring配置文件中配置AOP

2.日志记录

3.缓存管理

4.事务管理

5.性能监控

6.异常处理

6.1.定义一个自定义注解

 6.2.定义一个切面类

 6.3.实现方式:

7.总结:


AOP(Aspect-Oriented Programming)是一种面向切面编程的技术,它通过切面的横切关注点的机制来解决面向对象编程(OOP)中的重复性问题。下面我们将讨论AOP在Java编程中的六个场景,以及如何使用AOP技术来优化代码。

1.权限控制

1.1.定义权限注解

首先需要定义一个注解,用于标记哪些方法需要进行权限控制。例如,可以定义一个名为@RequiresPermissions的注解,用于标记需要进行权限控制的方法。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiresPermissions {
    String[] value();
}

1.2.编写切面逻辑

 接下来,需要编写一个切面类,用于在方法执行前进行权限检查。切面类需要实现org.aspectj.lang.annotation.Aspect接口,并使用@Aspect注解进行标记。

@Aspect
@Component
public class PermissionAspect {
    @Autowired
    private AuthService authService;

    @Pointcut("@annotation(com.example.demo.annotation.RequiresPermissions)")
    public void permissionPointCut() {
    }

    @Before("permissionPointCut()")
    public void checkPermission(JoinPoint joinPoint) throws UnauthorizedException {
        // 从注解中获取需要进行权限控制的权限列表
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        RequiresPermissions requiresPermissions = signature.getMethod().getAnnotation(RequiresPermissions.class);
        String[] permissions = requiresPermissions.value();

        // 根据权限列表进行权限检查
        boolean hasPermission = authService.checkPermission(permissions);
        if (!hasPermission) {
            throw new UnauthorizedException("您没有访问该资源的权限!");
        }
    }
}

  在上面的代码中,使用@Pointcut注解定义了一个切点,用于匹配所有标记了@RequiresPermissions注解的方法。然后,在@Before通知中,通过authService.checkPermission()方法进行权限检查,如果权限不足,则抛出UnauthorizedException异常。

@Service
public class AuthService {
    @Autowired
    private UserDao userDao;
    @Autowired
    private RoleDao roleDao;

    /**
     * 检查用户是否具有访问指定权限的权限
     * @param permissions 需要进行权限控制的权限列表
     * @return 如果用户具有访问指定权限的权限,则返回true;否则返回false
     */
    public boolean checkPermission(String[] permissions) {
        // 从会话中获取当前用户的角色列表或权限列表
        User currentUser = userDao.getCurrentUser();
        List userRoles = roleDao.getRolesByUserId(currentUser.getId());
        List userPermissions = new ArrayList<>();
        for (Role role : userRoles) {
            userPermissions.addAll(role.getPermissions());
        }

        // 将权限列表转换为Set,方便进行比对
        Set userPermissionSet = new HashSet<>(userPermissions);
        Set requiredPermissionSet = new HashSet<>(Arrays.asList(permissions));

        // 进行权限比对
        return userPermissionSet.containsAll(requiredPermissionSet);
    }
}

在上面的代码中,首先从会话中获取当前用户的角色列表或权限列表。然后,将角色列表中的所有权限合并到一个列表中,并将该列表转换为Set。接下来,将需要进行权限控制的权限列表也转换为Set。最后,将两个Set进行比对,判断当前用户是否具有访问该资源的权限。

1.3.在Spring配置文件中配置AOP

最后,在Spring配置文件中配置AOP,以便让切面类生效。具体配置如下:



通过以上三个步骤,就可以实现基于AOP的权限控制。在需要进行权限控制的方法上,只需要加上@RequiresPermissions注解,并在注解中指定需要进行权限控制的权限列表,即可自动触发权限检查。

2.日志记录

在大多数应用程序中,日志记录是必不可少的。我们需要记录应用程序中的重要事件,以便在出现故障时进行故障排除。AOP可以帮助我们通过拦截器和通知来实现日志记录。我们可以创建一个拦截器,在每个方法调用前后记录日志。通过这种方式,我们可以轻松地记录应用程序中的所有事件,并轻松地进行故障排除。

@Component
@Aspect
@Slf4j
public class AspectProxy {

    private final ConcurrentHashMap qpsMap = new ConcurrentHashMap<>();

//    @Pointcut("execution(com.example.takeout.explain.QPSLog)")
    @Pointcut("@annotation(com.example.takeout.explain.QPSLog)")
    public void qpsPointcut() {
    }

    @Around("qpsPointcut()")
    public Object aroundQps(ProceedingJoinPoint joinPoint) throws Throwable {
        //打印执行时间
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = new Date();
        String time = simpleDateFormat.format(date);
        log.info("打印执行时间:{}", time);
        //获取参数名称,参数类型,参数值
        Object[] args = joinPoint.getArgs();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String[] parameterNames = signature.getParameterNames();
        for (int i = 0; i < args.length; i++) {
            Object arg = args[i];
            String parameterName = parameterNames[i];
            String parameterType = signature.getParameterTypes()[i].getSimpleName();
            log.info("参数名称: {} ,参数类型: {} ,参数值: {}", parameterName, parameterType, arg);
        }
        //获取接口路径
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String requestURI = request.getRequestURI();
        log.info("请求路径:{}", requestURI);
        String remoteHost = request.getRemoteHost();
        log.info("打印请求IP:{}", remoteHost);
        //获取接口名称,统计接口的执行时间
        String methodName = joinPoint.getSignature().toShortString();
        AtomicLong counter = qpsMap.computeIfAbsent(methodName, k -> new AtomicLong(0));
        long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long endTime = System.currentTimeMillis();
        long qps = counter.incrementAndGet();
        long costTime = endTime - startTime;
        log.info("{} QPS: {} 执行时间: {}ms", methodName, qps, costTime);
        return result;
    }
}

3.缓存管理

在许多应用程序中,缓存是一个关键的性能优化技术。通过缓存,我们可以避免频繁地从数据库或其他后端存储中读取数据。AOP可以帮助我们通过拦截器和通知来实现缓存管理。我们可以创建一个拦截器,在每个方法调用前检查缓存中是否存在所需的数据。如果缓存中存在数据,则直接返回缓存中的数据。如果缓存中不存在数据,则调用方法并将返回值存储在缓存中。通过这种方式,我们可以实现简单而有效的缓存管理,从而提高应用程序的性能。

@Aspect
public class CachingAspect {
    private Map cache = new ConcurrentHashMap<>();
    @Around("execution(* com.example.myapp.*.*(..))")
    public Object cacheResult(ProceedingJoinPoint joinPoint) throws Throwable {
        String key = joinPoint.getSignature().toLongString();
        if (cache.containsKey(key)) {
            return cache.get(key);
        } else {
            Object result = joinPoint.proceed();
            cache.put(key, result);
            return result;
        }
    }
}

在上述示例中,@Around注解表示在执行匹配execution(* com.example.myapp.*.*(..))的方法时执行缓存管理。ConcurrentHashMap类用于存储缓存数据,如果缓存中存在与joinPoint对应的数据,则直接返回缓存数据,否则通过调用joinPoint.proceed()方法执行业务逻辑,并将结果存储在缓存中。这样,下次再调用相同的方法时,可以直接返回缓存中的数据,而不必再执行业务逻辑,从而提高了应用程序的性能。 

4.事务管理

在许多应用程序中,事务管理是必不可少的。我们需要确保在执行多个相关操作时,如果其中任何一个操作失败,则所有操作都将回滚。AOP可以帮助我们通过拦截器和通知来实现事务管理。我们可以创建一个拦截器,在每个方法调用前开启一个事务,并在方法调用完成后提交或回滚事务。通过这种方式,我们可以确保在执行多个相关操作时,事务得到正确地管理。

@Aspect
public class TransactionAspect {
    @Around("execution(* com.example.myapp.*.*(..))")
    public Object transactional(ProceedingJoinPoint joinPoint) throws Throwable {
        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
        return transactionTemplate.execute(status -> {
            try {
                Object result = joinPoint.proceed();
                return result;
            } catch (Throwable t) {
                status.setRollbackOnly();
                throw t;
            }
        });
    }
}

在上述示例中,@Around注解表示在执行匹配execution(* com.example.myapp.*.*(..))的方法时执行事务管理。TransactionTemplate类用于创建一个事务模板,它可以将一个方法调用包装在事务中,并根据事务结果进行提交或回滚。在这个示例中,使用transactionManager对象作为事务管理器,并通过调用joinPoint.proceed()方法执行业务逻辑。如果发生异常,事务将回滚。 

5.性能监控

在许多应用程序中,性能监控是必不可少的。

@Aspect
public class MonitoringAspect {
    private final Timer timer = Metrics.timer("com.example.myapp.method.duration", "method", "", "status", "");
    @
@Around("execution(* com.example.myapp.*.*(..))")
public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
    Timer.Sample sample = Timer.start();
    try {
        Object result = joinPoint.proceed();
        return result;
    } finally {
        sample.stop(timer);
    }
}

6.异常处理

6.1.定义一个自定义注解

定义一个自定义注解 @GlobalExceptionHandler,用于标识需要进行全局异常处理的方法

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GlobalExceptionHandler {
}

 6.2.定义一个切面类

定义一个切面类 GlobalExceptionHandlerAspect,使用 @Around 注解拦截带有 @GlobalExceptionHandler 注解的方法,并进行异常处理

@Aspect
@Component
public class GlobalExceptionHandlerAspect {

    @Around("@annotation(com.example.demo.annotation.GlobalExceptionHandler)")
    public Object handleException(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            // 调用被拦截的方法
            return joinPoint.proceed();
        } catch (Exception e) {
            // 构造错误响应
            ErrorResponse errorResponse = new ErrorResponse();
            errorResponse.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
            errorResponse.setMessage(e.getMessage());
            errorResponse.setTimestamp(new Date().getTime());
            errorResponse.setPath(getRequestPath());

            // 打印错误日志
            log.error("Exception caught: {}", e.getMessage(), e);

            // 返回错误响应
            return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    private String getRequestPath() {
        // 获取请求路径
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        return request.getRequestURI();
    }
}

 6.3.实现方式:

在需要进行全局异常处理的 Controller 方法上加上 @GlobalExceptionHandler 注解

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/users/{id}")
    @GlobalExceptionHandler
    public ResponseEntity getUser(@PathVariable Long id) {
        User user = userService.getUserById(id);
        if (user == null) {
            throw new UserNotFoundException("User not found: " + id);
        }
        return new ResponseEntity<>(user, HttpStatus.OK);
    }

}

 在上面代码中,@GlobalExceptionHandler 注解用于标识需要进行全局异常处理的方法,被该注解标识的方法会被 GlobalExceptionHandlerAspect 类中的切面方法拦截,并进行异常处理。

GlobalExceptionHandlerAspect 类使用了 @Aspect 注解和 @Component 注解,声明了一个切面类,并实现了 @Around 注解指定的环绕通知方法。该方法中使用 ProceedingJoinPoint 对象调用了被拦截的方法,并捕获了可能抛出的异常。在捕获到异常后,该方法构造了一个错误响应对象 ErrorResponse,记录了错误码、错误信息、时间戳和请求路径等信息,并使用 log 对象打印了错误日志。最后,该方法使用 ResponseEntity 对象返回了错误响应。

在 Controller 方法中,使用了 @GlobalExceptionHandler 注解标识了需要进行全局异常处理的方法。当该方法中抛出异常时,会自动调用 GlobalExceptionHandlerAspect 类中的切面方法进行异常处理。

7.总结:

      我们探讨了AOP切面编程在Java中的应用场景,并演示了如何使用AspectJ库实现AOP。AOP可以帮助我们轻松地实现日志记录、安全性、事务管理、缓存、性能监控和异常处理等功能。它可以帮助我们更好地组织和管理代码,并提高应用程序的可维护性、可扩展性和可重用性。

      虽然AOP在许多应用程序中都很有用,但它也有一些缺点。其中最明显的是它的复杂性。AOP需要使用切入点表达式和通知方法等概念,这些概念对于新手来说可能比较难以理解。此外,使用AOP还会带来一些性能开销,因为它需要在运行时生成代理和动态代理等机制。

       因此,在实现AOP时,我们需要仔细考虑每个应用程序的需求和约束,并权衡AOP带来的好处和成本。如果我们使用得当,AOP可以帮助我们编写更优雅、更灵活、更健壮的代码,并提高我们的开发效率和应用程序的质量。

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