您的位置:首页 > 科技 > IT业 > seo优化网站多少钱_宿迁房产_百度推广登录入口官网网_济南优化哪家好

seo优化网站多少钱_宿迁房产_百度推广登录入口官网网_济南优化哪家好

2024/11/17 23:57:55 来源:https://blog.csdn.net/zhengzhaoyang122/article/details/142286519  浏览:    关键词:seo优化网站多少钱_宿迁房产_百度推广登录入口官网网_济南优化哪家好
seo优化网站多少钱_宿迁房产_百度推广登录入口官网网_济南优化哪家好

优质博文:IT-BLOG-CN

此篇文章是基于 Tomcat Request Cookie 丢失问题 文章的一个延续

一、Request 跨线程访问问题

问题代码摘要

为了方便选择发起get请求,然后只需要传递一个参数就行,核心步骤是要把request传递到异步线程里面去,调用getParameter再次获取对应入参。

@GetMapping("/getTest")
public String getTest(HttpServletRequest request) {String age = request.getParameter("age");System.out.println("age=" + age);new Thread(() -> {try {Thread.sleep(200);} catch (InterruptedException e) {throw new RuntimeException(e);}String age1 = request.getParameter("age");System.out.println("age1=" + age1);}).start();return "success";
}

获取请求:http://127.0.0.1:8080/getTest?age=18

从控制台你可以看到这样的输出:

age=18
age1=null

当再次发起调用,会看到控制台的输出是这样的:

age=18
age1=null
age=null
age1=null

和上面的问题类似,这里也有一个类似的方法:getParameter

@Override
public String getParameter(String name) {if (!parametersParsed) {parseParameters();}return coyoteRequest.getParameters().getParameter(name);
}

parametersParsed参数初始化是false进入parseParameters()方法解析参数,将age=18放到paramHashValues这个Map容器中。 后续的重复请求就会省略解析参数的操作。

parseParameters()方法执行完成之后,接着从前面的 paramHashValues容器里面把age对应的18返回回去:

public String getParameter(String name) {handleQueryParameters(); // 这里也需要注意,存在一个类似的逻辑ArrayList<String> values = paramHashValues.get(name);if (values != null) {if (values.size() == 0) {return "";}return values.get(0); // 返回的是 age 的值 18} else {return null;}
}

这里重点看下handleQueryParameters方法的实现:

public void handleQueryParameters() {if (didQueryParameters) {return;}didQueryParameters = true;if (queryMB == null || queryMB.isNull()) {return;}try {decodedQuery.duplicate(queryMB);} catch (IOException e) {e.printStackTrace();}processParameters(decodedQuery, queryStringCharset);
}

这个方法在parseParamters中也会调用:

protected void parseParamters() {parametersParsed = true;Parameters parameters = coyoteRequest.getParameters();boolean success = false;try {// Set this every time in case limit has been changed via JMXparameters.setLimit(getConnector().getMaxParameterCount());//...Charset charset = getCharset();boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();parameters.setCharset(charset);//...// 这里也调用了 handleQueryParameters 方法。parameters.handleQueryParameters();}
}

handleQueryParameters方法才是真正解析参数的方法,为了防止重复解析它加入了这样的逻辑:

if (didQueryParameters) {return;
}didQueryParameters = true;

didQueryParameters初始为false,随后被设置为true。这个和之前的业务逻辑一致,入参解析一次并存放至Map中。

方法叫做recycle,表明是循环再利用,在这里面会把存放参数的Map清空,把didQueryParameters再次设置为了falseorg.apache.tomcat.util.http.Parameters#recycle方法如下:

public void recycle() {parameterCount = 0;paramHashValues.clear();didQueryParameters = false;charset = DEFAULT_BODY_CHARSET;decodeQuery.recycle();parseFailedReason = null;
}

而当你用同样的手段去观察parametersParsed参数,也就是这个参数的时候,会发现它也有一个recycle方法:org.apache.catalina.connector.Request#recycle

public void recycle() {internalDispatcherType = null;requestDispatcherPath = null;authType = null;inputBuffer.recycle();usingInputStream = false;usingReader = false;userPrincipal = null;parametersParsed = false;
}

由于我们在异步线程里面还触发了一次getParameter方法:但是getTest方法已经完成了响应,这个时候Request可能已经完成了回收。为了避免这个“可能”,我添加了sleep,保证request完成回收。

@GetMapping("/getTest")
public String getTest(HttpServletRequest request) {String age = request.getParameter("age");System.out.println("age=" + age);new Thread(() -> {try {Thread.sleep(200);} catch (InterruptedException e) {throw new RuntimeException(e);}String age1 = request.getParameter("age");System.out.println("age1=" + age1);}).start();return "success";
}

再次触发handleQueryParameters的时候,didQueryParameters由于被recycle了,所以变成了false

然后执行解析的逻辑,把didQueryParameters设置为true

但是,我们可以看到,此时查询的内容却没有了,是个null:这里的null也很好理解,肯定是随着调用结束,被recycle了。

为什么再次发起请求的时候,都返回null。

因为TomcatRquest使用的是池化思想,如果你拿到的是上一次的Request请求,那么因为在异步线程里面调用getParameter的时候,把didQueryParameters设置为true了。

但是异步线程里面的调用,超出了request的生命周期,所以并不会再次触发requestrecycle相关操作,因此这个request拿来复用的时候didQueryParameters还是true

所以,第二次请求的入参有值的,但是没用啊,didQueryParameterstrue,程序直接return了,不会去解析你的入参:

二、Request 的生命周期

每个request对象只在servlet的服务方法的范围内有效,或者在过滤器的doFilter方法的范围内有效。

但是组件的异步处理功能被启用后,并且在request上调用了startAsync方法后比较特殊。我们先看下startAsync方法:

public AsyncContext startAsync() throws IllegalStateException

在发生异步处理的情况下,request对象的生命周期一直会延续到在AsyncContext上调用complete方法之前。

/*** Completes the async request processing and closes the response stream*/
void complete();

也就是说如果需要在上述范围之外,也就是多线程中使用request对象,需要使用到如下两个方法:
【1】requeststartAsync方法;
【2】AsyncContextcomplete方法;

我们将之前的代码进行改造:

@GetMapping("/getTest")
public String getTest(HttpServletRequest request, HttpServletResponse response) {AsycnContext asycnContext = request.startAsync(request, response);String age = request.getParameter("age");System.out.println("age=" + age);new Thread(() -> {try {Thread.sleep(200);} catch (InterruptedException e) {throw new RuntimeException(e);}String age1 = request.getParameter("age");System.out.println("age1=" + age1);asycnContext.complete();}).start();return "success";
}

此时在进行调用的时候,就算调用两次,都会正常输出:

age=18
age1=18
age=18
age1=18

从现象上来说,就是getTest请求返回之后,request线程并没有被调用recycle方法进行回收。

recycle方法的调用链上很快就能找到这个方法:

@Override
public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status) throws IOException {// ......if (dispatches != null) {// ......} else if (isAsync() || isUpgrade() || state == SocketState.ASYNC_END) {state = dispatch(status);state = checkForPipelinedData(state, socketWrapper);}
}

complete()方法上面的注解closes the response stream也不难发现,只有调用complete()方法之后,response流才会关闭。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com