经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
详解SpringBoot中的统一功能处理的实现
来源:jb51  时间:2023/1/30 15:19:47  对本文有异议

前言

接下来是 Spring Boot 统?功能处理模块了,也是 AOP 的实战环节,要实现的课程?标有以下 3 个:

  • 统??户登录权限验证
  • 统?数据格式返回
  • 统?异常处理

接下我们?个?个来看。

一、用户登录权限效验

?户登录权限的发展从之前每个?法中??验证?户登录权限,到现在统?的?户登录验证处理,它是?个逐渐完善和逐渐优化的过程。

1.1 最初的用户登录验证

我们先来回顾?下最初?户登录验证的实现?法:

  1. @RestController
  2. @RequestMapping("/user")
  3. public class UserController {
  4. /**
  5. * 某?法 1
  6. */
  7. @RequestMapping("/m1")
  8. public Object method(HttpServletRequest request) {
  9. // 有 session 就获取,没有不会创建
  10. HttpSession session = request.getSession(false);
  11. if (session != null && session.getAttribute("userinfo") != null) {
  12. // 说明已经登录,业务处理
  13. return true;
  14. } else {
  15. // 未登录
  16. return false;
  17. }
  18. }
  19. /**
  20. * 某?法 2
  21. */
  22. @RequestMapping("/m2")
  23. public Object method2(HttpServletRequest request) {
  24. // 有 session 就获取,没有不会创建
  25. HttpSession session = request.getSession(false);
  26. if (session != null && session.getAttribute("userinfo") != null) {
  27. // 说明已经登录,业务处理
  28. return true;
  29. } else {
  30. // 未登录
  31. return false;
  32. }
  33. }
  34. // 其他?法...
  35. }

从上述代码可以看出,每个?法中都有相同的?户登录验证权限,它的缺点是:

  • 每个?法中都要单独写?户登录验证的?法,即使封装成公共?法,也?样要传参调?和在?法中进?判断。
  • 添加控制器越多,调??户登录验证的?法也越多,这样就增加了后期的修改成本和维护成本。
  • 这些?户登录验证的?法和接下来要实现的业务?何没有任何关联,但每个?法中都要写?遍。

所以提供?个公共的 AOP ?法来进?统?的?户登录权限验证迫在眉睫。

1.2 Spring AOP 用户统一登录验证的问题

说到统?的?户登录验证,我们想到的第?个实现?案是 Spring AOP 前置通知或环绕通知来实现,具体实现代码如下:

  1. import org.aspectj.lang.ProceedingJoinPoint;
  2. import org.aspectj.lang.annotation.*;
  3. import org.springframework.stereotype.Component;
  4. @Aspect
  5. @Component
  6. public class UserAspect {
  7. // 定义切点?法 controller 包下、?孙包下所有类的所有?法
  8. @Pointcut("execution(* com.example.demo.controller..*.*(..))")
  9. public void pointcut(){ }
  10. // 前置?法
  11. @Before("pointcut()")
  12. public void doBefore(){
  13.  
  14. }
  15.  
  16. // 环绕?法
  17. @Around("pointcut()")
  18. public Object doAround(ProceedingJoinPoint joinPoint){
  19. Object obj = null;
  20. System.out.println("Around ?法开始执?");
  21. try {
  22. // 执?拦截?法
  23. obj = joinPoint.proceed();
  24. } catch (Throwable throwable) {
  25. throwable.printStackTrace();
  26. }
  27. System.out.println("Around ?法结束执?");
  28. return obj;
  29. }
  30. }

如果要在以上 Spring AOP 的切?中实现?户登录权限效验的功能,有以下两个问题:

1.没办法获取到 HttpSession 对象。

2.我们要对?部分?法进?拦截,?另?部分?法不拦截,如注册?法和登录?法是不拦截的,这样的话排除?法的规则很难定义,甚?没办法定义。

那这样如何解决呢?

1.3 Spring 拦截器

对于以上问题 Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步骤:

1.创建?定义拦截器,实现 HandlerInterceptor 接?的 preHandle(执?具体?法之前的预处理)?法。

2.将?定义拦截器加? WebMvcConfigurer 的 addInterceptors ?法中。

具体实现如下。

补充 过滤器:

过滤器是Web容器提供的。触发的时机比拦截器更靠前,Spring 初始化前就执行了,所以并不能处理用户登录权限效验等问题。

