Spring全注解开发----Servlet 3.0

2021-11-15 15:06:07 浏览数 (1)

Servlet 3.0

  • servlet3.0-简介&测试
  • 使用前导入servlet相关的依赖
  • @WebServlet : 注册servlet ,以及servlet 3.0相关的注解说明,链接在下面:
    • 同样,要注册Filter用@WebFilter注解、注册Listener用@WebListener注解;如果在注册的时候,需要一些初始化参数,我们就可以用@WebInitParam注解;
    • 小细节回顾: 解决tomcat响应中文乱码问题,通知浏览器使用uft-8编码来对数据进行解码
  • servlet3.0 ==> ServletContainerInitializer
    • 第一步:我们写一个MyServletContainerInitializer 类实现ServletContainerInitializer 接口
    • 第二步:在这个路径下新建一个文件
      • 注意:从运行结果可以分析出,这里传递过来的所有感兴趣的类型是指定类型下面所有子类,不包括本身
  • servlet3.0-在ServletContext中利用编码的方式注册三大组件
    • 使用编码方式注册三大组件的小总结
  • servlet3.0-与SpringMVC整合分析
    • 首先导入springMVC相关的依赖
    • 原理
    • springmvc-整合
      • 1.创建web初始化器
      • 2.父子配置类,spring是父配置类,要排除扫描controller注解,而springmvc是子配置类,只负责扫描controller注解
      • 3.我们再来写一个Controller和Service进行测试
    • springmvc-定制与接管SpringMVC
    • 注解来定制SpringMVC: @EnableWebMvc
    • 通过一个配置类来实现接口:extends WebMvcConfigurerAdapter, 这样就可以来实现定制配置了
      • 但是上面直接实现WebMvcConfigurer接口的方式,有很多的方法用不到,我们可以用这个适配器WebMvcConfigurerAdapter来实现,它实现了WebMvcConfigurer接口:
      • 我们可以来定义一个视图解析器:
      • 问题: 我们在这个路径下放一个图片,然后再写一个jsp来访问它,这个时候,图片是显示不出来的:
      • 现在,我们来配置允许静态资源的访问:
      • 注意:如果图片是在WEB-INF下面的,那么获取图片是获取不到的,因为WEB-INF下面的资源不能直接访问,jsp页面可以通过controller跳转方式实现内部资源访问,但是图片嘛.....,目前我知识有限,还不知道如何访问
    • 自定义拦截器需要实现HandlerInterceptor接口:
    • 在配置类中注册拦截器
  • servlet3.0-异步请求
      • 添加异步处理后:
  • springmvc-异步请求-返回Callable
    • springmvc-异步请求-返回DeferredResult

servlet3.0-简介&测试

现在,我们来说说注解版的web,我们以前来写web的三大组件:Servlet、Filter、Listener,包括SpringMVC的前端控制器DispatcherServlet都需要在web.xml文件中来进行注册;而在Servlet3.0标准以后,就给我们提供了方便的注解的方式来完成我们这些组件的注册以及添加,提供了运行时的可插拔的插件能力;

说明:Servlet3.0及以上的标准是需要Tomcat7及以上的支持;

如果使用全注解开发,那么就可以去掉web.xml配置文件了,转用编码方式进行替代


使用前导入servlet相关的依赖

代码语言:javascript复制
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
      <scope>provided</scope>
    </dependency>


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

@WebServlet : 注册servlet ,以及servlet 3.0相关的注解说明,链接在下面:

代码语言:javascript复制
@WebServlet("/hello")//指定当前servlet的请求映射路径
public class MyServlet  extends HttpServlet
{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
    {
        resp.setContentType("text/html;charset=utf-8");
        resp.getWriter().write("大忽悠");
    }
}

同样,要注册Filter用@WebFilter注解、注册Listener用@WebListener注解;如果在注册的时候,需要一些初始化参数,我们就可以用@WebInitParam注解;

Servlet 3.0 注解 @WebServlet @WebFilter @WebListener

@WebFilter 的使用

@WebServlet的使用方法


小细节回顾: 解决tomcat响应中文乱码问题,通知浏览器使用uft-8编码来对数据进行解码

代码语言:javascript复制
        resp.setContentType("text/html;charset=utf-8");

