Este artículo aprenderá el módulo de procesamiento de funciones unificadas de Spring Boot, que también es la parte real de AOP
-
Interfaz de implementación de verificación de permiso de inicio de sesión de usuario
HandlerInterceptor
+WebMvcConfigurer
-
El manejo de excepciones usa anotaciones
@RestControllerAdvice
+@ExceptionHandler
-
El formato de datos regresa usando anotaciones
@ControllerAdvice
e implementa la interfaz@ResponseBodyAdvice
1. Verificación de autoridad de inicio de sesión de usuario unificado
El proceso de desarrollo y mejora de la autoridad de inicio de sesión del usuario
-
Verificación de inicio de sesión de usuario inicial: Obtenga la sesión y la información del usuario en la sesión en cada método. Si hay un usuario, entonces el inicio de sesión se considera exitoso; de lo contrario, el inicio de sesión falla
-
La segunda versión de verificación de inicio de sesión de usuario: proporcione un método unificado, llame al método de verificación de identidad de inicio de sesión de usuario unificado en cada método que deba verificarse para juzgar
-
La tercera versión de validación de inicio de sesión de usuario: use Spring AOP para realizar una validación de inicio de sesión de usuario unificada
-
La cuarta versión de verificación de inicio de sesión de usuario: uso de interceptores Spring para lograr una verificación de inicio de sesión unificada para los usuarios
1.1 La verificación inicial de la autoridad de inicio de sesión del usuario
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/a1")
public Boolean login (HttpServletRequest request) {
// 有 Session 就获取,没有就不创建
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null) {
// 说明已经登录,进行业务处理
return true;
} else {
// 未登录
return false;
}
}
@RequestMapping("/a2")
public Boolean login2 (HttpServletRequest request) {
// 有 Session 就获取,没有就不创建
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null) {
// 说明已经登录,进行业务处理
return true;
} else {
// 未登录
return false;
}
}
}
El código escrito de esta manera tiene la misma autoridad de verificación de inicio de sesión de usuario en cada método. Las desventajas son:
-
Cada método debe escribir el método de verificación de inicio de sesión del usuario por separado, incluso si está encapsulado en un método público, todavía necesita pasar parámetros y llamar y juzgar en el método.
-
Cuantos más controladores se agreguen, más métodos se llamarán para la verificación de inicio de sesión del usuario, lo que aumenta el éxito de la modificación y el mantenimiento posteriores.
-
Estos métodos de verificación de inicio de sesión de usuario no tienen casi nada que ver con el negocio que se implementará ahora, pero cada método debe escribirse nuevamente, por lo que es una muy buena solución para proporcionar un método AOP público para el método de verificación de autoridad de inicio de sesión de usuario unificado.
1.2 Autenticación de inicio de sesión de usuario unificado de Spring AOP
Autenticación de inicio de sesión de usuario unificado, el primer pensamiento del método de implementación es usar la notificación previa de Spring AOP o la notificación envolvente para lograr
@Aspect // 当前类是一个切面
@Component
public class UserAspect {
// 定义切点方法 Controller 包下、子孙包下所有类的所有方法
@Pointcut("execution(* com.example.springaop.controller..*.*(..))")
public void pointcut(){}
// 前置通知
@Before("pointcut()")
public void doBefore() {}
// 环绕通知
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) {
Object obj = null;
System.out.println("Around 方法开始执行");
try {
obj = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("Around 方法结束执行");
return obj;
}
}
Sin embargo, si la función de verificación del permiso de inicio de sesión del usuario solo se implementa en el aspecto del código anterior Spring AOP, hay dos problemas:
-
No hay forma de obtener
HttpSession
y Solicitar objeto -
Necesitamos interceptar algunos métodos, pero no otros métodos. Por ejemplo, el método de registro y el método de inicio de sesión no se interceptan, es decir, las reglas de interceptación reales son muy complicadas y las expresiones simples de aspectJ no pueden cumplir con los requisitos de interceptación.
1.3 Interceptores de resorte
En respuesta al problema de Spring AOP en el código anterior, Spring proporciona un interceptor de implementación específico: HandlerInterceptor
la implementación del interceptor tiene dos pasos:
1. Cree un interceptor personalizado e implemente HandlerInterceptor
el método preHandle en la interfaz en Spring
2. Agregue el interceptor personalizado a la configuración del marco y establezca las reglas de interceptación
-
@Configuration
Anotar la clase actual -
implementar
WebMvcConfigurer
la interfaz -
addInterceptors
método de anulación
Nota: Se pueden configurar varios interceptores en un proyecto al mismo tiempo
(1) Crear un interceptor personalizado
/**
* @Description: 自定义用户登录的拦截器
* @Date 2023/2/13 13:06
*/
@Component
public class LoginIntercept implements HandlerInterceptor {
// 返回 true 表示拦截判断通过,可以访问后面的接口
// 返回 false 表示拦截未通过,直接返回结果给前端
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
// 1.得到 HttpSession 对象
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null) {
// 表示已经登录
return true;
}
// 执行到此代码表示未登录,未登录就跳转到登录页面
response.sendRedirect("/login.html");
return false;
}
}
(2) Agregue el interceptor personalizado a la configuración del sistema y establezca las reglas de interceptación
-
addPathPatterns
: indica la URL que se debe interceptar e**
indica que se interceptan todos los métodos -
excludePathPatterns
: Indica la URL que debe ser excluida
Descripción: las reglas de interceptación pueden interceptar las URL utilizadas en este proyecto, incluidos los archivos estáticos (archivos de imagen, archivos JS y CSS, etc.).
/**
* @Description: 将自定义拦截器添加到系统配置中,并设置拦截的规则
* @Date 2023/2/13 13:13
*/
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Resource
private LoginIntercept loginIntercept;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor(new LoginIntercept());//可以直接new 也可以属性注入
registry.addInterceptor(loginIntercept).
addPathPatterns("/**"). // 拦截所有 url
excludePathPatterns("/user/login"). //不拦截登录注册接口
excludePathPatterns("/user/reg").
excludePathPatterns("/login.html").
excludePathPatterns("/reg.html").
excludePathPatterns("/**/*.js").
excludePathPatterns("/**/*.css").
excludePathPatterns("/**/*.png").
excludePathPatterns("/**/*.jpg");
}
}
1.4 Ejercicio: Interceptor de inicio de sesión
Requerir
-
Las páginas de inicio de sesión y registro no están bloqueadas, otras páginas están bloqueadas
-
Después de que el inicio de sesión se escribe con éxito en la sesión, se puede acceder a la página interceptada normalmente
En 1.3, se creó y agregó un interceptor personalizado a la configuración del sistema, y se establecieron las reglas para la interceptación.
(2) Cree controller
un paquete, créelo en el paquete UserController
y escriba el código comercial de la página de inicio de sesión y la página de inicio
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public boolean login(HttpServletRequest request,String username, String password) {
boolean result = false;
if (StringUtils.hasLength(username) && StringUtils.hasLength(password)) {
if(username.equals("admin") && password.equals("admin")) {
HttpSession session = request.getSession();
session.setAttribute("userinfo","userinfo");
return true;
}
}
return result;
}
@RequestMapping("/index")
public String index() {
return "Hello Index";
}
}
(3) Ejecute el programa, visite la página y compare el efecto antes y después de iniciar sesión
1.5 Principio de implementación del interceptor
Con el interceptor se realizará el procesamiento comercial correspondiente antes de llamar al Controller, el proceso de ejecución se muestra en la siguiente figura
Análisis del código fuente del principio de implementación
Toda la ejecución se logrará Controller
a través de un programador. DispatcherServlet
Y todos los métodos se ejecutarán DispatcherServlet
en doDispatch
el método de programación, doDispatch
el análisis del código fuente es el siguiente:
A través del análisis del código fuente, se puede ver que el interceptor en Sping también se realiza a través de la idea de proxy dinámico y notificación envolvente.
1.6 Agregar un prefijo de acceso unificado
Agregue el prefijo api a todas las direcciones de solicitud, c significa todo
@Configuration
public class AppConfig implements WebMvcConfigurer {
// 所有的接口添加 api 前缀
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("api", c -> true);
}
}
2. Manejo unificado de excepciones
Agregar @ControllerAdvice
la clase de notificación del controlador a la clase actual
Agregue al método @ExceptionHandler(xxx.class)
, indicando el controlador de excepciones, y agregue el código comercial devuelto por la excepción
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/index")
public String index() {
int num = 10/0;
return "Hello Index";
}
}
En el paquete de configuración, crea MyExceptionAdvice
la clase.
@RestControllerAdvice // 当前是针对 Controller 的通知类(增强类)
public class MyExceptionAdvice {
@ExceptionHandler(ArithmeticException.class)
public HashMap<String,Object> arithmeticExceptionAdvice(ArithmeticException e) {
HashMap<String, Object> result = new HashMap<>();
result.put("state",-1);
result.put("data",null);
result.put("msg" , "算出异常:"+ e.getMessage());
return result;
}
}
También puedes escribir así, el efecto es el mismo.
@ControllerAdvice
public class MyExceptionAdvice {
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public HashMap<String,Object> arithmeticExceptionAdvice(ArithmeticException e) {
HashMap<String, Object> result = new HashMap<>();
result.put("state",-1);
result.put("data",null);
result.put("msg" , "算数异常:"+ e.getMessage());
return result;
}
}
Si hay otra excepción de puntero nulo, el código anterior no funcionará y se debe escribir un controlador para las excepciones de puntero nulo.
@ExceptionHandler(NullPointerException.class)
public HashMap<String,Object> nullPointerExceptionAdvice(NullPointerException e) {
HashMap<String, Object> result = new HashMap<>();
result.put("state",-1);
result.put("data",null);
result.put("msg" , "空指针异常异常:"+ e.getMessage());
return result;
}
@RequestMapping("/index")
public String index(HttpServletRequest request,String username, String password) {
Object obj = null;
System.out.println(obj.hashCode());
return "Hello Index";
}
Pero una cosa a tener en cuenta es que si cada excepción se escribe de esta manera, la carga de trabajo será muy grande y hay excepciones personalizadas, por lo que definitivamente no es bueno escribir de esta manera. Ya que es una excepción, simplemente escriba Excepción directamente. . Es la clase principal de todas las excepciones. Si encuentra dos excepciones que no están escritas anteriormente, coincidirá directamente con Exception
Cuando hay varias notificaciones de excepción, el orden de coincidencia es que la clase actual y sus subclases coincidan hacia arriba.
@ExceptionHandler(Exception.class)
public HashMap<String,Object> exceptionAdvice(Exception e) {
HashMap<String, Object> result = new HashMap<>();
result.put("state",-1);
result.put("data",null);
result.put("msg" , "异常:"+ e.getMessage());
return result;
}
Puede ver que la coincidencia de prioridad sigue siendo la excepción de puntero nulo escrita anteriormente
3. Retorno de formato de datos unificado
3.1 Realización de la devolución del formato de datos unificado
(1) Agregar a la clase actual @ControllerAdvice
(2) Implementar ResponseBodyAdvice
reescribiendo su método
-
supports
Método, este método indica si el contenido debe reescribirse (a través de este método, algunos controladores y métodos pueden reescribirse selectivamente), y si se reescribe, devuelve verdadero -
beforeBodyWrite
método, este método se llama antes de que el método regrese
@ControllerAdvice
public class MyResponseAdvice implements ResponseBodyAdvice {
// 返回一个 boolean 值,true 表示返回数据之前对数据进行重写,也就是会进入 beforeBodyWrite 方法
// 返回 false 表示对结果不进行任何处理,直接返回
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
// 方法返回之前调用此方法
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
HashMap<String,Object> result = new HashMap<>();
result.put("state",1);
result.put("data",body);
result.put("msg","");
return result;
}
}
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public boolean login(HttpServletRequest request,String username, String password) {
boolean result = false;
if (StringUtils.hasLength(username) && StringUtils.hasLength(password)) {
if(username.equals("admin") && password.equals("admin")) {
HttpSession session = request.getSession();
session.setAttribute("userinfo","userinfo");
return true;
}
}
return result;
}
@RequestMapping("/reg")
public int reg() {
return 1;
}
}
3.2 Análisis del código fuente de @ControllerAdvice
A través @ControllerAdvice
del análisis del código fuente, podemos conocer el proceso de ejecución de la excepción unificada anterior y la devolución de datos unificados.
(1) Primero mire el código fuente de @ControllerAdvice
Puede ver @ControllerAdvice
que se deriva del @Component
componente y toda la inicialización del componente llamará InitializingBean
a la interfaz
(2) Veamos qué clases de implementación tiene initializeBean
Durante el proceso de consulta, se encontró que la subclase de implementación en Spring MVC es que RequestMappingHandlerAdapter
hay un método en ella afterPropertiesSet()
, que indica el método que se ejecutará después de que se completen todas las configuraciones de parámetros.
(3) Y hay un método initControllerAdviceCache en este método, consulte este método
Se encuentra que este método buscará y usará todas @ControllerAdvice
las clases durante la ejecución. Cuando se envía un evento, se llamará al método Advice correspondiente, como llamar a la encapsulación de datos unificados antes de devolver los datos. Por ejemplo, cuando ocurre una excepción, se implementa llamando al método de asesoramiento anormal