最近在代码中有用上这个AOP实现日志管理,来记录一下
一:什么是AOP?
Spring Boot的AOP(Aspect Oriented Programming,面向切面编程)是一种编程思想和技术,旨在通过将应用程序的功能分割成多个独立的模块来提高代码的可维护性和可重用性。
AOP可以帮助我们跨多个类和模块提取和封装应用程序中重复的代码逻辑,例如日志记录、性能监控、安全检查等。它通过定义一个切面(Aspect),在代码中就是使用@Aspect注解,将这些横跨多个类的共同关注点(即横切关注点)从主要业务逻辑中分离出来。
在Spring Boot中,我们可以通过使用AOP注解和切点表达式来定义切面和切点。AOP注解可以在方法上标注,以指定切面的行为,例如在方法执行前后执行某些逻辑。切点表达式可以用于定义哪些方法需要被切面所拦截。
二:依赖引入
<!--Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
三:注解使用
-
@Aspect
:@Aspect
注解用于定义切面(Aspect),切面是横切关注点(如日志记录、性能统计)的集合。通过在类上添加@Aspect
注解,表示该类是一个切面类,可以定义一组通知(Advice)以及切点(Pointcut)来捕获并处理目标对象的方法调用。 -
@Pointcut
:@Pointcut
注解用于定义切点(Pointcut),切点用于定义在哪些连接点(Joinpoint)上应用通知。通常,切点表示一组匹配连接点的表达式。通过在方法上添加@Pointcut
注解,可以定义一个切点,然后在通知中引用该切点来确定要应用通知的连接点。 -
@Around
:@Around
注解用于定义环绕通知(Around Advice),环绕通知是一种通知类型,在目标方法执行前后、异常抛出时等都可以执行一些逻辑。通过在方法上添加@Around
注解,定义环绕通知,可以控制目标方法的执行,包裹目标方法,允许在方法执行前后执行一些额外逻辑
四:使用方法
接下来,就贴代码
1.核心注释类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}
2.日志切面处理方法
@Component
@Aspect
public class LogAspect {
@Resource
private SysLogService sysLogService;
/**
* 配置切入点
*/
@Pointcut("@annotation(com.xxx.config.annotation.Log)")
public void logPointcut() {
// 该方法无方法体,主要为了让同类中其他方法使用此切入点
}
/**
* 配置环绕通知,使用在方法logPointcut()上注册的切入点
*
* @param joinPoint join point for advice
*/
@Around("logPointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
Object result;
result = joinPoint.proceed();
HttpServletRequest request = RequestHolder.getHttpServletRequest();
sysLogService.save(request,joinPoint);
return result;
}
}
结合代码解释一下意思
- @Pointcut是spring AOP的一个注解,用于定义切入点。切入点决定了哪些方法被AOP拦截
- 这里定义的切入点 `logPointcut` 会匹配所有被 `com.xxx.config.annotation.Log` 注解标记的方法。这意味着,当你的应用中有方法被这个注解标记
- `logPointcut` 方法本身没有方法体,这是因为它只是作为一个标识符,用于在后面的通知中引用
- `@Around` 是 Spring AOP 的一个注解,用于定义一个环绕通知。环绕通知可以在方法执行前后进行额外的操作。
- `joinPoint.proceed()` 用于执行被拦截的方法,并返回其结果。这里,我们先执行被拦截的方法,并获取其返回值。
- `sysLogService.save(request, joinPoint)` 调用 `SysLogService` 的 `save` 方法来保存日志。这里,它保存了当前的 HTTP 请求信息和被拦截方法
3.工具类,此方法是从当前上下文中获取HttpServletRequest的一些属性。
public class RequestHolder {
public static HttpServletRequest getHttpServletRequest() {
return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
}
}
-
RequestContextHolder.getRequestAttributes()
:RequestContextHolder
是Spring框架中的一个工具类,用于管理请求的属性。getRequestAttributes()
方法用于从当前线程中获取与请求关联的属性。这些属性通常封装了关于当前HTTP请求的信息。
-
Objects.requireNonNull()
:- 这是Java的
Objects
类中的一个静态方法,用于检查给定的对象引用是否为null
。 - 如果给定的对象引用为
null
,则此方法会抛出一个NullPointerException
。 - 在这里,它用于确保
RequestContextHolder.getRequestAttributes()
返回的对象不是null
。
- 这是Java的
-
(ServletRequestAttributes)
- 这是一个强制类型转换。
RequestContextHolder.getRequestAttributes()
返回的是一个RequestAttributes
类型的对象,但我们需要将它转换为ServletRequestAttributes
类型,因为ServletRequestAttributes
提供了访问HttpServletRequest
对象的方法。 ServletRequestAttributes
是RequestAttributes
的一个实现,专门用于处理与Servlet相关的请求。
- 这是一个强制类型转换。
-
.getRequest()
:- 这是
ServletRequestAttributes
类的一个方法,用于获取与当前请求关联的HttpServletRequest
对象。
- 这是
综上所述,这个方法的主要目的是从当前请求的上下文中获取HttpServletRequest
对象。但是,请注意,如果当前线程没有与任何请求关联,或者RequestContextHolder.getRequestAttributes()
返回null
,那么这段代码会抛出异常。所以,在调用此方法之前,你可能需要确保请求的上下文已经被正确设置。
4:Service接口
public interface SysLogService {
/**
* 保存日志
* @param request
* @param joinPoint
* @return
*/
int save (HttpServletRequest request, ProceedingJoinPoint joinPoint);
}
5:Impl类
@Service
public class SysLogServiceImpl implements SysLogService {
@Resource
private SysLogMapper sysLogMapper;
@Override
public int save(HttpServletRequest request, ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Log aopLog = method.getAnnotation( Log.class);
SysLog sysLog = new SysLog();
// 描述
if (sysLog != null) {
sysLog.setDescription(aopLog.value());
}
sysLog.setIp(ip(request));
sysLog.setUserAgent(getBrowser(request));
sysLog.setAdminName(UserUtil.getUsername());
sysLog.setAddress(String.valueOf(request.getRequestURL()));
sysLog.setParams(getParameter(method, joinPoint.getArgs()));
sysLog.setCreateTime(new Date());
return sysLogMapper.insert(sysLog);
}
/**
* 获取浏览器类型
* @param request
* @return
*/
public static String getBrowser(HttpServletRequest request) {
UserAgent parse = UserAgentUtil.parse(request.getHeader("User-Agent"));
Browser browser = parse.getBrowser();
return browser.getName();
}
/**
* 获取ip
* @param request
* @return
*/
public static String ip (HttpServletRequest request){
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 ) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 ) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 ) {
ip = request.getRemoteAddr();
}
String comma = ",";
String localhost = "127.0.0.1";
if (ip.contains(comma)) {
ip = ip.split(",")[0];
}
if (localhost.equals(ip)) {
// 获取本机真正的ip地址
try {
ip = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
log.error(e.getMessage(), e);
}
}
return ip;
}
}
6:Controller使用@Log,想在哪个方法上使用都可以
@Log("用户登录")
@ApiOperation(value = "用户登录", notes = "用户登录", httpMethod = "POST")
@PostMapping("/userLogin")
public ApiResult<UsersRes> userLogin(@RequestBody LoginUserReq userInfo, HttpServletResponse response) {
UsersRes login;
try {
login = usersService.login(userInfo);
} catch (Exception e) {
logger.error("用户登录异常", e);
return ApiResultFactory.transfer(e);
}
return ApiResultFactory.newSuccessResult(login);
}
五:总结
以上就是本文章的全部内容,本文章有借鉴了一些老师的代码,再结合本项目使用,记录文章是为了方便学习而记录,希望也能对大家有所帮助!