servlet3.0 ==> ServletContainerInitializer

代码语言:javascript复制
Shared libraries(共享库) / runtimes pluggability(运行时插件能力)

1、Servlet容器启动会扫描,当前应用里面每一个jar包的ServletContainerInitializer的实现

2、提供ServletContainerInitializer的实现类;

	必须绑定在,META-INF/services/javax.servlet.ServletContainerInitializer
	
	文件的内容就是ServletContainerInitializer实现类的全类名;

总结:容器在启动应用的时候,会扫描当前应用每一个jar包里面

META-INF/services/javax.servlet.ServletContainerInitializer

指定的实现类,启动并运行这个实现类的方法;传入感兴趣的类型;


ServletContainerInitializer;

@HandlesTypes;

第一步:我们写一个MyServletContainerInitializer 类实现ServletContainerInitializer 接口

代码语言:javascript复制
//容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类或者子接口等)传递过来
//传入感兴趣的类型
@HandlesTypes(value = {HelloService.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {
    /**
     * 在应用启动的时候,会运行onStartup方法;
     * Set<Class<?>> :感兴趣的类型的所有子类型;
     * ServletContext 代表当前的web应用的ServletContext对象,一个web应用相当于是一个ServletContext
     * @throws ServletException
     */
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        System.out.println("感兴趣的类型:");
        set.forEach(System.out::println);
    }
}

第二步:在这个路径下新建一个文件

文件的内容就写我们实现ServletContainerInitializer 这个接口的类MyServletContainerInitializer 的全类名:


最后,我们运行起来,运行结果为:

代码语言:javascript复制
感兴趣的类型:
class com.ldc.service.HelloServiceExt
class com.ldc.service.AbstractHelloService
class com.ldc.service.HelloServiceImpl

注意:从运行结果可以分析出,这里传递过来的所有感兴趣的类型是指定类型下面所有子类,不包括本身


servlet3.0-在ServletContext中利用编码的方式注册三大组件

首先我们写一个Servlet:

代码语言:javascript复制
public class MyServlet  extends HttpServlet
{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
    {
        resp.setContentType("text/html;charset=utf-8");
        resp.getWriter().write("大忽悠");
    }
}

再来一个Filter:

代码语言:javascript复制
public class UserFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //过滤请求
        System.out.println("放行");
        //放行
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}

最后,再写一个Listener:

代码语言:javascript复制
/**
 * 监听项目的启动和停止
 */
public class UserListener implements ServletContextListener {
    //监听ServletContextEvent的启动初始化
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("项目启动");
    }
    //监听ServletContextEvent销毁
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("项目停止");
    }
}

最后,我们来用ServletContext来进行注册三大组件:

代码语言:javascript复制
//容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类或者子接口等)传递过来
//传入感兴趣的类型
@HandlesTypes(value = {HelloService.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {
    /**
     * 在应用启动的时候,会运行onStartup方法;
     * Set<Class<?>> :感兴趣的类型的所有子类型;
     * ServletContext 代表当前的web应用的ServletContext对象,一个web应用相当于是一个ServletContext
     * 1)、使用ServletContext注册Web组件(Servlet、Filter、Listener)
     * 2)、使用编码的方式,在项目启动的时候给ServletContext添加组件
     * 必须在项目启动的时候来添加
     *  (1)ServletContainerInitializer得到ServletContext对象来注册;
     *  (2)ServletContextListener的方法的参数里面的ServletContextEvent对象可以获取ServletContext对象
     */
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        System.out.println("感兴趣的类型:");
        set.forEach(System.out::println);

        //注册组件
        ServletRegistration.Dynamic servlet = servletContext.addServlet("userServlet", new MyServlet());
        //配置servlet的映射信息
        servlet.addMapping("/hello");

        //注册Listener
        servletContext.addListener(UserListener.class);

        //注册Filter
        FilterRegistration.Dynamic filter = servletContext.addFilter("userFilter", UserFilter.class);
        //拦截什么方式的请求,和要拦截的请求路径
        filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),true,"/*");
    }
}

我们启动,然后再浏览器上进行访问:


使用编码方式注册三大组件的小总结

1.使用ServletContext注册Web组件(Servlet、Filter、Listener)

