个人网站内容怎么写,免费php源码网,电商网站开发文档,智慧工厂管理系统背景
在本系列的前面一篇博客评论中#xff0c;有小伙伴指出#xff0c;API服务存在线程安全问题#xff1a;
https://blog.csdn.net/seawaving/article/details/122905199#comments_34477405
今天来确认下#xff0c;线程是否安全#xff1f;如不安全#xff0c;如何…背景
在本系列的前面一篇博客评论中有小伙伴指出API服务存在线程安全问题
https://blog.csdn.net/seawaving/article/details/122905199#comments_34477405
今天来确认下线程是否安全如不安全如何修复
回顾
先来回顾下先前的实现可能存在线程安全的是自己实现的过滤器链如下
package tech.abc.platform.cip.api.framework;import tech.abc.platform.cip.common.entity.ApiRequest;
import tech.abc.platform.cip.common.entity.ApiResponse;import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;/*** API服务过滤器链条** author wqliu* date 2022-2-12**/
public class ApiFilterChain {/*** 请求*/private ApiRequest request;/*** 响应*/private ApiResponse response new ApiResponse();/*** 过滤器集合*/private final ListApiFilter filters;/*** 过滤器迭代器*/private IteratorApiFilter iterator;public ApiFilterChain() {filters Collections.EMPTY_LIST;}public ApiFilterChain(ApiFilter... filters) {this.filters Arrays.asList(filters);}/*** 获取请求** return {link ApiRequest}*/public ApiRequest getRequest() {return this.request;}/*** 获取响应** return {link ApiResponse}*/public ApiResponse getResponse() {return this.response;}/*** 执行过滤** param request 请求* param response 响应*/public void doFilter(ApiRequest request, ApiResponse response) {// 如迭代器为空则初始化if (this.iterator null) {this.iterator this.filters.iterator();}// 集合中还有过滤器则继续往下传递if (this.iterator.hasNext()) {ApiFilter nextFilter this.iterator.next();nextFilter.doFilter(request, response, this);}// 将处理结果更新到属性中this.request request;this.response response;}/*** 重置*/public void reset() {this.request null;this.response null;this.iterator null;}}
该类是参照官方的MockFilterChain实现的源码如下
/** Copyright 2002-2018 the original author or authors.** Licensed under the Apache License, Version 2.0 (the License);* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an AS IS BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package org.springframework.mock.web;import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;/*** Mock implementation of the {link javax.servlet.FilterChain} interface.** pA {link MockFilterChain} can be configured with one or more filters and a* Servlet to invoke. The first time the chain is called, it invokes all filters* and the Servlet, and saves the request and response. Subsequent invocations* raise an {link IllegalStateException} unless {link #reset()} is called.** author Juergen Hoeller* author Rob Winch* author Rossen Stoyanchev* since 2.0.3* see MockFilterConfig* see PassThroughFilterChain*/
public class MockFilterChain implements FilterChain {Nullableprivate ServletRequest request;Nullableprivate ServletResponse response;private final ListFilter filters;Nullableprivate IteratorFilter iterator;/*** Register a single do-nothing {link Filter} implementation. The first* invocation saves the request and response. Subsequent invocations raise* an {link IllegalStateException} unless {link #reset()} is called.*/public MockFilterChain() {this.filters Collections.emptyList();}/*** Create a FilterChain with a Servlet.* param servlet the Servlet to invoke* since 3.2*/public MockFilterChain(Servlet servlet) {this.filters initFilterList(servlet);}/*** Create a {code FilterChain} with Filters and a Servlet.* param servlet the {link Servlet} to invoke in this {link FilterChain}* param filters the {link Filter}s to invoke in this {link FilterChain}* since 3.2*/public MockFilterChain(Servlet servlet, Filter... filters) {Assert.notNull(filters, filters cannot be null);Assert.noNullElements(filters, filters cannot contain null values);this.filters initFilterList(servlet, filters);}private static ListFilter initFilterList(Servlet servlet, Filter... filters) {Filter[] allFilters ObjectUtils.addObjectToArray(filters, new ServletFilterProxy(servlet));return Arrays.asList(allFilters);}/*** Return the request that {link #doFilter} has been called with.*/Nullablepublic ServletRequest getRequest() {return this.request;}/*** Return the response that {link #doFilter} has been called with.*/Nullablepublic ServletResponse getResponse() {return this.response;}/*** Invoke registered {link Filter Filters} and/or {link Servlet} also saving the* request and response.*/Overridepublic void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {Assert.notNull(request, Request must not be null);Assert.notNull(response, Response must not be null);Assert.state(this.request null, This FilterChain has already been called!);if (this.iterator null) {this.iterator this.filters.iterator();}if (this.iterator.hasNext()) {Filter nextFilter this.iterator.next();nextFilter.doFilter(request, response, this);}this.request request;this.response response;}/*** Reset the {link MockFilterChain} allowing it to be invoked again.*/public void reset() {this.request null;this.response null;this.iterator null;}/*** A filter that simply delegates to a Servlet.*/private static final class ServletFilterProxy implements Filter {private final Servlet delegateServlet;private ServletFilterProxy(Servlet servlet) {Assert.notNull(servlet, servlet cannot be null);this.delegateServlet servlet;}Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {this.delegateServlet.service(request, response);}Overridepublic void init(FilterConfig filterConfig) throws ServletException {}Overridepublic void destroy() {}Overridepublic String toString() {return this.delegateServlet.toString();}}}
这个类用在了我们API服务中如下
package tech.abc.platform.cip.api.service.impl;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tech.abc.platform.cip.api.exception.ApiException;
import tech.abc.platform.cip.api.framework.ApiFilterChain;
import tech.abc.platform.cip.api.framework.BasicValidateFilter;
import tech.abc.platform.cip.api.framework.BusinessFilter;
import tech.abc.platform.cip.api.framework.FrameworkValidateFilter;
import tech.abc.platform.cip.api.service.ApiRestService;
import tech.abc.platform.cip.common.entity.ApiRequest;
import tech.abc.platform.cip.common.entity.ApiResponse;
import tech.abc.platform.cip.enums.ApiServiceExecuteResultEnum;
import tech.abc.platform.cip.service.ApiServiceLogService;
import tech.abc.platform.common.exception.CustomException;import java.time.LocalDateTime;/*** * API服务技术框架实现** author wqliu* date 2022-2-10**/
Service
public class ApiRestServiceImpl implements ApiRestService {private ApiFilterChain filterChain;Autowiredprivate ApiServiceLogService apiServiceLogService;public ApiRestServiceImpl(FrameworkValidateFilter frameworkValidateFilter,BusinessFilter businessFilter, BasicValidateFilter basicValidateFilter) {filterChain new ApiFilterChain(basicValidateFilter, frameworkValidateFilter, businessFilter);}Overridepublic ApiResponse handle(ApiRequest apiRequest) {LocalDateTime receiveTime LocalDateTime.now();ApiResponse apiResponse new ApiResponse();try {filterChain.doFilter(apiRequest, apiResponse);apiResponse this.filterChain.getResponse();apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.SUCCESS.name());} catch (CustomException ex) {// 自定义异常处理apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.ERROR.name());apiResponse.setErrorCode(S00);apiResponse.setErrorMessage(ex.getMessage());} catch (ApiException ex) {// API异常处理apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.ERROR.name());apiResponse.setErrorCode(ex.getErrorCode());apiResponse.setErrorMessage(ex.getMessage());} catch (Exception ex) {// 非预期异常处理apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.ERROR.name());apiResponse.setErrorCode(S99);apiResponse.setErrorMessage(未定义异常 ex.getMessage());} finally {// 需要重置为下次请求服务filterChain.reset();// 记录日志apiServiceLogService.recordLog(apiRequest, apiResponse, receiveTime);}return apiResponse;}
}
分析
其中ApiRestServiceImpl使用的Service注解Spring的默认处理是单例模式ApiFilterChain是在构造函数中new出来的。线程安全的关键点在于ApiFilterChain持有和保存了ApiRequest和ApiResponse对象。
从代码层面分析了一下ApiFilterChain确实没有持有ApiRequest和ApiResponse对象的必要通过方法接收ApiRequest对象然后处理过程中修改ApiResponse对象都是引用没必要再保存一份。
至于当时为什么这么写大概是参照官方MockFilterChain写法高度信任而没有深度思考是否需要这么做。
验证
上面是从代码层面分析接下来就动手验证下线程安全问题是否存在借此也夯实下实际工作中很少用到的多线程并发及线程安全基础。
如何验证呢
其实思路也挺简单发起多次接口调用通过日志输出对象的HashCode看看HashCode是否是同一个就好了。
使用Postman来做接口测试调用平台内置的查询待处理消息的服务接口platform.message.query如下 注为方便测试把签名验证处理临时注释掉了因此入参sign属性随便写了个1111以通过非空验证。
在服务接口处理中添加日志输出这里用error而不是info目的是更容易找到如下 然后使用postman发起两次接口调用查看日志如下 可以看到无论是ApiRestService还是其所属的filterChain哈希码是完全相同的已经足以说明就是同一个对象因此存在线程安全问题。当接口同时收到多个请求时也就是多线程并发时持有的请求对象和响应对象会混乱掉是个大问题。
修正
既然问题已经确认那接下来就修正它。
在前面分析环节实际已经分析出来filterChain并不需要持有请求对象和响应对象去除掉后就从根本上解决了线程安全问题调整如下
package tech.abc.platform.cip.api.framework;import tech.abc.platform.cip.common.entity.ApiRequest;
import tech.abc.platform.cip.common.entity.ApiResponse;import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;/*** API服务过滤器链条** author wqliu* date 2022-2-12**/
public class ApiFilterChain {/*** 过滤器集合*/private final ListApiFilter filters;/*** 过滤器迭代器*/private IteratorApiFilter iterator;public ApiFilterChain() {filters Collections.EMPTY_LIST;}public ApiFilterChain(ApiFilter... filters) {this.filters Arrays.asList(filters);}/*** 执行过滤** param request 请求* param response 响应*/public void doFilter(ApiRequest request, ApiResponse response) {// 如迭代器为空则初始化if (this.iterator null) {this.iterator this.filters.iterator();}// 集合中还有过滤器则继续往下传递if (this.iterator.hasNext()) {ApiFilter nextFilter this.iterator.next();nextFilter.doFilter(request, response, this);}}/*** 重置*/public void reset() {this.iterator null;}}
测试功能正常如下
新的疑问点
在调整ApiFilterChain的过程中去除了存在线程安全的ApiRequest和ApiResponse对象同时发现还持有一个过滤器集合对象
private final List filters该对象在构造方法中初始化在接口服务的finally里执行reset清理工作不过reset方法是重置过滤器集合的迭代器而不是清空过滤器集合本身。
假设是多线程并发情况下A、B两个请求先后到达A请求处理结束了调用reset清空了过滤器的迭代器而B请求还在只走完了3个过滤器中的2个会不会有问题呢
按照前面的验证方法输出哈希码确定是同一个对象。
要做并发测试比较麻烦得辅助Jmeter等工具来实现了
这个地方高度怀疑存在线程安全问题比较彻底的解决办法就是把API服务变更为非单例模式。
彻底改造
接口服务对应的控制器中直接new对象不使用依赖注入如下
RestController
RequestMapping(/api)
Slf4j
public class ApiRestController {PostMapping(/rest)AllowAllpublic ResponseEntityApiResponse post(RequestBody ApiRequest apiRequest) {ApiRestService apiRestService new ApiRestServiceImpl();ApiResponse apiResponse apiRestService.handle(apiRequest);return new ResponseEntityApiResponse(apiResponse, HttpStatus.OK);}}ApiRestServiceImpl去除Service注解从而也不再是单例模式调整构造方法以及内部获取类的方式如下
package tech.abc.platform.cip.api.service.impl;import lombok.extern.slf4j.Slf4j;
import tech.abc.platform.cip.api.exception.ApiException;
import tech.abc.platform.cip.api.framework.ApiFilterChain;
import tech.abc.platform.cip.api.framework.BasicValidateFilter;
import tech.abc.platform.cip.api.framework.BusinessFilter;
import tech.abc.platform.cip.api.framework.FrameworkValidateFilter;
import tech.abc.platform.cip.api.service.ApiRestService;
import tech.abc.platform.cip.common.entity.ApiRequest;
import tech.abc.platform.cip.common.entity.ApiResponse;
import tech.abc.platform.cip.enums.ApiServiceExecuteResultEnum;
import tech.abc.platform.cip.service.ApiServiceLogService;
import tech.abc.platform.common.exception.CustomException;
import tech.abc.platform.common.utils.SpringUtil;import java.time.LocalDateTime;/*** * API服务技术框架实现** author wqliu* date 2022-2-10**/Slf4j
public class ApiRestServiceImpl implements ApiRestService {private ApiFilterChain filterChain;public ApiRestServiceImpl() {BusinessFilter businessFilter SpringUtil.getBean(BusinessFilter.class);FrameworkValidateFilter frameworkValidateFilter SpringUtil.getBean(FrameworkValidateFilter.class);BasicValidateFilter basicValidateFilter SpringUtil.getBean(BasicValidateFilter.class);filterChain new ApiFilterChain(basicValidateFilter, frameworkValidateFilter, businessFilter);}Overridepublic ApiResponse handle(ApiRequest apiRequest) {LocalDateTime receiveTime LocalDateTime.now();ApiResponse apiResponse new ApiResponse();try {filterChain.doFilter(apiRequest, apiResponse);apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.SUCCESS.name());} catch (CustomException ex) {// 自定义异常处理apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.ERROR.name());apiResponse.setErrorCode(S00);apiResponse.setErrorMessage(ex.getMessage());} catch (ApiException ex) {// API异常处理apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.ERROR.name());apiResponse.setErrorCode(ex.getErrorCode());apiResponse.setErrorMessage(ex.getMessage());} catch (Exception ex) {// 非预期异常处理apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.ERROR.name());apiResponse.setErrorCode(S99);apiResponse.setErrorMessage(未定义异常 ex.getMessage());} finally {ApiServiceLogService apiServiceLogService SpringUtil.getBean(ApiServiceLogService.class);// 记录日志apiServiceLogService.recordLog(apiRequest, apiResponse, receiveTime);}return apiResponse;}
}
运行发现多次接口调用进行测试每次接口调用无论是ApiRestService还是ApiFilterChain都不是同一个对象了因此线程肯定是安全的了。
开源平台资料
平台名称一二三开发平台 简介 企业级通用开发平台 设计资料[csdn专栏] 开源地址[Gitee] 开源协议MIT 如果您在阅读本文时获得了帮助或受到了启发希望您能够喜欢并收藏这篇文章为它点赞~ 请在评论区与我分享您的想法和心得一起交流学习不断进步遇见更加优秀的自己