目录

spring注解驱动之Web

servlet3.0 标准以后提供了使用注解的方式来注入组件(servlet、filter、DispatchServlet),包括一些运行时可插拔的插件能力

servlet3.0 属于JSR315系列的规范,但是需要tomcat7及以上版本的支持

关于注解驱动和插件能力的说明详细说明请参考 官网文档

tomcat版本支持请参考tomcat官方说明

演示使用原生servlet注解开发

引入servlet依赖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.2</version>
            <scope>provided</scope>
        </dependency>

编写java程序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package com.eh.servlet;

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 java.io.IOException;

// 使用注解代替web.xml配置文件来识别Servlet
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("hello servlet3.0");
    }
}

使用tomcat7及以上版本启动应用

访问:http://localhost:8080/spring_annotation_servlet3_0_Web_exploded/hello

使用servlet原生注解开发web应用非常少,下面介绍共享库和运行时插件能力,这个在整合其他框架时用的比较多

共享库和运行时插件能力

使用编码的方式往容器中添加三大组件

Servlet3.0以后,引入一个接口,javax.servlet.ServletContainerInitializer,该接口的onStartup方法可以在ServletContext启动完成之前创建组件(Servlet容器启动会扫描,当前应用里面每一个jar包的ServletContainerInitializer的实现),并且实现了该接口的类通过特定格式的配置,能够在Servlet容器启动的时候自动被识别到然后被加载,最终完成组件的创建工作。

包含每一个jar包里的ServletContainerInitializer的实现,格式规则必须绑定在classpath下,META-INF/services/javax.servlet.ServletContainerInitializer,META-INF/services/这一块为文件路径,javax.servlet.ServletContainerInitializer这个是文件的名称,而不是类名,然后将实现了ServletContainerInitializer接口的全类名粘贴到这个文件内容里即可。

使用IDEA直接新建packageMETA-INF会不能直接创建,但是可以直接创建META-INF/services,然后再在这个路径下新建文件即可

演示:

  1. 编写待注册的Servlet组件,不使用WebServlet注解

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    package com.eh.servlet;
       
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
       
    public class CustomServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
            resp.setContentType("text/html;charset=UTF-8");
            resp.getWriter().write("使用编码方式映射Servlet..........");
        }
    }
    
  2. 编写Filter

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    package com.eh.servlet;
       
    import javax.servlet.*;
    import java.io.IOException;
       
    public class CustomFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("过滤器初始化。。。。。。。。");
            System.out.println("servlet容器: " + filterConfig.getServletContext());
       
        }
       
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("目标方法被拦截.............");
            filterChain.doFilter(servletRequest, servletResponse);
        }
       
        @Override
        public void destroy() {
            System.out.println("销毁方法。。。。。。。。。。。");
        }
    }
    
  3. 编写ServletContextListener

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    package com.eh.servlet;
       
    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
       
    public class CustomListener implements ServletContextListener {
        @Override
        public void contextInitialized(ServletContextEvent servletContextEvent) {
            System.out.println("监听容器初始化。。。。。。。。。");
            System.out.println(servletContextEvent);
        }
       
        @Override
        public void contextDestroyed(ServletContextEvent servletContextEvent) {
            System.out.println("监听容器销毁..........");
            System.out.println(servletContextEvent);
        }
    }
    
  4. 编写注册三大组件的配置类,实现ServletContainerInitializer接口

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    
    package com.eh.servlet;
       
    import javax.servlet.*;
    import javax.servlet.annotation.HandlesTypes;
    import java.util.EnumSet;
    import java.util.Set;
       
       
    @HandlesTypes(value = {Filter.class})
    public class CustomsContainerInit implements ServletContainerInitializer {
        /**
         * 在容器启动的时候创建servlet组件,不需要使用配置文件
         *
         * @param set            容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口等)传递过来作为onStartup的第一个参数
         * @param servletContext 代表当前web应用的ServletContext,一个Web应用一个ServletContext
         * @throws ServletException
         *
         */
        @Override
        public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
            System.out.println("@HandlesTypes传入的类型有: ");
            for (Class clazz : set) {
                System.out.println(clazz);
            }
       
            // 注册Servlet
            ServletRegistration.Dynamic customServlet = servletContext.addServlet("customServlet", new CustomServlet());
            customServlet.addMapping("/custom");
       
            // 注册Listener
            servletContext.addListener(CustomListener.class);
       
            // 注册Filter
            FilterRegistration.Dynamic customFilter = servletContext.addFilter("customFilter", new CustomFilter());
            customFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
       
        }
    }
    
  5. 识别ServletContainerInitializer实现类

    文件:META-INF/services/javax.servlet.ServletContainerInitializer

    1
    
    com.eh.servlet.CustomsContainerInit
    
  6. 启动容器,访问http://localhost:8080/spring_annotation_servlet3_0_Web_exploded/custom,然后销毁容器,日志如下,正确识别

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    @HandlesTypes传入的类型有: 
    class com.eh.servlet.CustomFilter
    监听容器初始化。。。。。。。。。
       
    过滤器初始化。。。。。。。。
       
    目标方法被拦截.............
    目标方法被拦截.............
    目标方法被拦截.............
    /Users/david/soft/apache-tomcat-8.5.16/bin/catalina.sh stop
    销毁方法。。。。。。。。。。。
    监听容器销毁..........
    