1.3.1 准备工作

  1. package com.example.demo.controller;
  2.  
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.springframework.util.StringUtils;
  5. import org.springframework.web.bind.annotation.RequestMapping;
  6. import org.springframework.web.bind.annotation.RestController;
  7.  
  8. import javax.servlet.http.HttpServletRequest;
  9. import javax.servlet.http.HttpSession;
  10.  
  11. @RestController
  12. @RequestMapping("/user")
  13. @Slf4j
  14. public class UserController {
  15.  
  16. @RequestMapping("/login")
  17. public boolean login(HttpServletRequest request,
  18. String username, String password) {
  19. // // 1.非空判断
  20. // if (username != null && username != "" &&
  21. // password != null && username != "") {
  22. // // 2.验证用户名和密码是否正确
  23. // }
  24.  
  25. // 1.非空判断
  26. if (StringUtils.hasLength(username) && StringUtils.hasLength(password)) {
  27. // 2.验证用户名和密码是否正确
  28. if ("admin".equals(username) && "admin".equals(password)) {
  29. // 登录成功
  30. HttpSession session = request.getSession();
  31. session.setAttribute("userinfo", "admin");
  32. return true;
  33. } else {
  34. // 用户名或密码输入错误
  35. return false;
  36. }
  37. }
  38. return false;
  39. }
  40.  
  41. @RequestMapping("/getinfo")
  42. public String getInfo() {
  43. log.debug("执行了 getinfo 方法");
  44. return "执行了 getinfo 方法";
  45. }
  46.  
  47. @RequestMapping("/reg")
  48. public String reg() {
  49. log.debug("执行了 reg 方法");
  50. return "执行了 reg 方法";
  51. }
  52.  
  53. }

1.3.2 自定义拦截器

接下来使?代码来实现?个?户登录的权限效验,?定义拦截器是?个普通类,具体实现代码如下:

  1. package com.example.demo.config;
  2.  
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.springframework.stereotype.Component;
  5. import org.springframework.web.servlet.HandlerInterceptor;
  6.  
  7. import javax.servlet.http.HttpServletRequest;
  8. import javax.servlet.http.HttpServletResponse;
  9. import javax.servlet.http.HttpSession;
  10.  
  11. /**
  12. * 登录拦截器
  13. */
  14. @Component
  15. @Slf4j
  16. public class LoginInterceptor implements HandlerInterceptor {
  17.  
  18. @Override
  19. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  20. // 登录判断业务
  21. HttpSession session = request.getSession(false);
  22. if (session != null && session.getAttribute("userinfo") != null) {
  23. return true;
  24. }
  25. log.error("当前用户没有访问权限");
  26. response.setStatus(401);
  27. return false;
  28. }
  29. }

返回 boolean 类型。

相当于一层安保:

为 false 则不能继续往下执行;为 true 则可以。

1.3.3 将自定义拦截器加入到系统配置

将上?步中的?定义拦截器加?到系统配置信息中,具体实现代码如下:

  1. package com.example.demo.config;
  2.  
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  6. import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
  7. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  8.  
  9. @Configuration // 一定不要忘记
  10. public class MyConfig implements WebMvcConfigurer {
  11.  
  12. @Autowired
  13. private LoginInterceptor loginInterceptor;
  14.  
  15. @Override
  16. public void addInterceptors(InterceptorRegistry registry) {
  17. registry.addInterceptor(loginInterceptor)
  18. .addPathPatterns("/**") // 拦截所有请求
  19. .excludePathPatterns("/user/login") // 排除不拦截的 url
  20. // .excludePathPatterns("/**/*.html")
  21. // .excludePathPatterns("/**/*.js")
  22. // .excludePathPatterns("/**/*.css")
  23. .excludePathPatterns("/user/reg"); // 排除不拦截的 url
  24. }
  25. }

或者:

  1. package com.example.demo.common;
  2.  
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
  6. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  7. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  8.  
  9. import java.util.ArrayList;
  10. import java.util.List;
  11.  
  12. @Configuration
  13. public class AppConfig implements WebMvcConfigurer {
  14.  
  15. // 不拦截的 url 集合
  16. List<String> excludes = new ArrayList<String>() {{
  17. add("/**/*.html");
  18. add("/js/**");
  19. add("/editor.md/**");
  20. add("/css/**");
  21. add("/img/**"); // 放行 static/img 下的所有文件
  22. add("/user/login"); // 放行登录接口
  23. add("/user/reg"); // 放行注册接口
  24. add("/art/detail"); // 放行文章详情接口
  25. add("/art/list"); // 放行文章分页列表接口
  26. add("/art/totalpage"); // 放行文章分页总页数接口
  27. }};
  28.  
  29. @Autowired
  30. private LoginInterceptor loginInterceptor;
  31.  
  32. @Override
  33. public void addInterceptors(InterceptorRegistry registry) {
  34. // 配置拦截器
  35. InterceptorRegistration registration =
  36. registry.addInterceptor(loginInterceptor);
  37. registration.addPathPatterns("/**");
  38. registration.excludePathPatterns(excludes);
  39. }
  40. }

如果不注入对象的话,addInterceptor() 的参数也可以直接 new 一个对象:

  1. @Configuration // 一定不要忘记
  2. public class MyConfig implements WebMvcConfigurer {
  3.  
  4. @Override
  5. public void addInterceptors(InterceptorRegistry registry) {
  6. registry.addInterceptor(new LoginInterceptor())
  7. .addPathPatterns("/**") // 拦截所有请求
  8. .excludePathPatterns("/user/login") // 排除不拦截的 url
  9. // .excludePathPatterns("/**/*.html")
  10. // .excludePathPatterns("/**/*.js")
  11. // .excludePathPatterns("/**/*.css")
  12. // .excludePathPatterns("/**/*.jpg")
  13. // .excludePathPatterns("/**/login")
  14. .excludePathPatterns("/user/reg"); // 排除不拦截的 url
  15. }
  16. }

其中:

addPathPatterns:表示需要拦截的 URL,“**”表示拦截任意?法(也就是所有?法)。

excludePathPatterns:表示需要排除的 URL。

说明:以上拦截规则可以拦截此项?中的使? URL,包括静态?件 (图??件、JS 和 CSS 等?件)。

1.4 拦截器实现原理

正常情况下的调?顺序:

然?有了拦截器之后,会在调? Controller 之前进?相应的业务处理,执?的流程如下图所示:

1.4.1 实现原理源码分析

所有的 Controller 执?都会通过?个调度器 DispatcherServlet 来实现,这?点可以从 Spring Boot 控制台的打印信息看出,如下图所示:

?所有?法都会执? DispatcherServlet 中的 doDispatch 调度?法,doDispatch 源码如下:

  1. protected void doDispatch(HttpServletRequest request, HttpServletResponse
  2. response) throws Exception {
  3. HttpServletRequest processedRequest = request;
  4. HandlerExecutionChain mappedHandler = null;
  5. boolean multipartRequestParsed = false;
  6. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  7. try {
  8. try {
  9. ModelAndView mv = null;
  10. Object dispatchException = null;
  11. try {
  12. processedRequest = this.checkMultipart(request);
  13. multipartRequestParsed = processedRequest != request;
  14. mappedHandler = this.getHandler(processedRequest);
  15. if (mappedHandler == null) {
  16. this.noHandlerFound(processedRequest, response);
  17. return;
  18. }
  19. HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.g
  20. etHandler());
  21. String method = request.getMethod();
  22. boolean isGet = HttpMethod.GET.matches(method);
  23. if (isGet || HttpMethod.HEAD.matches(method)) {
  24. long lastModified = ha.getLastModified(request, mapped
  25. Handler.getHandler());
  26. if ((new ServletWebRequest(request, response)).checkNo
  27. tModified(lastModified) && isGet) {
  28. return;
  29. }
  30. }
  31. // 调?预处理【重点】
  32. if (!mappedHandler.applyPreHandle(processedRequest, respon
  33. se)) {
  34. return;
  35. }
  36. // 执? Controller 中的业务
  37. mv = ha.handle(processedRequest, response, mappedHandler.g
  38. etHandler());
  39. if (asyncManager.isConcurrentHandlingStarted()) {
  40. return;
  41. }
  42. this.applyDefaultViewName(processedRequest, mv);
  43. mappedHandler.applyPostHandle(processedRequest, response,
  44. mv);
  45. } catch (Exception var20) {
  46. dispatchException = var20;
  47. } catch (Throwable var21) {
  48. dispatchException = new NestedServletException("Handler di
  49. spatch failed", var21);
  50. }
  51. this.processDispatchResult(processedRequest, response, mappedH
  52. andler, mv, (Exception)dispatchException);
  53. } catch (Exception var22) {
  54. this.triggerAfterCompletion(processedRequest, response, mapped
  55. Handler, var22);
  56. } catch (Throwable var23) {
  57. this.triggerAfterCompletion(processedRequest, response, mapped
  58. Handler, new NestedServletException("Handler processing failed", var23));
  59. }
  60. } finally {
  61. if (asyncManager.isConcurrentHandlingStarted()) {
  62. if (mappedHandler != null) {
  63. mappedHandler.applyAfterConcurrentHandlingStarted(processe
  64. dRequest, response);
  65. }
  66. } else if (multipartRequestParsed) {
  67. this.cleanupMultipart(processedRequest);
  68. }
  69. }
  70. }

