【Java EE】统一功能返回

一、拦截器

1.1 拦截器的作用

在对于数据库进行增删查改的时候,如果当前页面不检查用户是否登录,然后就能操作成功是不合理的,解决方法有两个:

  1. 对于已经写好的每个接口都加上一个判断,从Session中获取用户信息,看UserInfo是不是空的,如果是空的,后端就将返回值设为一个错误的状态,然后前端根据这个状态强制跳转到用户登录界面。

这个状态最好是一个类,里面装上:

    1. 状态码(code)
    2. 状态信息(message)
    3. 返回的数据(data)
  1. 可以使用拦截器,对于所有需要用户登录的界面进行拦截。代码如下:

@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HttpSession session = request.getSession();UserInfo userInfo = (UserInfo) session.getAttribute(Constants.SESSION_USER_INFO);if (userInfo == null || userInfo.getId() <= 0) {log.info("用户未登录!");response.setStatus(401);return false;}return true;}
}
/*** 拦截器的配置信息*/
//@Configuration
public class WebConfig implements WebMvcConfigurer {@AutowiredLoginInterceptor loginInterceptor;private List<String> excludePaths = Arrays.asList("/user/login","/**/*.js","/**/*.css","/**/*.png","/**/*.html");@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns(excludePaths);}
}

1.2 使用拦截器的注意事项

  1. 拦截器的代码很短,主要把握好两个部分:

    1. 配置拦截器(招保安)
    2. 定义拦截器(招到以后安排哪部分的安保工作)

添加前置、后置、视图渲染的拦截工作

  1. 拦截器要加@component注解注册为Bean,方便Spring扫描得到

1.3 @DispatcherServlet源码分析

1. 继承关系

  1. DispatcherServlet继承 FrameworkServlet

  2. FrameworkServlet 继承 HttpServletBean

  3. HttpServletBean 继承 HttpServlet

  4. HttpServlet继承 GenericServlet

  5. enericServlet实现 Servlet(是一个接口)

  6. Servlet的声明周期有三部分:

    1. init()
    2. service()
    3. destroy()

2. DispatcherServlet

是从日志中发现了DispatcherServlet这个类。

img

3. initStrategies

	protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}
  1. Multipart是和文件相关的,Resolver是解析器
  2. 本地初始化解析器
  3. 主体初始化解析器
  4. 处理器初始化映射
  5. 处理器初始化适配器
  6. 处理器初始化

4. doDispatch

// 主要代码
// 获得处理器
mappedHandler = getHandler(processedRequest);
// 获得处理器适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// 执行拦截器(预处理阶段),如果返回false就立即停止(被拦截)
if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;
}
// 使用处理器适配器对于处理器进行调度,返回ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());// 拦截器的后处理
mappedHandler.applyPostHandle(processedRequest, response, mv);// 对于所有资源进行整理
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
4.1. doDispatch-applyPreHandle
	/*** Apply preHandle methods of registered interceptors.* @return {@code true} if the execution chain should proceed with the* next interceptor or the handler itself. Else, DispatcherServlet assumes* that this interceptor has already dealt with the response itself.*/boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {for (int i = 0; i < this.interceptorList.size(); i++) {HandlerInterceptor interceptor = this.interceptorList.get(i);if (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i;}return true;}

这是一个拦截器链,然后遍历每一个拦截器,对于各个拦截器进行拦截的前置处理。

4.2. doDispatch-applyPostHandle
	/*** Apply postHandle methods of registered interceptors.*/void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)throws Exception {for (int i = this.interceptorList.size() - 1; i >= 0; i--) {HandlerInterceptor interceptor = this.interceptorList.get(i);interceptor.postHandle(request, response, this.handler, mv);}}

遍历拦截器链上的每一个拦截器,对于其进行后置拦截处理

二、统一数据返回格式

2.1 快速入门

对于每个接口,如果都是一样的返回格式,那么对于前后端交流是非常友好的。

实现代码:

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {return Result.success(body);}
}
  1. supports方法是决定要不要使用统一返回格式:

    1. true——使用
    2. false——不使用
  2. beforeBodyWrite方法:在写入响应正文前需要做的事情。

    1. 此处就是对于所有的返回正文进行了一次封装,Result就是一个常见的返回类,有code,msg,data等信息

2.2 存在问题

当返回String的时候,会报500的错误。

img

img

原因?源代码整起

先根据错误堆栈信息打个断点试试

img

img

img

子类:

img

追根溯源,

就是因为write方法会传进去一个Result类型的参数给addDefaultHeaders,

而addDefaultHeaders方法是被子类重写过的,

