java web 3.0改成2.5_servlet2.5升级servlet3.1/servlet4.0的新特性(注解替换web.xml配置文件、http2.0)在项目中的应用等...

项目中引用的是servlet3.1的jar包,但是沿用旧的servlet2.5的规则编写代码。正好利用这个机会,尝试下servlet新版本带来的新特性

一、配置优化

1.简化配置文件servlet的新建

原servlet2.5在新建servlet时,需要在web.xml中创建对应对象,才可以访问

shortly

com.kaistart.gateway.servlet.KaistartGatewayServlet

1

shortly

/api

注解方式可以调整如下:

@WebServlet(urlPatterns = "/api",loadOnStartup = 1)

public class KaistartGatewayServlet extends HttpServlet {

}

其中loadOnStarup的作用如下:

1.load-on-startup元素标记容器是否在启动的时候就加载这个servlet(实例化并调用其init()方法)。

2.它的值必须是一个整数,表示servlet应该被载入的顺序。;

3.当值为0或者大于0时,表示容器在启动时就加载并初始化这个servlet。

4.当值小于0或者没有指定时,则表示容器在该Servlet被请求时,才会去加载。

5.正数的值越小,该Servlet的优先级就越高,应用启动时就优先加载。

6.当值相同的时候,容器就会自己选择优先加载。

2.过滤器、监听器的创建简化

contextConfigLocation

classpath*:/spring/spring.xml

log4jConfigLocation

classpath:log4j.xml

org.springframework.web.context.ContextLoaderListener

SpringCharacterEncodingFilter

org.springframework.web.filter.CharacterEncodingFilter

encoding

UTF-8

SpringCharacterEncodingFilter

/*

新:

package com.kaistart.gateway.init;

import java.util.EnumSet;

import javax.servlet.DispatcherType;

import javax.servlet.FilterRegistration.Dynamic;

import javax.servlet.ServletContext;

import javax.servlet.ServletException;

import org.springframework.web.WebApplicationInitializer;

import org.springframework.web.context.ContextLoaderListener;

import org.springframework.web.filter.CharacterEncodingFilter;

/**

*

* 升级servlet 简化web.xml配置

* @author chenhailong

* @date 2019年5月29日 下午6:46:27

*/

public class Init implements WebApplicationInitializer {

@Override

public void onStartup(ServletContext servletContext) throws ServletException {

//配置文件

servletContext.setInitParameter("contextConfigLocation","classpath*:/spring/spring.xml");

//日志文件

servletContext.setInitParameter("log4jConfigLocation", "classpath:log4j.xml");

//编码过滤器

CharacterEncodingFilter filter = new CharacterEncodingFilter();

filter.setEncoding("UTF-8");

Dynamic filterRegistration = servletContext.addFilter("SpringCharacterEncodingFilter",new CharacterEncodingFilter());

filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");

//监听器

servletContext.addListener(new ContextLoaderListener());

}

}

3.welcome-file-list \ session-config没有找到配置

4. 开始使用servlet3.0开始的异步特性

a.获取AsyncContext对象应该使用request.startAsync();

AsyncContext async = request.startAsync();

如果使用AsyncContext async = request.getAsyncContext();,会报错

java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)

org.apache.catalina.connector.Request.getAsyncContext(Request.java:1695)

org.apache.catalina.connector.RequestFacade.getAsyncContext(RequestFacade.java:1055)

com.kaistart.gateway.servlet.KaistartGatewayCallbackServlet.service(KaistartGatewayCallbackServlet.java:70)

javax.servlet.http.HttpServlet.service(HttpServlet.java:729)

org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)

org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)

b.需要设置request请求支持异步处理

@WebServlet(urlPatterns = "/callback/*", loadOnStartup = 1,asyncSupported = true)

否则报错

java.lang.IllegalStateException: A filter or servlet of the current chain does not support asynchronous operations.

org.apache.catalina.connector.Request.startAsync(Request.java:1630)

org.apache.catalina.connector.Request.startAsync(Request.java:1623)

org.apache.catalina.connector.RequestFacade.startAsync(RequestFacade.java:1030)

com.kaistart.gateway.servlet.KaistartGatewayCallbackServlet.service(KaistartGatewayCallbackServlet.java:70)

javax.servlet.http.HttpServlet.service(HttpServlet.java:729)

org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)

org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)

c. 当采用以下这种写法,没有使用线程池时如下(压测时出现):

final PrintWriter writer = response.getWriter();

writer.println("异步之前输出的内容。");

writer.flush();

//开始异步调用,获取对应的AsyncContext。

final AsyncContext asyncContext = request.startAsync();

asyncContext.setTimeout(1*5000L);

asyncContext.start(new Runnable() {

@Override

public void run() {

writer.println("111.");

writer.flush();

asyncContext.complete();

}

});