从上述源码可以看出在开始执? Controller 之前,会先调? 预处理?法 applyPreHandle,?applyPreHandle ?法的实现源码如下:

  1. boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
  2. for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex
  3. = i++) {
  4. // 获取项?中使?的拦截器 HandlerInterceptor
  5. HandlerInterceptor interceptor = (HandlerInterceptor)this.intercep
  6. torList.get(i);
  7. if (!interceptor.preHandle(request, response, this.handler)) {
  8. this.triggerAfterCompletion(request, response, (Exception)null
  9. );
  10. return false;
  11. }
  12. }
  13. return true;
  14. }

从上述源码可以看出,在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor 并执?拦截器中的 preHandle ?法,这样就会咱们前?定义的拦截器对应上了,如下图所示:

此时?户登录权限的验证?法就会执?,这就是拦截器的实现原理。

1.4.2 拦截器小结

通过上?的源码分析,我们可以看出,Spring 中的拦截器也是通过动态代理和环绕通知的思想实现的,?体的调?流程如下:

1.5 扩展:统一访问前缀添加

所有请求地址添加 api 前缀:

  1. @Configuration
  2. public class AppConfig implements WebMvcConfigurer {
  3. // 所有的接?添加 api 前缀
  4. @Override
  5. public void configurePathMatch(PathMatchConfigurer configurer) {
  6. configurer.addPathPrefix("api", c -> true);
  7. }
  8. }

其中第?个参数是?个表达式,设置为 true 表示启动前缀。

二、统一异常处理

统?异常处理使?的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执?某个通知,也就是执?某个?法事件,具体实现代码如下:

  1. package com.example.demo.config;
  2.  
  3. import org.springframework.web.bind.annotation.ControllerAdvice;
  4. import org.springframework.web.bind.annotation.ExceptionHandler;
  5. import org.springframework.web.bind.annotation.ResponseBody;
  6.  
  7. import java.util.HashMap;
  8.  
  9. /**
  10. * 统一处理异常
  11. */
  12. @ControllerAdvice
  13. public class ErrorAdive {
  14.  
  15. @ExceptionHandler(Exception.class)
  16. @ResponseBody
  17. public HashMap<String, Object> exceptionAdvie(Exception e) {
  18. HashMap<String, Object> result = new HashMap<>();
  19. result.put("code", "-1");
  20. result.put("msg", e.getMessage());
  21. return result;
  22. }
  23.  
  24. @ExceptionHandler(ArithmeticException.class)
  25. @ResponseBody
  26. public HashMap<String, Object> arithmeticAdvie(ArithmeticException e) {
  27. HashMap<String, Object> result = new HashMap<>();
  28. result.put("code", "-2");
  29. result.put("msg", e.getMessage());
  30. return result;
  31. }
  32.  
  33. }

方法名和返回值可以?定义,重要的是 @ControllerAdvice 和 @ExceptionHandler 注解。

以上?法表示,如果出现了异常就返回给前端?个 HashMap 的对象,其中包含的字段如代码中定义的那样。

