SpringBoot开发,如何使用切面aspect文件做埋点?
发布于 作者:苏南大叔 来源:程序如此灵动~

切面aspect
是AOP
编程中的重要概念,除了aspect
外,还存在着其它相关概念。本文以介绍概念建立印象为主线,所以,仅仅说明aspect
的基本使用方法。对比以往概念的话,这个aspect
就是个无侵入式的自动埋点的编程方式。它是无侵入式的,没有它也可以运行,有了它就增加了相关埋点功能,非常绿色环保。
苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程经验总结。测试环境:win10
,openjdk@23.0.2
,IntelliJ IDEA 2024.3.4.1
,maven@3.3.2
,spring boot@2.5.4
,java@17
,mysql@5.7.26
。
项目代码
在苏南大叔的SpringBoot
系列文章中,在UserController
这个接口里面,需要做两个验证,分别是:
- 是否登陆验证。https://newsn.net/say/springboot-session.html
- csrf token是否有效验证。https://newsn.net/say/springboot-csrf.html
这两个验证逻辑,都是写在UserController.java
文件内部的。范例如下:
package com.example.demo.controller;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
//...
@PostMapping
public ResponseEntity<?> createUser(@RequestBody User user, HttpServletRequest request) {
if (!isLoggedIn(request)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("User not logged in");
}
try {
validateCsrfToken(request);
return ResponseEntity.ok(userService.createUser(user));
} catch (RuntimeException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}
}
private void validateCsrfToken(HttpServletRequest request) {
String csrfToken = request.getHeader("X-CSRF-TOKEN");
String sessionCsrfToken = (String) request.getSession().getAttribute("CSRF_TOKEN");
if (csrfToken == null || !csrfToken.equals(sessionCsrfToken)) {
throw new RuntimeException("Invalid CSRF Token");
}
}
private boolean isLoggedIn(HttpServletRequest request) {
return request.getSession().getAttribute("user") != null;
}
}
当然,这两个判断,都与session
有关。回顾一下csrf
初始化数据写入的过程:
@PostMapping("/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
HttpSession session,
Model model) {
System.out.println(username+","+password);
if (isValidUser(username, password)) {
session.setAttribute("user", username);
return "redirect:/success";
} else {
model.addAttribute("error", "Invalid username or password");
return "login";
}
}
@GetMapping("/success")
public String showSuccessPage(HttpSession session, Model model) {
if (session.getAttribute("user") == null) {
return "redirect:/login";
}
model.addAttribute("user", session.getAttribute("user"));
// HttpSession session = request.getSession();
String csrfToken = UUID.randomUUID().toString();
session.setAttribute("csrfToken", csrfToken);
model.addAttribute("csrfToken", csrfToken);
return "success";
}
AOP面向切面编程
面向切面编程是一种编程范式,Aspect-Oriented Programming
用于处理程序中分散的、交叉的关注点(cross-cutting concerns),比如日志、安全性、事务管理等。
在没有AOP
的情况下,这些关注点通常会散布在整个代码库中,导致代码重复和维护困难。AOP通过引入"Aspect"(切面)这一概念,允许开发者将这些关注点从业务逻辑中分离出来,从而提高代码的模块性和可维护性。
在面向切面编程(AOP
)中,切面(Aspect
) 是一个模块化的单元,用于封装横切关注点(Cross-Cutting Concerns
)。横切关注点是那些跨越多个模块或功能的通用关注点,如日志记录、事务管理、安全性和性能监控等。
pom.xml
为了本文代码的实现,需要安装spring-boot-starter-aop
。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
修改pom.xml
后,执行mvn install
。
aspect切面
然而登陆及csrf
这两个验证逻辑,是可以通过aspect
切面独立出去的。参考代码:
src/main/java/com/example/demo/aspect/SecurityAspect.java
:
package com.example.demo.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@Aspect
@Component
public class SecurityAspect {
private final HttpServletRequest request;
private static final String CSRF_TOKEN = "csrfToken";
public SecurityAspect(HttpServletRequest request) {
this.request = request;
}
@Before("execution(* com.example.demo.controller.UserController.*(..))")
public void checkCsrfToken() {
String method = request.getMethod();
if (RequestMethod.GET.name().equals(method) ||
RequestMethod.HEAD.name().equals(method) ||
RequestMethod.OPTIONS.name().equals(method) ||
RequestMethod.TRACE.name().equals(method)) {
return;
}
HttpSession session = request.getSession();
String sessionCsrfToken = (String) session.getAttribute(CSRF_TOKEN);
String requestCsrfToken = request.getHeader("X-CSRF-Token");
if (sessionCsrfToken == null || !sessionCsrfToken.equals(requestCsrfToken)) {
throw new RuntimeException("CSRF token 验证失败");
}
System.out.println("CSRF token 验证通过");
}
@Before("execution(* com.example.demo.controller.UserController.*(..))")
public void checkIfLoggedIn() {
HttpSession session = request.getSession();
String user = (String) session.getAttribute("user");
if (user == null || user.isEmpty()) {
throw new RuntimeException("用户未登录");
}
System.out.println("用户已登录");
}
}
aspect切面说明
【范围定义】
在这个例子中,aspect
通过定义Before
,实现了对其它文件里面定义的Controller
的定制。在checkCsrfToken()
中,因为只有对数据进行修改的接口才需要csrf
验证,所以要对接口请求的方法进行筛选。这个筛选也可以定义在Before
里面,但是,目前由于经验限制,只能做到过滤最常见的Get
请求。暂留后续文字更新。
【搭积木效果】
如果把这个aspect
文件移走的话,代码并不会报错。只是目标controller
失去了防火效果而已。所以,搭积木的效果非常明显。这个是非侵入式的埋点,效果非常好,非常理想!
运行效果
效果如下所示:
对于正常的用户来说,并不会看到这些错误提示。但是,这些错误提示也确实太繁杂了。所以,在这里埋个伏笔,下篇文章解决这个问题。
结语
苏南大叔的SpringBoot
系列文章,到目前为止,应该可以应付大多数的开发需求了。虽然写的不是太详细,但是如果以功利为目的的话,这已经足够了。
更多苏南大叔的java
文章,可以点击下面的连接:


