目录
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技术来优化代码。
首先需要定义一个注解,用于标记哪些方法需要进行权限控制。例如,可以定义一个名为@RequiresPermissions的注解,用于标记需要进行权限控制的方法。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiresPermissions {
String[] value();
}
接下来,需要编写一个切面类,用于在方法执行前进行权限检查。切面类需要实现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进行比对,判断当前用户是否具有访问该资源的权限。
最后,在Spring配置文件中配置AOP,以便让切面类生效。具体配置如下:
通过以上三个步骤,就可以实现基于AOP的权限控制。在需要进行权限控制的方法上,只需要加上@RequiresPermissions注解,并在注解中指定需要进行权限控制的权限列表,即可自动触发权限检查。
在大多数应用程序中,日志记录是必不可少的。我们需要记录应用程序中的重要事件,以便在出现故障时进行故障排除。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;
}
}
在许多应用程序中,缓存是一个关键的性能优化技术。通过缓存,我们可以避免频繁地从数据库或其他后端存储中读取数据。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()
方法执行业务逻辑,并将结果存储在缓存中。这样,下次再调用相同的方法时,可以直接返回缓存中的数据,而不必再执行业务逻辑,从而提高了应用程序的性能。
在许多应用程序中,事务管理是必不可少的。我们需要确保在执行多个相关操作时,如果其中任何一个操作失败,则所有操作都将回滚。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()
方法执行业务逻辑。如果发生异常,事务将回滚。
在许多应用程序中,性能监控是必不可少的。
@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);
}
}
定义一个自定义注解
@GlobalExceptionHandler
,用于标识需要进行全局异常处理的方法
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GlobalExceptionHandler {
}
定义一个切面类
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();
}
}
在需要进行全局异常处理的 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
类中的切面方法进行异常处理。
我们探讨了AOP切面编程在Java中的应用场景,并演示了如何使用AspectJ库实现AOP。AOP可以帮助我们轻松地实现日志记录、安全性、事务管理、缓存、性能监控和异常处理等功能。它可以帮助我们更好地组织和管理代码,并提高应用程序的可维护性、可扩展性和可重用性。
虽然AOP在许多应用程序中都很有用,但它也有一些缺点。其中最明显的是它的复杂性。AOP需要使用切入点表达式和通知方法等概念,这些概念对于新手来说可能比较难以理解。此外,使用AOP还会带来一些性能开销,因为它需要在运行时生成代理和动态代理等机制。
因此,在实现AOP时,我们需要仔细考虑每个应用程序的需求和约束,并权衡AOP带来的好处和成本。如果我们使用得当,AOP可以帮助我们编写更优雅、更灵活、更健壮的代码,并提高我们的开发效率和应用程序的质量。