我们可以针对不同的异常,返回不同的结果,?以下代码所示:

  1. import org.springframework.web.bind.annotation.ControllerAdvice;
  2. import org.springframework.web.bind.annotation.ExceptionHandler;
  3. import org.springframework.web.bind.annotation.ResponseBody;
  4. import java.util.HashMap;
  5. @ControllerAdvice
  6. @ResponseBody
  7. public class ExceptionAdvice {
  8. @ExceptionHandler(Exception.class)
  9. public Object exceptionAdvice(Exception e) {
  10. HashMap<String, Object> result = new HashMap<>();
  11. result.put("success", -1);
  12. result.put("message", "总的异常信息:" + e.getMessage());
  13. result.put("data", null);
  14. return result;
  15. }
  16. @ExceptionHandler(NullPointerException.class)
  17. public Object nullPointerexceptionAdvice(NullPointerException e) {
  18. HashMap<String, Object> result = new HashMap<>();
  19. result.put("success", -1);
  20. result.put("message", "空指针异常:" + e.getMessage());
  21. result.put("data", null);
  22. return result;
  23. }
  24. }

当有多个异常通知时,匹配顺序为当前类及其子类向上依次匹配,案例演示:

在 UserController 中设置?个空指针异常,实现代码如下:

  1. @RestController
  2. @RequestMapping("/u")
  3. public class UserController {
  4. @RequestMapping("/index")
  5. public String index() {
  6. Object obj = null;
  7. int i = obj.hashCode();
  8. return "Hello,User Index.";
  9. }
  10. }

以上程序的执?结果如下:

此时若出现异常就不会报错了,代码会继续执行,但是会把自定义的异常信息返回给前端!

统一完数据返回格式后:

  1. package com.example.demo.common;
  2.  
  3. import org.springframework.web.bind.annotation.ControllerAdvice;
  4. import org.springframework.web.bind.annotation.ExceptionHandler;
  5. import org.springframework.web.bind.annotation.ResponseBody;
  6.  
  7. /**
  8. * 异常类的统一处理
  9. */
  10. @ControllerAdvice
  11. @ResponseBody
  12. public class ExceptionAdvice {
  13.  
  14. @ExceptionHandler(Exception.class)
  15. public Object exceptionAdvice(Exception e) {
  16. return AjaxResult.fail(-1, e.getMessage());
  17. }
  18. }

统一异常处理不用配置路径,是拦截整个项目中的所有异常。

三、统一数据返回格式

3.1 为什么需要统一数据返回格式

统?数据返回格式的优点有很多,比如以下几个:

  • ?便前端程序员更好的接收和解析后端数据接?返回的数据。
  • 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就?了,因为所有接?都是这样返回的。
  • 有利于项?统?数据的维护和修改。
  • 有利于后端技术部?的统?规范的标准制定,不会出现稀奇古怪的返回内容。

3.2 统一数据返回格式的实现

统?的数据返回格式可以使用 @ControllerAdvice + ResponseBodyAdvice接口 的方式实现,具体实现代码如下:

  1. import org.springframework.core.MethodParameter;
  2. import org.springframework.http.MediaType;
  3. import org.springframework.http.server.ServerHttpRequest;
  4. import org.springframework.http.server.ServerHttpResponse;
  5. import org.springframework.web.bind.annotation.ControllerAdvice;
  6. import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyA
  7. dvice;
  8.  
  9. import java.util.HashMap;
  10.  
  11. /**
  12. * 统一返回数据的处理
  13. */
  14. @ControllerAdvice
  15. public class ResponseAdvice implements ResponseBodyAdvice {
  16. /**
  17. * 内容是否需要重写(通过此?法可以选择性部分控制器和?法进?重写)
  18. * 返回 true 表示重写
  19. */
  20. @Override
  21. public boolean supports(MethodParameter returnType, Class converterTyp
  22. e) {
  23. return true;
  24. }
  25. /**
  26. * ?法返回之前调?此?法
  27. */
  28. @Override
  29. public Object beforeBodyWrite(Object body, MethodParameter returnType,
  30. MediaType selectedContentType,
  31. Class selectedConverterType, ServerHttpR
  32. equest request,
  33. ServerHttpResponse response) {
  34. // 构造统?返回对象
  35. HashMap<String, Object> result = new HashMap<>();
  36. result.put("state", 1);
  37. result.put("msg", "");
  38. result.put("data", body);
  39. return result;
  40. }
  41. }

统一处理后,此时所有返回的都是 json 格式的数据了。

若方法的返回类型为 String,统一数据返回格式封装后,返回会报错!?

转换器的问题,解决方案:

实际开发中这种统一数据返回格式的方式并不常用。因为它会将所有返回都再次进行封装,过于霸道了 ~

