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

作者: admin 分类: 异常处理 发布时间: 2019-06-14 00:02  阅读: 314 views

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

 

一、配置优化

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

原servlet2.5在新建servlet时,需要在web.xml中创建对应对象,才可以访问
<servlet>
    <servlet-name>shortly</servlet-name>
    <servlet-class>com.kaistart.gateway.servlet.KaistartGatewayServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>shortly</servlet-name>
    <url-pattern>/api</url-pattern>
</servlet-mapping>

注解方式可以调整如下:

@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.过滤器、监听器的创建简化

原
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>classpath*:/spring/spring.xml</param-value>
</context-param>
<context-param>
	<param-name>log4jConfigLocation</param-name>
	<param-value>classpath:log4j.xml</param-value>
</context-param>

<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<filter>
	<filter-name>SpringCharacterEncodingFilter</filter-name>
	<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
	<init-param>
		<param-name>encoding</param-name>
		<param-value>UTF-8</param-value>
	</init-param>
</filter>
<filter-mapping>
	<filter-name>SpringCharacterEncodingFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping> 



新:

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<Runnable>(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创建线程组,配置好信息后测试如下:

 

从代码上调整
1. 默认方式 async.start( new thread() )
2. 队列方式 blockingqueue
3. 线程池方式 executors

从压测变量上测试
1.线程数     
2.持续时间
3.并发数

数据均没有特别大的差异,可能还有部分没弄明白。
并参考其他压测数据:https://blog.csdn.net/iteye_21091/article/details/82617598。 
可能要在特定的场景下发挥异步的性能提升。需要去发现下

 

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

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

 

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

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


   原创文章,转载请标明本文链接: servlet2.5升级servlet3.1/servlet4.0的新特性(注解替换web.xml配置文件、http2.0)在项目中的应用等

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

电子邮件地址不会被公开。 必填项已用*标注

更多阅读