注意:

使用编码的释放给ServletContext添加组件,必须在项目启动的时候添加,也就是只能在下面两处地方

  1. 像演示中所示,使用ServletContainerInitializer得到的ServletContext来创建组件
  2. javax.servlet.ServletContainerInitializer#onStartup

springmvc整合servlet3.0+

最终效果

http://img.cana.space/picStore/20201101123804.png

环境准备

整合分析

官方文档参考

  1. web容器在启动的时候,会扫描买个jar包下的META-INF/services/javax.servlet.ServletContainerInitializer文件

  2. 加载这个文件指定的类org.springframework.web.SpringServletContainerInitializer(在spring-web项目中)

    1
    2
    
    @HandlesTypes(WebApplicationInitializer.class)
    public class SpringServletContainerInitializer implements ServletContainerInitializer {
    
  3. spring的应用一启动会加载感兴趣的WebApplicationInitializer接口下的所有组件

    1
    2
    3
    4
    
    AbstractContextLoaderInitializer (org.springframework.web.context)
        AbstractDispatcherServletInitializer (org.springframework.web.servlet.support)
            AbstractAnnotationConfigDispatcherServletInitializer (org.springframework.web.servlet.support)
    AbstractReactiveWebInitializer (org.springframework.web.server.adapter)
    
  4. 并且为WebApplicationInitializer组件创建对象(组件不是接口,也不是抽象类)

    1. AbstractContextLoaderInitializer

      创建根容器

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      
      protected void registerContextLoaderListener(ServletContext servletContext) {
         WebApplicationContext rootAppContext = createRootApplicationContext();
         if (rootAppContext != null) {
            ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
            listener.setContextInitializers(getRootApplicationContextInitializers());
            servletContext.addListener(listener);
         }
         else {
            logger.debug("No ContextLoaderListener registered, as " +
                  "createRootApplicationContext() did not return an application context");
         }
      }
      
    2. AbstractDispatcherServletInitializer

      创建一个web的ioc容器

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      
      protected void registerDispatcherServlet(ServletContext servletContext) {
         String servletName = getServletName();
         Assert.hasLength(servletName, "getServletName() must not return null or empty");
      // 创建一个web的ioc容器
         WebApplicationContext servletAppContext = createServletApplicationContext();
         Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
      // 创建DispatcherServlet
         FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
         Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
         dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
      // 将创建的DispatcherServlet添加到应用上下文servletContext中
         ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
         if (registration == null) {
            throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
                  "Check if there is another servlet registered under the same name.");
         }
      // 配置信息,映射
         registration.setLoadOnStartup(1);
         registration.addMapping(getServletMappings());
         registration.setAsyncSupported(isAsyncSupported());
            
         Filter[] filters = getServletFilters();
         if (!ObjectUtils.isEmpty(filters)) {
            for (Filter filter : filters) {
               registerServletFilter(servletContext, filter);
            }
         }
            
         customizeRegistration(registration);
      }
      
    3. AbstractAnnotationConfigDispatcherServletInitializer

      注解方式配置的DispatcherServlet初始化器

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      
      // 创建根容器
      @Override
      @Nullable
      protected WebApplicationContext createRootApplicationContext() {
         Class<?>[] configClasses = getRootConfigClasses(); // 获取配置类
         if (!ObjectUtils.isEmpty(configClasses)) {
            AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
            context.register(configClasses);
            return context;
         }
         else {
            return null;
         }
      }
            
      // 创建web的ioc容器
      @Override
      protected WebApplicationContext createServletApplicationContext() {
         AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
         Class<?>[] configClasses = getServletConfigClasses(); // 获取配置类
         if (!ObjectUtils.isEmpty(configClasses)) {
            context.register(configClasses);
         }
         return context;
      }
      

小结

以注解方式来启动SpringMVC,继承AbstractAnnotationConfigDispatcherServletInitializer,实现抽象方法指定DispatcherServlet的配置信息。

1
2
3
4
5
@Nullable
protected abstract Class<?>[] getRootConfigClasses();

@Nullable
protected abstract Class<?>[] getServletConfigClasses();