而通常我们会写一个统一封装的类,让程序猿在返回时统一返回这个类 (软性约束),例如:

  1. package com.example.demo.common;
  2.  
  3. import java.util.HashMap;
  4.  
  5. /**
  6. * 自定义的统一返回对象
  7. */
  8. public class AjaxResult {
  9. /**
  10. * 业务执行成功时进行返回的方法
  11. *
  12. * @param data
  13. * @return
  14. */
  15. public static HashMap<String, Object> success(Object data) {
  16. HashMap<String, Object> result = new HashMap<>();
  17. result.put("code", 200);
  18. result.put("msg", "");
  19. result.put("data", data);
  20. return result;
  21. }
  22.  
  23. /**
  24. * 业务执行成功时进行返回的方法
  25. *
  26. * @param data
  27. * @return
  28. */
  29. public static HashMap<String, Object> success(String msg, Object data) {
  30. HashMap<String, Object> result = new HashMap<>();
  31. result.put("code", 200);
  32. result.put("msg", msg);
  33. result.put("data", data);
  34. return result;
  35. }
  36.  
  37. /**
  38. * 业务执行失败返回的数据格式
  39. *
  40. * @param code
  41. * @param msg
  42. * @return
  43. */
  44. public static HashMap<String, Object> fail(int code, String msg) {
  45. HashMap<String, Object> result = new HashMap<>();
  46. result.put("code", code);
  47. result.put("msg", msg);
  48. result.put("data", "");
  49. return result;
  50. }
  51.  
  52. /**
  53. * 业务执行失败返回的数据格式
  54. *
  55. * @param code
  56. * @param msg
  57. * @return
  58. */
  59. public static HashMap<String, Object> fail(int code, String msg, Object data) {
  60. HashMap<String, Object> result = new HashMap<>();
  61. result.put("code", code);
  62. result.put("msg", msg);
  63. result.put("data", data);
  64. return result;
  65. }
  66. }

同时搭配统一数据返回格式:

  1. package com.example.demo.common;
  2.  
  3. import com.fasterxml.jackson.databind.ObjectMapper;
  4. import lombok.SneakyThrows;
  5. import org.springframework.core.MethodParameter;
  6. import org.springframework.http.MediaType;
  7. import org.springframework.http.server.ServerHttpRequest;
  8. import org.springframework.http.server.ServerHttpResponse;
  9. import org.springframework.web.bind.annotation.ControllerAdvice;
  10. import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
  11.  
  12. import java.util.HashMap;
  13.  
  14. /**
  15. * 统一数据返回封装
  16. */
  17. @ControllerAdvice
  18. public class ResponseAdvice implements ResponseBodyAdvice {
  19. @Override
  20. public boolean supports(MethodParameter returnType, Class converterType) {
  21. return true;
  22. }
  23.  
  24. @SneakyThrows
  25. @Override
  26. public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
  27. if (body instanceof HashMap) { // 本身已经是封装好的对象
  28. return body;
  29. }
  30. if (body instanceof String) { // 返回类型是 String(特殊)
  31. ObjectMapper objectMapper = new ObjectMapper();
  32. return objectMapper.writeValueAsString(AjaxResult.success(body));
  33. }
  34. return AjaxResult.success(body);
  35. }
  36. }

3.3 @ControllerAdvice 源码分析(了解)

通过对 @ControllerAdvice 源码的分析我们可以知道上?统?异常和统?数据返回的执?流程,我们先从 @ControllerAdvice 的源码看起,点击 @ControllerAdvice 实现源码如下:

从上述源码可以看出 @ControllerAdvice 派?于 @Component 组件,?所有组件初始化都会调用 InitializingBean 接?。

所以接下来我们来看 InitializingBean 有哪些实现类?在查询的过程中我们发现了,其中 Spring MVC中的实现?类是 RequestMappingHandlerAdapter,它??有?个?法 afterPropertiesSet() ?法,表示所有的参数设置完成之后执?的?法,如下图所示:

?这个?法中有?个 initControllerAdviceCache ?法,查询此?法的源码如下:

我们发现这个?法在执?是会查找使?所有的 @ControllerAdvice 类,这些类会被容器中,但发?某个事件时,调?相应的 Advice ?法,?如返回数据前调?统?数据封装,?如发?异常是调?异常的 Advice ?法实现。

以上就是详解SpringBoot中的统一功能处理的实现的详细内容,更多关于SpringBoot统一功能处理的资料请关注w3xue其它相关文章!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号