tomcat容器会容易出现以下类似问题,

https://github.com/spring-projects/spring-boot/issues/15057

报错一:

java.lang.IllegalStateException: Calling [asyncError()] is not valid for a request with Async state [COMPLETE_PENDING]

at org.apache.coyote.AsyncStateMachine.asyncError(AsyncStateMachine.java:412)

at org.apache.coyote.http11.AbstractHttp11Processor.action(AbstractHttp11Processor.java:847)

at org.apache.coyote.Request.action(Request.java:378)

at org.apache.catalina.core.AsyncContextImpl.setErrorState(AsyncContextImpl.java:412)

at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:543)

at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1132)

at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:684)

at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1539)

at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1495)

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)

at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)

at java.lang.Thread.run(Thread.java:748)

报错二:

java.lang.IllegalStateException: Calling [asyncComplete()] is not valid for a request with Async state [MUST_ERROR]

at org.apache.coyote.AsyncStateMachine.doComplete(AsyncStateMachine.java:304)

at org.apache.coyote.AsyncStateMachine.asyncComplete(AsyncStateMachine.java:289)

at org.apache.coyote.http11.AbstractHttp11Processor.action(AbstractHttp11Processor.java:876)

at org.apache.coyote.Request.action(Request.java:378)

at org.apache.catalina.core.AsyncContextImpl.complete(AsyncContextImpl.java:96)

at com.kaistart.gateway.servlet.KaistartGatewayCallbackServlet.lambda$0(KaistartGatewayCallbackServlet.java:93)

at org.apache.catalina.core.AsyncContextImpl$RunnableWrapper.run(AsyncContextImpl.java:559)

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)

at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)

at java.lang.Thread.run(Thread.java:748)

采用线程池之后,异常没有出现。应该和asyncContext.start()方法有关

二、附上异步完整代码 + 压测结果

运行相关环境:mac、tomcat8.0、jdk1.8.0_171、eclipse、jmeter、servlet3.1jar包

1、web.xml配置 替换为java类初始化

package com.kaistart.gateway.init;

import java.util.EnumSet;

import javax.servlet.DispatcherType;

import javax.servlet.FilterRegistration.Dynamic;

import javax.servlet.ServletContext;

import javax.servlet.ServletException;

import org.springframework.web.WebApplicationInitializer;

import org.springframework.web.context.ContextLoaderListener;

import org.springframework.web.filter.CharacterEncodingFilter;

/**

* 升级servlet 代替web.xml文件中的配置

* @author chenhailong

* @date 2019年5月29日 下午6:46:27

*/

public class Init implements WebApplicationInitializer {

@Override

public void onStartup(ServletContext servletContext) throws ServletException {

//配置文件

servletContext.setInitParameter("contextConfigLocation","classpath*:/spring/spring.xml");

//日志文件

servletContext.setInitParameter("log4jConfigLocation", "classpath:log4j.xml");

//编码过滤器

CharacterEncodingFilter filter = new CharacterEncodingFilter();

filter.setEncoding("UTF-8");

Dynamic filterRegistration = servletContext.addFilter("SpringCharacterEncodingFilter",new CharacterEncodingFilter());

filterRegistration.setAsyncSupported(true);

filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");

//监听器

servletContext.addListener(new ContextLoaderListener());

}

}

2、容器启动后,创建线程池相关

package com.kaistart.gateway.init;

import java.util.concurrent.LinkedBlockingQueue;

import java.util.concurrent.ThreadPoolExecutor;

import java.util.concurrent.TimeUnit;

import javax.servlet.ServletContextEvent;

import javax.servlet.ServletContextListener;

import javax.servlet.annotation.WebListener;

/**

*

* 容器启动后创建线程池

* @author chenhailong

* @date 2019年5月30日 下午6:44:01

*/

@WebListener

public class ContextListener implements ServletContextListener {

private int coreThread = 50; //核心线程数

private int maxThread = 100; //最大线程数

private long liveTime = 30000L;//超过核心线程数的超时时间设置

private int queueSize = 500; //队列长度

@Override

public void contextInitialized(ServletContextEvent sce) {

ThreadPoolExecutor executor =

new ThreadPoolExecutor(coreThread, maxThread, liveTime, TimeUnit.SECONDS,

new LinkedBlockingQueue(queueSize), new ThreadPoolExecutor.DiscardPolicy());

sce.getServletContext().setAttribute("executor", executor);

}

@Override

public void contextDestroyed(ServletContextEvent sce) {

ThreadPoolExecutor executor = (ThreadPoolExecutor)sce.getServletContext().getAttribute("executor");

executor.shutdown();

}

}

3、监听异步执行,这个可以不做

package com.kaistart.gateway.init;

import java.io.IOException;

import javax.servlet.AsyncEvent;