整合演示

演示环境:

  • tomcat8

引入依赖:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<dependencies>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.0</version>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.2</version>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>

</dependencies>

根容器配置类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package com.eh.mvc;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

// 根容器扫描com.eh.mvc包下所有组件,但不扫描Controller注解的类
@ComponentScan(value = "com.eh.mvc", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class)
})
public class RootConfig {
}

子容器配置类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com.eh.mvc;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

// springMvc子容器只扫描Controller, 与根容器扫描形成互补
// useDefaultFilters = false禁用默认的过滤规则
@ComponentScan(value = "com.eh.mvc", includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class)
}, useDefaultFilters = false)
public class AppConfig {
}

编写AbstractAnnotationConfigDispatcherServletInitializer的子类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.eh.mvc;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;


public class MyWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    // 获取根容器的配置类(Spring的配置文件)父容器
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }

    // 获取Web容器的配置类(SpringMVC配置文件)子容器
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{AppConfig.class};
    }

    // 获取DispatcherServlet的映射信息
    @Override
    protected String[] getServletMappings() {
        // 拦截所有请求(包括静态资源xxx.js,xxx.png),不包括*.jsp
        // /*:拦截所有请求,包括*.jsp
        // jsp页面是tomcat的jsp引擎解析的,不能进行拦截
        return new String[]{"/"};
    }
}

编写测试类Controller和测试类Service

HelloService

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package com.eh.mvc;

import org.springframework.stereotype.Service;

@Service
public class HelloService {

    public String sayHello(String prefix) {
        return prefix + " servlet3.0+ & springMvc";
    }
}

HelloController

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package com.eh.mvc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @Autowired
    private HelloService helloService;

    @RequestMapping("/hello")
    public String hello() {
        return helloService.sayHello("hello");
    }
}

演示结果:

http://img.cana.space/picStore/20201101131751.png

定制与接管SpringMVC

完整示例地址

更多配置请参考 官方文档

  1. @EnableWebMvc,开启SpringMVC定制配置功能

    相当于<mvc:annotation-driven/>

  2. 配置组件(视图解析器、视图映射、静态资源映射、拦截器等等),implements WebMvcConfigurer

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    
    package com.eh.mvc;
       
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.FilterType;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.servlet.config.annotation.*;
       
    // springMvc子容器只扫描Controller
    // useDefaultFilters = false禁用默认的过滤规则
    @ComponentScan(value = "com.eh.mvc", includeFilters = {
            @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class)
    }, useDefaultFilters = false)
    @EnableWebMvc
    public class AppConfig implements WebMvcConfigurer {
       
        // 自定义视图解析器
        @Override
        public void configureViewResolvers(ViewResolverRegistry registry) {
            // 默认registry.jsp();,return jsp("/WEB-INF/", ".jsp");
            registry.jsp("/WEB-INF/views/", ".jsp");
        }
       
        /**
         * 相当于在配置文件中写 <mvc:default-servlet-handler/>
         * 将SpringMVC处理不了的请求交给tomcat,静态资源就可以访问
         *
         * @param configurer
         */
        @Override
        public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
            // 开启
            configurer.enable();
        }
       
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new MyFirstInterceptor()).addPathPatterns("/**");
        }
    }
    

异步请求

servlet3.0原生异步请求

在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求。即每一次Http请求都由某一个线程从头到尾负责处理。

http://img.cana.space/picStore/20201101135453.png

如果一个请求需要进行IO操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。即便是像Spring、Struts这样的高层框架也脱离不了这样的桎梏,因为他们都是建立在Servlet之上的。为了解决这样的问题,Servlet 3.0引入了异步处理,然后在Servlet 3.1中又引入了非阻塞IO来进一步增强异步处理的性能。

http://img.cana.space/picStore/20201101135534.png