2.使用编码的方式,在项目启动的时候给ServletContext添加组件, 必须在项目启动的时候来添加

2.1 ServletContainerInitializer得到ServletContext对象来注册;

2.2 ServletContextListener的方法的参数里面的ServletContextEvent对象可以获取ServletContext对象,然后在项目启动的时候进行注册,即利用监听器进行组件注册


servlet3.0-与SpringMVC整合分析

首先导入springMVC相关的依赖

代码语言:javascript复制
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>4.3.11.RELEASE</version>
    </dependency>

原理

代码语言:javascript复制
1、web容器在启动的时候,会扫描每个jar包下的META-INF/services/javax.servlet.ServletContainerInitializer
2、加载这个文件指定的类SpringServletContainerInitializer
3、spring的应用一启动会加载感兴趣的WebApplicationInitializer接口的下的所有组件;
4、并且为WebApplicationInitializer组件创建对象(组件不是接口,不是抽象类)
	1)、AbstractContextLoaderInitializer:创建根容器;createRootApplicationContext();
	2)、AbstractDispatcherServletInitializer:
			创建一个web的ioc容器;createServletApplicationContext();
			创建了DispatcherServlet;createDispatcherServlet();
			将创建的DispatcherServlet添加到ServletContext中;
				getServletMappings();
	3)、AbstractAnnotationConfigDispatcherServletInitializer:注解方式配置的DispatcherServlet初始化器
			创建根容器:createRootApplicationContext()
					getRootConfigClasses();传入一个配置类
			创建web的ioc容器: createServletApplicationContext();
					获取配置类;getServletConfigClasses();
	
总结:
	以注解方式来启动SpringMVC;继承AbstractAnnotationConfigDispatcherServletInitializer;
实现抽象方法指定DispatcherServlet的配置信息;

下面我们找到了SpringServletContainerInitializer 继承ServletContainerInitializer 的实现类源码如下:

代码语言:javascript复制
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    public SpringServletContainerInitializer() {
    }

    public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
        List<WebApplicationInitializer> initializers = new LinkedList();
        Iterator var4;
        if (webAppInitializerClasses != null) {
            var4 = webAppInitializerClasses.iterator();

            while(var4.hasNext()) {
                Class<?> waiClass = (Class)var4.next();
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer)waiClass.newInstance());
                    } catch (Throwable var7) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
        } else {
            servletContext.log(initializers.size()   " Spring WebApplicationInitializers detected on classpath");
            AnnotationAwareOrderComparator.sort(initializers);
            var4 = initializers.iterator();

            while(var4.hasNext()) {
                WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
                initializer.onStartup(servletContext);
            }

        }
    }
}

springmvc-整合

1.创建web初始化器

代码语言:javascript复制
//Web容器启动的时候创建对象;调用方法来初始化容器以及前端控制器
public class MyWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    //获取父容器的配置类:(Spring的配置文件) --->作为父容器
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{RootConfig.class};
    }

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

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

2.父子配置类,spring是父配置类,要排除扫描controller注解,而springmvc是子配置类,只负责扫描controller注解

spring是父配置类:

代码语言:javascript复制
//Spring的容器不扫描Controller,父容器
@ComponentScan(value = {"com.ldc."},excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
})
public class RootConfig
{}

springmvc是子配置类:

代码语言:javascript复制
//SpringMVC只扫描Controller,子容器
//useDefaultFilters = false 禁用默认的过滤规则
@ComponentScan(value = {"com.ldc"},includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
},useDefaultFilters = false)
public class AppConfig {

}

3.我们再来写一个Controller和Service进行测试

代码语言:javascript复制
@Controller
public class HelloController {

    @Autowired
    private HelloService helloService;

    @ResponseBody
    @RequestMapping("hello")
    public String hello() {
        String hello = helloService.sayHello("tomcat");
        return hello;
    }
}
代码语言:javascript复制
@Service
public class HelloService {

    public String sayHello(String name) {
        return "Hello,"   name;
    }
}

springmvc-定制与接管SpringMVC

以前进行的xml配置:

代码语言:javascript复制
将springmvc处理不了的请求交给tomcat的模板引擎解析,这样静态资源就可以访问
<mvc:default-servlet-handler/>
springmvc高级功能开启
<mvc:annotation-driven/>
配置拦截器
<mvc:interceptors><mvc:interceptors/>
配置视图映射(发送的请求不想通过controller,只想直接地跳转到目标页面)
<mvc:view-controller path="/hello" view-name="hello"></mvc:view-controller>
...

注解来定制SpringMVC: @EnableWebMvc

代码语言:javascript复制
定制SpringMVC;
1)、@EnableWebMvc:开启SpringMVC定制配置功能;
	<mvc:annotation-driven/>;

2)、配置组件(视图解析器、视图映射、静态资源映射、拦截器。。。)
	通过一个配置类来实现接口:extends WebMvcConfigurerAdapter
	这样就可以来实现定制配置了

通过一个配置类来实现接口:extends WebMvcConfigurerAdapter, 这样就可以来实现定制配置了

我们就可以这样来写:

代码语言:javascript复制
//SpringMVC只扫描Controller,子容器
//useDefaultFilters = false 禁用默认的过滤规则
@ComponentScan(value = {"com.ldc"},includeFilters = {
        @Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
},useDefaultFilters = false)
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer pathMatchConfigurer) {

    }

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer contentNegotiationConfigurer) {

    }

    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer asyncSupportConfigurer) {

    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer defaultServletHandlerConfigurer) {
        //将SpringMVC处理不了的请求交给tomcat,专门针对于静态资源的,这个时候,静态资源就是可以访问的
        defaultServletHandlerConfigurer.enable();
    }

    @Override
    public void addFormatters(FormatterRegistry formatterRegistry) {
        //添加自定义的类型转换器
    }

    @Override
    public void addInterceptors(InterceptorRegistry interceptorRegistry) {

    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry resourceHandlerRegistry) {

    }

    @Override
    public void addCorsMappings(CorsRegistry corsRegistry) {

    }

    @Override
    public void addViewControllers(ViewControllerRegistry viewControllerRegistry) {

    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry viewResolverRegistry) {

    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> list) {

    }

    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> list) {

    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> list) {

    }

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> list) {

    }

    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {

    }

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {

    }

    @Override
    public Validator getValidator() {
        return null;
    }

    @Override
    public MessageCodesResolver getMessageCodesResolver() {
        return null;
    }
}

但是上面直接实现WebMvcConfigurer接口的方式,有很多的方法用不到,我们可以用这个适配器WebMvcConfigurerAdapter来实现,它实现了WebMvcConfigurer接口:

我们可以来定义一个视图解析器:

代码语言:javascript复制
//SpringMVC只扫描Controller,子容器
//useDefaultFilters = false 禁用默认的过滤规则
@ComponentScan(value = {"com.ldc"},includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
},useDefaultFilters = false)
@EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
    //定制

    //视图解析器

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //默认所有页面都是从/WEB-INF/xxx.jsp
        /*
        *     public UrlBasedViewResolverRegistration jsp() {
        return this.jsp("/WEB-INF/", ".jsp");
    }
        * */
        //registry.jsp();
        //我们也可以自己来写规则
        registry.jsp("/WEB-INF/views/", ".jsp");
    }
}

我们在/WEB-INF/views/这个路径下新建一个success.jsp

代码语言:javascript复制
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>success</h1>
</body>
</html>

问题: 我们在这个路径下放一个图片,然后再写一个jsp来访问它,这个时候,图片是显示不出来的:

现在,我们来配置允许静态资源的访问:

代码语言:javascript复制
//SpringMVC只扫描Controller,子容器
//useDefaultFilters = false 禁用默认的过滤规则
@ComponentScan(value = {"com.ldc"},includeFilters = {
        @Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
},useDefaultFilters = false)
@EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
    //定制

    //视图解析器

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //默认所有页面都是从/WEB-INF/xxx.jsp
        //registry.jsp();
        //我们也可以自己来写规则
        registry.jsp("/WEB-INF/views/", ".jsp");
    }

    //静态资源的访问
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

这个时候,我们的图片就是可以访问了:


注意:如果图片是在WEB-INF下面的,那么获取图片是获取不到的,因为WEB-INF下面的资源不能直接访问,jsp页面可以通过controller跳转方式实现内部资源访问,但是图片嘛…,目前我知识有限,还不知道如何访问