import javax.servlet.annotation.WebListener;

/**

* 用来监听异步操作不同事件的后续处理,这里在暂不处理

* @author chenhailong

* @date 2019年5月30日 下午7:18:22

*/

@WebListener

public class AsyncsListener implements javax.servlet.AsyncListener {

@Override

public void onComplete(AsyncEvent event) throws IOException {

//TODO when complete

}

@Override

public void onTimeout(AsyncEvent event) throws IOException {

//TODO when timeout

}

@Override

public void onError(AsyncEvent event) throws IOException {

//TODO when error

}

@Override

public void onStartAsync(AsyncEvent event) throws IOException {

//TODO startasync

}

}

4.创建普通servlet请求

package com.kaistart.gateway.servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import com.kaistart.gateway.common.exception.GatewayException;

/**

*

*

* @author chenhailong

* @date 2019年1月21日 上午11:30:50

*/

@WebServlet(urlPatterns = "/testapi",loadOnStartup = 1)

public class KaistartGatewayTest extends HttpServlet {

private static final long serialVersionUID = 1L;

private static final Logger logger = LoggerFactory.getLogger(KaistartGatewayTest.class);

@Override

public void init(ServletConfig config) throws ServletException {

super.init(config);

}

@Override

protected void service(HttpServletRequest request, HttpServletResponse response) {

try {

//这里可以写具体的业务实现

Thread.sleep(1000);

} catch (GatewayException e) {

} catch (Throwable e) {

logger.error("eid="+e.hashCode()+" "+e.getMessage(), e);

}

}

}

5.创建异步servlet请求

package com.kaistart.gateway.servlet;

import java.util.concurrent.ThreadPoolExecutor;

import javax.servlet.AsyncContext;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import com.kaistart.gateway.init.AsyncsListener;

/**

*

* 注:使用servlet异步操作,如不使用线程池,直接使用 async.start(new Runnable())会报错:https://github.com/spring-projects/spring-boot/issues/15057

*

* @author wuyuan.lfk

* @date 2019年1月21日 上午11:30:50

*/

@WebServlet(urlPatterns = "/api",loadOnStartup = 1,asyncSupported = true)

public class KaistartGatewayServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

@Override

public void init(ServletConfig config) throws ServletException {

super.init(config);

}

@Override

protected void service(HttpServletRequest request, HttpServletResponse response) {

if(request.isAsyncSupported()) {

AsyncContext async = request.startAsync();

async.setTimeout(30*1000L);

async.addListener(new AsyncsListener());

//获取线程池

ThreadPoolExecutor executor = (ThreadPoolExecutor)request.getServletContext().getAttribute("executor");

executor.execute(new AsyncRequestProcessor(

async)

);

System.out.println("doService() -> 完成");

}

}

public class AsyncRequestProcessor implements Runnable{

private AsyncContext context;

public AsyncRequestProcessor(AsyncContext context) {

this.context = context;

}

@Override

public void run() {

context.start(() -> {

try {

//这里可以写具体的业务实现

Thread.sleep(1000);

System.out.println("doRun() -> 完成");

} catch (Exception e) {

e.printStackTrace();

}

context.complete();

});

}

}

}

6. 利用jmeter创建线程组,配置好信息后测试如下:

ef2453d093f242e352667dcf4738c64b.png

从代码上调整

1. 默认方式 async.start( new thread() )

2. 队列方式 blockingqueue

3. 线程池方式 executors

从压测变量上测试

1.线程数

2.持续时间

3.并发数

数据均没有特别大的差异,可能还有部分没弄明白。

并参考其他压测数据:https://blog.csdn.net/iteye_21091/article/details/82617598。

可能要在特定的场景下发挥异步的性能提升。需要去发现下

后来发现,可能是测试方式及测试数据的理解不正确。重新理解, throughput是表示每秒的吞吐量,吞吐量 = 总线程数/总运行时间。这样的话,单纯的以吞吐量来对比同步、异步之间的性能提升十分不准确。因为运行时间内的前期、中期、后期的吞吐量值是有很大差别的。如下图:

85c2d9710e012cbf712a8e76a44fff85.png

同步、异步的差别主要在B环节,业务逻辑的执行放到了新的线程中执行。这样的话异步的请求线程直接是走A -C逻辑后释放,可以处理其他请求,但是当前请求完成结束需要等待B的 asyncContext.complete()通知。 B环节的业务逻辑执行时间长短,直接拉开了同、异步的请求吞吐量。可以通过在代码中打印当前线程信息、请求次数等信息观察,需要结合tomcat工作线程池理解。这是我目前的理解了。

其他:servlet4.0 添加http2的特性增加了pushbuilder对象,这里就不举例了,参考如下:

https://www.jianshu.com/p/2d768df76501

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值