【Java EE】统一功能返回

news2024/9/24 9:26:13

一、拦截器

1.1 拦截器的作用

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

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

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

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

@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public 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 {
    @Autowired
    LoginInterceptor loginInterceptor;

    private List<String> excludePaths = Arrays.asList(
            "/user/login",
            "/**/*.js",
            "/**/*.css",
            "/**/*.png",
            "/**/*.html"
    );
    @Override
    public 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 {

    @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) {
        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 {
    @Autowired
    private ObjectMapper objectMapper;
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @SneakyThrows
    @Override
    public 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 {
    @ExceptionHandler
    public Object handler(Exception e) {
        return Result.failed(e.getMessage());
    }

    @ExceptionHandler
    public Object handler(ArithmeticException e) {
        return Result.failed(e.getMessage());
    }

    @ExceptionHandler
    public 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 AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {
            @Override
        	public void afterPropertiesSet() {
        		// Do this first, it may add ResponseBody advice beans
        		initControllerAdviceCache();
        		initMessageConverters();
                // ...
            }
            private void initControllerAdviceCache() {
                // 如果这个Bean是空,直接返回
        		if (getApplicationContext() == null) {
        			return;
        		}
        
                // 获得所有被@ControllerAdvice标注的Bean
        		List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
        
        		List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
                //...
        	}
        }
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
		implements ApplicationContextAware, InitializingBean {
    @Override
	public void afterPropertiesSet() {
		// Do this first, it may add ResponseBodyAdvice beans
		initExceptionHandlerAdviceCache();
		initMessageConverters();
    }

    // 如果这个Bean是空,直接返回
	private void initExceptionHandlerAdviceCache() {
		if (getApplicationContext() == null) {
			return;
		}

        // 获得所有被@ControllerAdvice标注的Bean
		List<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 {
    @Nullable
	private 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.coloradmin.cn/o/1920917.html

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

相关文章

嵌入式开发过程中,常见报错以及解决方法

编写不易&#xff0c;仅供学习&#xff0c;参考谢谢&#xff0c;还望理解。 #常见报错 文件最后一行没有新行 翻译&#xff1a;文件的最后一行结束时没有新行 解决方法&#xff1a;定位到&#xff0c;提示报错的 .h 文件 报错行 &#xff0c;加上一个新行 函数定义时与官方提…

体育资讯小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;球员管理&#xff0c;教练管理&#xff0c;赛事日程管理&#xff0c;赛事类型管理&#xff0c;联赛积分榜管理 开发系统&#xff1a;Windows 架构模式&#xff1a;SSM JDK版本&a…

(超简单)如何将heic格式转化成jpg,试试这6个heic转jpg小技巧

如何将heic格式转化成jpg&#xff1f;喜欢拍摄照片的小伙伴们可能对heic格式图片比较熟悉&#xff0c;heic格式是一种比较高效的图片格式&#xff0c;图片质量高&#xff0c;并且体积小。但heic种格式也存在一些问题&#xff0c;首先&#xff0c;由于它的其兼容性较差&#xff…

软考高项论文一次过经验分享

软考高项考试改革后一年考一次&#xff0c;有多少人的备考计划被打乱&#xff0c;相比较起来&#xff0c;去年我考的时候刚刚赶上机考&#xff0c;那是一个吐血~猝不及防&#xff0c;清楚的记得那会儿考完出来听到耳边哀声一片&#xff0c;虽然我过了&#xff0c;但是真的紧张的…

PGCCC|【PostgreSQL】PCA+PCP+PCM等IT类认证申报个税退税指南

小编特将PostgreSQL证书申报个税退税流程&#xff0c;编辑成文&#xff0c;供大家申报参考哦~ 1.申报专项附加扣除 第一步&#xff1a;打开个人所得税APP&#xff0c;选择“专项附加扣除填报”&#xff1a; 第二步&#xff1a;“扣除年度”选择您要申报的年度&#xff0c;并…

【RHCE】计划任务的练习题

1. 使⽤ at 命令计划从现在起三分钟后运⾏⼀项作业。该作业必须将 date 命令的输出保存 ⾄ /root/myjob.txt [rootlocalhost ~]# at now 3 min warning: commands will be executed using /bin/sh at> date > /root/myjob.txt at> <EOT>2. 使⽤ at 命令以交互…

2024【大模型的实战应用深入解析】(非常详细)零基础入门到精通,收藏这一篇就够了

ChatGPT从年初炸裂的诞生&#xff0c;引领了新一轮AI的浪潮——大模型。说来惭愧&#xff0c;其实从去年开始就有人提大模型&#xff0c;那时候大厂都在内部做&#xff0c;谷歌facebook腾讯阿里等等&#xff0c;我当时其实持反对意见的&#xff0c;每家都烧这么多钱&#xff0c…

Laravel :如何将Excel文件导入数据库

文章目录 一、前提二、使用2.1、新建一个导入文件2.2、新建一个控制器和方法,调用导入文件2.3、 新建一个页面&#xff0c;支持文件上传 一、前提 想要将excel内容入库&#xff0c;laravel有扩展可以使用,常用的扩展是maatwebsite/excel&#xff0c;安装步骤参考上一篇&#x…

FastGPT+OneAI接入网络模型

文章目录 FastGPT连接OneAI接入网络模型1.准备工作2.开始部署2.1下载 docker-compose.yml2.2修改docker-compose.yml里的参数 3.打开FastGPT添加模型3.1打开OneAPI3.2接入网络模型3.3重启服务 FastGPT连接OneAI接入网络模型 1.准备工作 本文档参考FastGPT的官方文档 主机ip接…

SAS:标记CRF时是否持续用ENRF还是ENRTPT?

背景&#xff1a; 在QC ACRF时发现针对是否持续有不同的标记方式&#xff0c;比如在不良事件中用AEENRF&#xff0c;在病史中用MHENRTPT&#xff0c;想知道为何会有此差别&#xff1f; 结论&#xff1a; 根据“是否持续”的时间是一个确定的时间点还是一个持续的时间段&#xf…

如何评价2023年辽宁省数学建模竞赛A题B题?

本文文章较长&#xff0c;阅读时间约为5分钟&#xff0c;点击目录条目可以快速跳转 完成进度情况 2023年辽宁省大学生数学建模竞赛A题完整论文和代码目前我已经完成了&#xff0c;论文包括摘要、问题重述、问题分析、模型假设、符号说明、模型的建立和求解&#xff08;问题1推…

攻防世界 string

国际惯例file,checksec一下&#xff0c;发现是64位的elf文件&#xff0c;不可修改got表&#xff0c;栈溢出保护开启&#xff0c;nx不可执行&#xff0c;没开地址随机化 这道题的流程比较复杂&#xff0c;交互较多&#xff0c;所以我们需要先分析清楚整个流程 拖入64位ida&…

重磅!新公司法正式实施,这些变化你必须知道! ️

新公司法来了&#xff01;企业设立和经营必知的关键变动 &#x1f3db;️&#x1f680; 大家好&#xff0c;我是猫头虎&#xff0c;科技自媒体博主。今天我们来聊聊一件大事——新公司法的实施&#xff0c;这对企业设立和经营带来了哪些重大影响&#xff1f;跟着我&#xff0c…

AI视频教程下载-1小时ChatGPT提示基础课程

Hour ChatGPT Prompting Basics Course (2024) 从“玩具”到“武器” 这是一门关于ChatGPT提示的入门级课程&#xff0c;无需任何门槛或基础知识。如果你是职场专业人士&#xff0c;只想将ChatGPT作为一种工具来提高工作效率、做出决策&#xff0c;或在日常生活中提供便利和建…

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第一篇 嵌入式Linux入门篇-第二十八章 借助U盘或TF卡拷贝程序到开发板上

i.MX8MM处理器采用了先进的14LPCFinFET工艺&#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53&#xff0c;单核Cortex-M4&#xff0c;多达五个内核 &#xff0c;主频高达1.8GHz&#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…

06.TMS570LC43入门指南——中断操作

06.TMS570LC43入门指南——中断操作 文章目录 06.TMS570LC43入门指南——中断操作一、简介二、中断&#xff08;VIM&#xff09;介绍2.1 VIM架构2.2 CPU 中断处理2.3 VIM中断通道映射2.4 中断请求默认分配 三、项目实现3.1 硬件部分3.2 软件部分3.2.1 HALCoGen 配置3.2.2 CCS 配…

国产麒麟、uos在线编辑word文件并控制编辑区域(局部编辑)

windows系统也适用&#xff0c;该插件可同时支持windows和国产系统 在实际项目开发中&#xff0c;以下场景可能会用到Word局部编辑功能&#xff1a; 合同审批公文流转策划设计报告汇签单招投标&#xff08;标书文件&#xff09;其他&#xff0c;有模板且需要不同人员协作编辑…

Ubuntu22.04安装NIVIDIA显卡驱动总结

1.首先在安装驱动时需要判断系统有无GPU以及GPU的型号 可以参考这篇文章&#xff1a; https://blog.51cto.com/u_13171517/8814753#:~:textubuntu%20%E7%B3%BB%E7%BB%9F%20%E6%80%8E%E4%B9%88%E5%88%A4%E6%96%AD%E7%B3%BB%E7%BB%9F%E6%9C%89%E6%B2%A1%E6%9C%89GPU%201%20%E6%…

2024骨传导耳机哪款值得买?健身人士说这五款骨传导耳机好~

在追求健康生活与高品质音频体验的今天&#xff0c;骨传导蓝牙耳机以其独特的魅力&#xff0c;引领了一场听觉革命。它巧妙利用骨骼传递声音&#xff0c;既保护了脆弱的耳膜&#xff0c;又带来了前所未有的佩戴自由。然而&#xff0c;在众多选择面前&#xff0c;如何慧眼识珠&a…