提示:这里可以添加本文要记录的大概内容:
此篇文章从为什么使用AOP、AOP的基本概念、AOP在项目中的运用进行讲解,其中还嵌套了一小部分的自定义注解的讲解。若是你是零基础的选手,直接跳过“ 一、AOP有啥用?”,从AOP的基本概念开始阅读即可。
提示:以下是本篇文章正文内容,下面案例可供参考
老样子,秉持着知其然且知其所以然的初心,先看看AOP到底能给我带来什么好处?请看下面的小🌰
没有使用 AOP 的情况:
假设有一个服务类 MyService
,我们希望在每个方法执行前后记录日志。在没有使用 AOP 的情况下,可能会在每个方法中都加入日志记录的代码,导致代码冗余,难以维护。
public class MyService {
public void doSomething() {
// 业务逻辑
System.out.println("Doing something...");
// 日志记录
System.out.println("Log: Method 'doSomething' executed.");
}
public void doAnotherThing() {
// 业务逻辑
System.out.println("Doing another thing...");
// 日志记录
System.out.println("Log: Method 'doAnotherThing' executed.");
}
// 其他方法...
}
使用 AOP 的情况:
通过使用 AOP,我们可以将日志记录逻辑独立出来,定义一个切面类,以便在每个方法执行前后执行相同的日志记录逻辑。
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.MyService.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before executing: " + joinPoint.getSignature().getName());
}
@After("execution(* com.example.MyService.*(..))")
public void logAfter(JoinPoint joinPoint) {
System.out.println("After executing: " + joinPoint.getSignature().getName());
}
}
在这个例子中,LoggingAspect
类是一个切面,它定义了 logBefore
和 logAfter
两个通知方法,用于在目标方法执行前后执行相应的日志记录逻辑。
对比两种情况:
通过使用 AOP,我们能够更清晰地关注业务逻辑,将横切关注点从业务逻辑中分离出来,提高了代码的可维护性和可读性。
首先,要明确一点。AOP是一种编程思想,模块化的编程思想。不搞虚的,直接举个🌰。
比如:我们在进行项目开发时,有好几个方法要用到日志记录,那我们就需要在很多个地方log.info(),这样不好的地方就是不标准化,当需要改动的时候需要到处找代码改动。采用AOP的话就可以把日志记录这个功能集中,也就是把它写成一个类里的方法(也就是所谓的模块化)。然后哪个方法需要用,我就在该方法处调用就行。这里的类就是切面类,需要调用日志记录的方法就是连接点。
下面的这些概念以及注解都是AOP(Aspect-Oriented Programming)编程思想的代码实现。
对于切入点和连接点没有搞太明白的兄弟不要着急,继续往下看!!
对应代码如下:
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.MyService.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before executing: " + joinPoint.getSignature().getName());
}
@After("execution(* com.example.MyService.*(..))")
public void logAfter(JoinPoint joinPoint) {
System.out.println("After executing: " + joinPoint.getSignature().getName());
}
}
在上述代码中:
@Aspect
注解标记类为切面。
@Before
注解表示在连接点之前执行的通知。
@After
注解表示在连接点之后执行的通知。
切入点表达式 "execution(* com.example.MyService.*(..))"
匹配了 MyService
类的所有方法。
所以logBefore
会在 MyService
类的所有方法执行前通过
System.out.println("Before executing: " + joinPoint.getSignature().getName());
进行日志的打印。
??注意:joinPoint
这个就是连接点,也就是具体执行的方法,会通过joinPoint.getSignature().getName()
拿到该方法对应的具体方法名。
所以在这里切入点匹配的是 MyService
类的所有方法,而每个具体的方法就是joinPoint
,也就是连接点。
知识点补充:切入点表达的两种方式
1、第一种:采用@Pointcut注解
@Aspect
public class MyAspect {
// 使用 @Pointcut 注解定义切入点表达式
@Pointcut("execution(* com.example.MyService.*(..))")
public void myServiceMethods() {
// 该方法体通常为空,因为注解值已经定义了切入点表达式
}
// 使用 @Before、@Around、@After 等注解引用切入点表达式
@Before("myServiceMethods()")
public void beforeMyServiceMethods() {
// 在切入点方法执行前执行的逻辑
}
// 其他通知和切入点方法的定义类似
}
2、第二种:采用execution表达式
@Aspect
public class MyAspect {
// 使用 @Before、@Around、@After 等注解引用切入点表达式
@Before("execution(* com.example.MyService.*(..))")
public void beforeMyServiceMethods() {
// 在切入点方法执行前执行的逻辑
}
// 其他通知和切入点方法的定义类似
}
自定义注解 @Auth
:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Auth {
String functionCode();
}
@interface Auth
:
@interface
是用于定义注解的关键字。Auth
是注解的名称。@Target({ElementType.METHOD})
:
@Target
注解指定了注解可以应用的元素类型,这里指定了该注解只能用于方法上。@Auth
注解只能应用在方法上,用于标识某个方法需要进行权限检查。@Retention(RetentionPolicy.RUNTIME)
:
@Retention
注解指定了注解的保留策略,这里设置为 RetentionPolicy.RUNTIME
表示注解在运行时可通过反射获取。String functionCode();
:
functionCode
是一个方法,该方法返回一个 String
类型的值。@Auth
注解时,你需要提供 functionCode
的值,例如:@Auth(functionCode = "READ_DATA")
。AOP 切面 AuthAspect
:
@Component
@Aspect
public class AuthAspect {
@Resource
private CockpitService cockpitService;
//这里定义了一个切入点
@Pointcut("@annotation(com.longfor.idata.server.annotation.Auth)")
public void checkAuth() {
}
//定义了一个环绕通知,且环绕通知是在checkAuth()这个切入点的方法中的前后执行的
@Around(value = "checkAuth()")
public BaseResponse<Object> doAround(ProceedingJoinPoint joinPoint) throws Throwable {
//**这一堆是方法执行前的**
//获取该连接点的方法签名(方法名、返回类型、参数列表、修饰符)
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取方法中的@Auth注解
Auth authAnno = signature.getMethod().getAnnotation(Auth.class);
String code = authAnno.functionCode();
// 封装了一个从session获取登录名的方法
String username = getUserName();
if (StringUtils.isEmpty(username)) {
throw new ForbiddenException("登录认证信息失效,请重新登录!");
}
boolean hasAuth = checkAuthorization(username,code);
//**这是方法执行中**
//如果有权限
if (hasAuth) {
// 获取方法调用时的参数
Object[] args = joinPoint.getArgs();
//继续执行该连接点的方法
return (BaseResponse) joinPoint.proceed(args);
}
//**这一堆是方法执行后执行的**
throw new ForbiddenException("您没有权限访问该功能!");
}
}
使用注解和AOP的Controller方法:
/**
* 获取需求列表
*
* @return
*/
@PostMapping("/list/all")
@Auth(functionCode = "READ_DATA")
public BaseResponse<Object> getAllRequirementList(@RequestBody RequirementSearchDTO searchDTO) {
return new BaseResponse(requirementService.getRequirementList(searchDTO));
}
@Auth(functionCode = "READ_DATA")
注解标记在需要进行权限检查的方法上,表示该方法需要权限码为 “READ_DATA” 的权限。当访问该controller方法时, AOP 切面会在这些被标记的方法执行前后进行拦截和处理。若是通过functionCode和userName获取到权限则可以执行这个controller,否则抛出异常“没有权限访问该功能!”
总体来说,AOP是一个强大的编程范式,它使得我们能够更加灵活地管理和组织代码。欢迎个人官爷评论区交流,若有收获请不吝点赞👍哟,🌹🌹🌹