所以会调用子类重写后的方法,(父类中接收这个的参数是一个泛型,对应到这个情况中正好是Result类型)

重写的方法中定死了这个参数是一个String类型,所以会发生类型转换异常。

所以需要给String做一个单独的处理!

处理方法就是使用SpringBoot内置的Jackson对于信息进行序列化,防止源码中的这种错误。

代码实现:

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Autowiredprivate ObjectMapper objectMapper;@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@SneakyThrows@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {// 对于String类型的响应正文进行序列化if (body instanceof String) {return objectMapper.writeValueAsString(body);}return Result.success(body);}
}

三、统一异常处理

统一异常也是一个面向切面编程思想的实现,其对于所有发生异常的接口进行捕获。

主要使用到的注解有三个:

  1. @ResponseBody

类注解,因为返回的仍然是数据,是响应正文。

  1. @ControllerAdvice

类注解,统一功能返回都是用这个注解。

  1. @ExceptionHandler

方法注解。

实现代码:

@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {@ExceptionHandlerpublic Object handler(Exception e) {return Result.failed(e.getMessage());}@ExceptionHandlerpublic Object handler(ArithmeticException e) {return Result.failed(e.getMessage());}@ExceptionHandlerpublic Object handler(NullPointerException e) {return Result.failed(e.getMessage());}
}

3.1 @ControlelrAdvice为什么能够对代码做到不侵入?源码整起

查看@ControlelrAdvice注解的源代码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {// ......
}

@ControlelrAdvice派生于@Compoent,所以可以不加五大注解也能够被Spring找到。

再看DispatcherServlet的源码,能够找到异常处理器的初始化,工作过程:

	protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}	

其中重点关注initHandlerAdapters(context)initHandlerExceptionResolvers(context)这两个方法。