自定义拦截器需要实现HandlerInterceptor接口:

代码语言:javascript复制
public class MyFirstInterceptor implements HandlerInterceptor {
    //在目标方法执行之前执行
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        System.out.println("preHandle...");
        return true;
    }

    //在目标方法执行之后执行
    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...");
    }

    //页面响应以后执行
    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        System.out.println("afterCompletion...");
    }
}

在配置类中注册拦截器

代码语言:javascript复制
//SpringMVC只扫描Controller,子容器
//useDefaultFilters = false 禁用默认的过滤规则
@ComponentScan(value = {"com.ldc"},includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
},useDefaultFilters = false)
@EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
    //定制
    //视图解析器
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //默认所有页面都是从/WEB-INF/xxx.jsp
        /*
        *     public UrlBasedViewResolverRegistration jsp() {
        return this.jsp("/WEB-INF/", ".jsp");
    }
        * */
        //registry.jsp();
        //我们也可以自己来写规则
        registry.jsp("/WEB-INF/views/", ".jsp");
    }

    //静态资源的访问
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
    //配置拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //拦截任意多层路径的任意请求
        registry.addInterceptor(new MyFirstInterceptor()).addPathPatterns("/**");
    }
}

我们重新启动,并且访问这个路径:

我们发现控制台已经打印了,说明拦截器已经起作用了:

代码语言:javascript复制
preHandle…
postHandle…
afterCompletion…

servlet3.0-异步请求

servlet3.0异步处理:

在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求。

即每一次Http请求都由某一个线程从头到尾负责处理。

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


我们可以在之前写的Servlet加上当前的线程:

代码语言:javascript复制
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(Thread.currentThread() " start...");
        try {
            sayHello();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        resp.getWriter().write("hello...");
        System.out.println(Thread.currentThread() " end...");
    }

    private void sayHello() throws InterruptedException {
        System.out.println(Thread.currentThread()  " processing...");
        Thread.sleep(3000);
    }

}

启动服务之后,控制台打印结果为:我们可以发现从线程开始、处理请求到执行结束从始至终都是Thread[http-nio-8081-exec-3,5,main]这个线程,主线程得不到释放,当下一个请求进来就得不到处理;

代码语言:javascript复制
UserFilter…doFilter…
Thread[http-nio-8081-exec-3,5,main] start…
Thread[http-nio-8081-exec-3,5,main] processing…
Thread[http-nio-8081-exec-3,5,main] end…

添加异步处理后:

我们再来加上是哪些线程处理的:

代码语言:javascript复制
@WebServlet(value = "/async",asyncSupported = true)
public class HelloAsyncServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.支持异步处理:asyncSupported = true

        //2.开启异步模式
        System.out.println("主线程开始..." Thread.currentThread() "==>"  Instant.now().toEpochMilli());
        AsyncContext startAsync = req.startAsync();
        //3.业务逻辑进行异步处理,开始异步处理
        startAsync.start(()-> {
            try {
                System.out.println("副线程开始..." Thread.currentThread() "==>"  Instant.now().toEpochMilli());
                sayHello();
                startAsync.complete();
                //获取异步上下文
                //AsyncContext asyncContext = req.getAsyncContext();
                //4.获取响应
                ServletResponse response = startAsync.getResponse();
                response.getWriter().write("hello async...");
                System.out.println("副线程结束..." Thread.currentThread() "==>"  Instant.now().toEpochMilli());
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        System.out.println("主线程结束..." Thread.currentThread() "==>"  Instant.now().toEpochMilli());
    }

    private void sayHello() throws InterruptedException {
        System.out.println(Thread.currentThread()  " processing..." "==>"  Instant.now().toEpochMilli());
        Thread.sleep(3000);
    }
}

执行结果如下:

代码语言:javascript复制
感兴趣的类型:
class com.ldc.service.HelloServiceImpl
class com.ldc.service.HelloServiceExt
class com.ldc.service.AbstractHelloService
UserListener…contextInitialized
[2019-01-18 04:47:06,991] Artifact servlet3.0:war exploded: Artifact is deployed successfully
[2019-01-18 04:47:06,992] Artifact servlet3.0:war exploded: Deploy took 447 milliseconds
UserFilter…doFilter…
UserFilter…doFilter…
UserFilter…doFilter…
主线程开始…Thread[http-nio-8081-exec-7,5,main]>1547801232248
主线程结束…Thread[http-nio-8081-exec-7,5,main]>1547801232253
副线程开始…Thread[http-nio-8081-exec-8,5,main]>1547801232253
Thread[http-nio-8081-exec-8,5,main] processing…>1547801232253
副线程结束…Thread[http-nio-8081-exec-8,5,main]==>1547801235253

现在的处理就是可以表示成下图:


springmvc-异步请求-返回Callable

代码语言:javascript复制
@Controller
public class AsyncController {
	 /**
     * 1、控制器返回Callable
     * 2、Spring异步处理,将Callable 提交到 TaskExecutor 使用一个隔离的线程进行执行
     * 3、DispatcherServlet和所有的Filter退出web容器的线程,但是response 保持打开状态;
     * 4、Callable返回结果,SpringMVC将请求重新派发给容器,恢复之前的处理;
     * 5、根据Callable返回的结果。SpringMVC继续进行视图渲染流程等(从收请求-视图渲染)。
     *
     * preHandle.../springmvc-annotation/async01
     主线程开始...Thread[http-bio-8081-exec-3,5,main]==>1513932494700
     主线程结束...Thread[http-bio-8081-exec-3,5,main]==>1513932494700
     =========DispatcherServlet及所有的Filter退出线程============================

     ================等待Callable执行==========
     副线程开始...Thread[MvcAsync1,5,main]==>1513932494707
     副线程开始...Thread[MvcAsync1,5,main]==>1513932496708
     ================Callable执行完成==========

     ================再次收到之前重发过来的请求========
     preHandle.../springmvc-annotation/async01
     postHandle...(Callable的之前的返回值就是目标方法的返回值)
     afterCompletion...

     异步的拦截器:
     1)、原生API的AsyncListener
     2)、SpringMVC:实现AsyncHandlerInterceptor;
     * @return
     */
    @ResponseBody
    @RequestMapping("/async01")
    public Callable<String> async01() {
        System.out.println("主线程开始..."   Thread.currentThread()   "==>"   Instant.now().getEpochSecond());
        Callable<String> callable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println("副线程开始..."   Thread.currentThread()   "==>"   Instant.now().getEpochSecond());
                Thread.sleep(2000);
                System.out.println("副线程结束..."   Thread.currentThread()   "==>"   Instant.now().getEpochSecond());
                return "Callable<String> async01()";
            }
        };
        System.out.println("主线程结束..."   Thread.currentThread()   "==>"   Instant.now().getEpochSecond());
        return callable;
    }

}

此时运行起来的测试结果如下:

preHandle… 主线程开始…Thread[http-nio-8081-exec-7,5,main]>1547802269 主线程结束…Thread[http-nio-8081-exec-7,5,main]>1547802269 副线程开始…Thread[MvcAsync1,5,main]>1547802269 副线程结束…Thread[MvcAsync1,5,main]>1547802271 Callable返回结果,SpringMVC将请求重新派发给容器,恢复之前的处理; preHandle… postHandle… afterCompletion…


springmvc-异步请求-返回DeferredResult

我们来看一个实际的应用场景:


代码语言:javascript复制
public class DeferredResultQueue {

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

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

代码语言:javascript复制
@Controller
public class AsyncController {


    @ResponseBody
    @RequestMapping("/createOrder")
    public DeferredResult<Object> createOrder()
    {
                         //设置当前请必须在三秒内执行完,否则显示错误消息
        DeferredResult<Object> deferredResult = new DeferredResult<>((long)3000, "create fail...");

        DeferredResultQueue.save(deferredResult);

        return deferredResult;
    }


    @ResponseBody
    @RequestMapping("/create")
    public String create(){
        //创建订单
        String order = UUID.randomUUID().toString();
        DeferredResult<Object> deferredResult = DeferredResultQueue.get();
        //只要这边设置了处理结果,对应的那边就可以得到返回
        deferredResult.setResult(order);
        return "success===>" order;
    }
}

我们先访问这个创建订单createOrder接口:

我们再来访问这个create接口,此时的结果如图所示:

在来看createOrder的返回结果:

0 人点赞