同步演示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.eh.mvc;

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 java.io.IOException;

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(Thread.currentThread() + " start");
        sayHello();
        resp.getWriter().write("hello");
        System.out.println(Thread.currentThread() + " end");
    }

    public void sayHello() {
        try {
            System.out.println(Thread.currentThread() + "processing");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
1
2
3
Thread[http-nio-8080-exec-8,5,main] start
Thread[http-nio-8080-exec-8,5,main]processing
Thread[http-nio-8080-exec-8,5,main] end

可以看到是同一个线程

异步演示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.eh.mvc;

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

// 1. 开启支持异步处理async
@WebServlet(urlPatterns = "/async", asyncSupported = true)
public class HelloAsyncServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("主线程开始..." + Thread.currentThread() + "====" + System.currentTimeMillis());
        // 开启异步模式
        AsyncContext asyncContext = req.startAsync();
        asyncContext.start(() -> {
            System.out.println("副线程开始..." + Thread.currentThread() + "====" + System.currentTimeMillis());
            sayHello();
            asyncContext.complete();
            // 获取响应
            ServletResponse response = asyncContext.getResponse();
            try {
                response.getWriter().write("hello, async");
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("副线程结束..." + Thread.currentThread() + "====" + System.currentTimeMillis());
        });
        System.out.println("主线程结束..." + Thread.currentThread() + "====" + System.currentTimeMillis());
    }

    public void sayHello() {
        try {
            System.out.println(Thread.currentThread() + "processing");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出:

1
2
3
4
5
主线程开始...Thread[http-nio-8080-exec-3,5,main]====1604211651077
主线程结束...Thread[http-nio-8080-exec-3,5,main]====1604211651078
副线程开始...Thread[http-nio-8080-exec-4,5,main]====1604211651078
Thread[http-nio-8080-exec-4,5,main]processing
副线程结束...Thread[http-nio-8080-exec-4,5,main]====1604211654082

这里使用了异步线程,但是还是同一个线程池,我们可以单独指定一个异步处理线程池

springmvc中使用异步请求

演示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.eh.mvc;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.concurrent.Callable;

@Controller
public class AsyncController {

    /**
     * 1. 控制器返回Callable
     * 2. Spring异步处理,将Callable提交到TaskExecutor,使用一个隔离的线程执行
     * 3. DispatcherServlet和所有的Filter退出web容器的线程,但是response保持打开状态,还能向浏览器写数据
     * 4. Callable返回结果,SpringMVC将请求重新派发给容器(所以日志打印两次请求开始),恢复之前的处理
     * 5. 根据Callable返回的结果,SpringMVC继续进行视图渲染流程等(从头开始,请求会再次发给mvc,从收请求到视图渲染,
     *      目标方法不用执行,异步返回的结果就是目标方法执行的结果)
     * @return
     */
    @ResponseBody
    @RequestMapping("/async01")
    public Callable<String> async01() {
        System.out.println("主线程开始..." + Thread.currentThread() + "====" + System.currentTimeMillis());
        Callable<String> callable = () -> {
            System.out.println("副线程开始..." + Thread.currentThread() + "====" + System.currentTimeMillis());
            Thread.sleep(3000);
            System.out.println("副线程结束..." + Thread.currentThread() + "====" + System.currentTimeMillis());
            return "Callable<String> async01()";
        };

        System.out.println("主线程结束..." + Thread.currentThread() + "====" + System.currentTimeMillis());
        return callable;
    }
}

输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
preHandle.../springmvc_annotation_war_exploded/async01
==========主线程处理请求============
主线程开始...Thread[http-nio-8080-exec-2,5,main]====1604213760933
主线程结束...Thread[http-nio-8080-exec-2,5,main]====1604213760933
==========副线程处理异步请求============
副线程开始...Thread[MvcAsync2,5,main]====1604213760934
副线程结束...Thread[MvcAsync2,5,main]====1604213763935
==========重新派发请求============
preHandle.../springmvc_annotation_war_exploded/async01
postHandler
afterCompletion

异步的拦截器:

  1. 原生的AsyncListener
  2. SpringMVC:实现AsyncHandlerInterceptor

实际应用场景

http://img.cana.space/picStore/20201101144640.png

springmvc实现上述方式:

1
2
3
4
5
6
@ResponseBody
@RequestMapping("/createOrder")
public DeferredResult<Object> createOrder() {
    DeferredResult<Object> deferredResult = new DeferredResult<>(3000L, "create order failed");
    return deferredResult;
}

3s之后会出现超时

http://img.cana.space/picStore/20201101145830.png

给deferredResult设置值

可以将deferredResult保存在Queue中,在另一个线程中取出deferredResult并将值设置回去

queue:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package com.eh.mvc;

import org.springframework.web.context.request.async.DeferredResult;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

public class DeferedResultQueue {

    private static Queue<DeferredResult<Object>> queue = new ConcurrentLinkedQueue<DeferredResult<Object>>();

    public static void save(DeferredResult<Object> deferredResult) {
        queue.add(deferredResult);
    }

    public static DeferredResult<Object> get() {
        return queue.poll();
    }
}

另一个线程:

这里以另一个请求作为演示

1
2
3
4
5
6
7
8
9
@ResponseBody
@RequestMapping("/createOrder")
public DeferredResult<Object> createOrder() {
    DeferredResult<Object> deferredResult = new DeferredResult<>(3000L, "create order failed");

    DeferedResultQueue.save(deferredResult);

    return deferredResult;
}

先调用/createOrder,在3s之内调用/create,

http://img.cana.space/picStore/20201101150549.png

完整示例地址