private void initHandlerAdapters(ApplicationContext context) {this.handlerAdapters = null;if (this.detectAllHandlerAdapters) {// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.Map<String, HandlerAdapter> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerAdapters = new ArrayList<>(matchingBeans.values());// We keep HandlerAdapters in sorted order.AnnotationAwareOrderComparator.sort(this.handlerAdapters);}}else {try {HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);this.handlerAdapters = Collections.singletonList(ha);}catch (NoSuchBeanDefinitionException ex) {// Ignore, we'll add a default HandlerAdapter later.}}// Ensure we have at least some HandlerAdapters, by registering// default HandlerAdapters if no other adapters are found.if (this.handlerAdapters == null) {this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}}
private void initHandlerExceptionResolvers(ApplicationContext context) {this.handlerExceptionResolvers = null;if (this.detectAllHandlerExceptionResolvers) {// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());// We keep HandlerExceptionResolvers in sorted order.AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);}}else {try {HandlerExceptionResolver her =context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);this.handlerExceptionResolvers = Collections.singletonList(her);}catch (NoSuchBeanDefinitionException ex) {// Ignore, no HandlerExceptionResolver is fine too.}}// Ensure we have at least some HandlerExceptionResolvers, by registering// default HandlerExceptionResolvers if no other resolvers are found.if (this.handlerExceptionResolvers == null) {this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}}

可以看到这两个方法是极其相似的,都是从Bean工厂中提取Bean,如果没有提取到,然后又使用其他手段去获得Bean。

从Bean工厂获得Bean的类型各不相同,分别是:

  1. HandlerAdapter.class
  2. HandlerExceptionResolver.class
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapterimplements BeanFactoryAware, InitializingBean {@Overridepublic void afterPropertiesSet() {// Do this first, it may add ResponseBody advice beansinitControllerAdviceCache();initMessageConverters();// ...}private void initControllerAdviceCache() {// 如果这个Bean是空,直接返回if (getApplicationContext() == null) {return;}// 获得所有被@ControllerAdvice标注的BeanList<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();//...}}
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolverimplements ApplicationContextAware, InitializingBean {@Overridepublic void afterPropertiesSet() {// Do this first, it may add ResponseBodyAdvice beansinitExceptionHandlerAdviceCache();initMessageConverters();}// 如果这个Bean是空,直接返回private void initExceptionHandlerAdviceCache() {if (getApplicationContext() == null) {return;}// 获得所有被@ControllerAdvice标注的BeanList<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());for (ControllerAdviceBean adviceBean : adviceBeans) {Class<?> beanType = adviceBean.getBeanType();if (beanType == null) {throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);}// 获得异常处理方法的处理器ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);if (resolver.hasExceptionMappings()) {this.exceptionHandlerAdviceCache.put(adviceBean, resolver);}if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {this.responseBodyAdvice.add(adviceBean);}}// ...}
}
public class ExceptionHandlerMethodResolver {@Nullableprivate Method getMappedMethod(Class<? extends Throwable> exceptionType) {List<Class<? extends Throwable>> matches = new ArrayList<>();for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {if (mappedException.isAssignableFrom(exceptionType)) {matches.add(mappedException);}}if (!matches.isEmpty()) {if (matches.size() > 1) {// 如果匹配到多个异常会进行排序,取第一个matches.sort(new ExceptionDepthComparator(exceptionType));}return this.mappedMethods.get(matches.get(0));}else {return NO_MATCHING_EXCEPTION_HANDLER_METHOD;}}
}

他们两个都实现了InitializingBean接口,所以都有afterPropertiesSet这个方法,能够执行一些额外的初始化。

public interface InitializingBean {void afterPropertiesSet() throws Exception;
}

这个方法是在bean的依赖属性被注入之后(此时Spring Application Context已经有了一些Bean),执行一些额外的初始化

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/45415.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSS 中的 ::before 和 ::after 伪元素

目录 一、CSS 伪元素 二、::before ::after 介绍 1、::before 2、::after 3、content 常用属性值 三、::before ::after 应用场景 1、设置统一字符 2、通过背景添加图片 3、添加装饰线 4、右侧展开箭头 5、对话框小三角 6、插入icon图标 一、CSS 伪元素 CSS伪元…

Spring Boot:连接MySQL错误Public Key Retrieval is not allowed

环境&#xff1a; MySQL版本&#xff1a;8.0.17 SpringBoot版本&#xff1a;2.5.15 解决 解决方式很简单&#xff0c;在数据库配置连接字符串spring.datasource.url末尾添加&allowPublicKeyRetrievaltrue即可&#xff0c;如下图&#xff1a; 重新启动&#xff0c;恢复正常…

kafka面试题(基础-进阶-高阶)

目录 Kafka 基础篇 1.Kafka 的用途有哪些?使用场景如何? 2.Kafka 中的ISR、AR 又代表什么?ISR 的伸缩又指什么 3.Kafka 中的 HW、LEO、LSO、LW 等分别代表什么? 4.Kafka 中是怎么体现消息顺序性的? 5.Kafka 中的分区器、序列化器、拦截器是否了解?它们之间的处理顺序…

django招聘数据分析与可视化管理系统-计算机毕业设计源码55218

摘要 随着互联网的迅速发展&#xff0c;招聘数据在规模和复杂性上呈现爆炸式增长&#xff0c;对数据的深入分析和有效可视化成为招聘决策和招聘管理的重要手段。本论文旨在构建一个基于Python的招聘数据分析与可视化管理系统。 该平台以主流招聘平台为数据源&#xff0c;利用Py…

四元数和旋转矩阵的求导

四元数的导数 四元数关于时间求导的推导 本质&#xff1a; 求导的定义是函数值的微增量关于自变量的微增量的极限。表示旋转的单位四元数作差后&#xff0c;其不再是单位四元数&#xff0c;也就不是旋转四元数了。单位四元数作差后&#xff0c;得到是被减四元数所在空间的切空…

直观易用的大模型开发框架LangChain,你会了没?

目录 简介基本组件小试牛刀关于沟通代码案例&#xff1a;调用Embedding、Completion、Chat Model总结 目前LangChain框架在集团大模型接入手册中的学习案例有限&#xff0c;为了让大家可以快速系统地了解LangChain大模型框架并开发&#xff0c;产出此文章。本文章包含了LangCha…

视频监控汇聚平台LntonCVS视频集中存储平台解决负载均衡的方案

随着技术的进步和企业对监控需求的增加&#xff0c;视频监控系统规模不断扩大&#xff0c;接入大量设备已成常态化挑战。为应对这一挑战&#xff0c;视频汇聚系统LntonCVS视频融合平台凭借其卓越的高并发处理能力&#xff0c;为企业视频监控管理系统提供可靠的负载均衡服务保障…

vue3实现一个接球小游戏

使用 Vue3 ts canvas 实现一个web端接球小游戏&#xff0c;主要交互包括&#xff1a;操作键盘的【<】【>】来滑动手柄去接球、游戏开始、游戏暂停、游戏继续、游戏重新开始、游戏失败&#xff0c;用到的知识包括&#xff1a;ts、canvas绘图、事件监听器的添加与移除&am…

优化 Java 数据结构选择与使用,提升程序性能与可维护性

优化 Java 数据结构选择与使用&#xff0c;提升程序性能与可维护性 引言 在软件开发中&#xff0c;数据结构的选择是影响程序性能、内存使用以及代码可维护性的关键因素之一。Java 作为一门广泛使用的编程语言&#xff0c;提供了丰富的内置数据结构&#xff0c;如数组、链表、…

JavaSE 面向对象程序设计进阶 IO 综合练习 利用糊涂包生成假数据 随机点名器 登录案例

目录 生成假数据 利用糊涂包生成假数据 随机点名器 综合练习 生成假数据 制造假数据 制造假数据也是开发中的一个能力 在各个网上爬取数据 这是其中一个方法 爬取网站中的内容 import cn.hutool.core.io.FileUtil;import java.io.IOException; import java.io.InputSt…

编程语言的选择:如何根据项目需求找到最合适的语言

在软件开发过程中&#xff0c;选择合适的编程语言是项目成功的关键之一。不同的编程语言拥有各自独特的特性和优势&#xff0c;适用于不同类型的项目。本文将从项目需求、团队技能、语言特性等多个角度&#xff0c;探讨如何为特定项目找到最合适的编程语言。 明确项目需求 首…

n2. Web相关工具

Web相关工具 1. http协议状态码2. Web相关工具2.1 links2.2 wget2.3 curl 3. httpd的压力测试工具 1. http协议状态码 http协议状态码分类 1xx&#xff1a;100-101 信息提示 2xx&#xff1a;200-206 成功 3xx&#xff1a;300-307 重定向 4xx&#xff1a;400-415 错误类信息&a…

昇思25天学习打卡营第24天 | LSTM+CRF序列标注

内容介绍&#xff1a; 序列标注指给定输入序列&#xff0c;给序列中每个Token进行标注标签的过程。序列标注问题通常用于从文本中进行信息抽取&#xff0c;包括分词(Word Segmentation)、词性标注(Position Tagging)、命名实体识别(Named Entity Recognition, NER)等。以命名实…

【JavaScript 算法】二分查找:快速定位目标元素

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、算法原理二、算法实现三、应用场景四、优化与扩展五、总结 二分查找&#xff08;Binary Search&#xff09;是一种高效的查找算法&#xff0c;适用于在有序数组中快速定位目标元素。相比于线性查找&#xff0c;二分查找…

护(H)网(W)行动正当时:你对HW知多少,一文带你全面了解护网行动

引言&#xff1a;2016年我国发布了《网络安全法》&#xff08;于2017年6月1日正式生效&#xff09;&#xff0c;明确规定了关键信息基础设施的运营者必须制定网络安全事件应急预案&#xff0c;并定期进行演练&#xff0c;为HW行动的开展提供了法律依据&#xff0c;通过红蓝对抗…

嵌入式裸机开发与 Linux 开发

引言 嵌入式系统在现代电子设备中占有重要地位&#xff0c;其开发模式主要分为裸机开发和基于操作系统&#xff08;如 Linux&#xff09;的开发。本文将详细介绍嵌入式裸机开发和 Linux 开发的特点、优缺点&#xff0c;并进行对比分析&#xff0c;以帮助读者更好地理解和选择合…

js 移动数组元素的几个方法

位置交换 /*** param {any[]} arr - 原始数组。* param {number} fromIndex - 当前元素所在位置索引。* param {number} toIndex - 移动到交换的位置索引。* returns {any[]} 返回修改后的数组。*/ const swapItem function(arr, fromIndex, toIndex) {arr[toIndex] arr.spl…

35、php 实现构建乘积数组、正则表达式匹配

题目&#xff1a; uniapp-v3是基于vue3语法的&#xff0c;在hbuilderx中运行即可 Project setup npm install Compiles and hot-reloads for development npm run serve Compiles and minifies for production npm run build 在HBuilderX中导入src文件打包;打包H5手机版可以…

Unity 中使用状态机模式来管理UI

1. 清晰的状态管理 状态机模式允许你以结构化的方式管理不同的UI状态。每个状态&#xff08;比如主菜单、设置菜单、游戏中界面等&#xff09;都有其独立的行为和属性&#xff0c;这使得管理复杂UI逻辑变得更加清晰和可维护。 2. 简化的状态切换 状态机模式可以简化不同UI状…

报表控件DevExpress Reporting中文教程 - 如何创建穿透钻取报表?

DevExpress Reporting是.NET Framework下功能完善的报表平台&#xff0c;它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集&#xff0c;包括数据透视表、图表&#xff0c;因此您可以构建无与伦比、信息清晰的报表。 钻取报表允许用户通过单击主/活动报表文档中的…