spring注解驱动之Web
servlet3.0 标准以后提供了使用注解的方式来注入组件(servlet、filter、DispatchServlet),包括一些运行时可插拔的插件能力
servlet3.0 属于JSR315系列的规范,但是需要tomcat7及以上版本的支持
关于注解驱动和插件能力的说明详细说明请参考 官网文档
tomcat版本支持请参考tomcat官方说明
演示使用原生servlet注解开发
引入servlet依赖
|
|
编写java程序
|
|
使用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
直接新建package
META-INF会不能直接创建,但是可以直接创建META-INF/services
,然后再在这个路径下新建文件即可
演示:
-
编写待注册的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.........."); } }
-
编写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("销毁方法。。。。。。。。。。。"); } }
-
编写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); } }
-
编写注册三大组件的配置类,实现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, "/*"); } }
-
识别ServletContainerInitializer实现类
文件:META-INF/services/javax.servlet.ServletContainerInitializer
1
com.eh.servlet.CustomsContainerInit
-
启动容器,访问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添加组件,必须在项目启动的时候添加,也就是只能在下面两处地方
- 像演示中所示,使用ServletContainerInitializer得到的ServletContext来创建组件
- javax.servlet.ServletContainerInitializer#onStartup
springmvc整合servlet3.0+
最终效果:
环境准备
整合分析
-
web容器在启动的时候,会扫描买个jar包下的META-INF/services/javax.servlet.ServletContainerInitializer文件
-
加载这个文件指定的类org.springframework.web.SpringServletContainerInitializer(在spring-web项目中)
1 2
@HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer {
-
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)
-
并且为WebApplicationInitializer组件创建对象(组件不是接口,也不是抽象类)
-
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"); } }
-
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); }
-
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的配置信息。
|
|
整合演示
演示环境:
- tomcat8
引入依赖:
|
|
根容器配置类
|
|
子容器配置类
|
|
编写AbstractAnnotationConfigDispatcherServletInitializer的子类
|
|
编写测试类Controller和测试类Service
HelloService
|
|
HelloController
|
|
演示结果:
定制与接管SpringMVC
更多配置请参考 官方文档
-
@EnableWebMvc,开启SpringMVC定制配置功能
相当于
<mvc:annotation-driven/>
-
配置组件(视图解析器、视图映射、静态资源映射、拦截器等等),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请求都由某一个线程从头到尾负责处理。
如果一个请求需要进行IO操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。即便是像Spring、Struts这样的高层框架也脱离不了这样的桎梏,因为他们都是建立在Servlet之上的。为了解决这样的问题,Servlet 3.0引入了异步处理,然后在Servlet 3.1中又引入了非阻塞IO来进一步增强异步处理的性能。
同步演示
|
|
|
|
可以看到是同一个线程
异步演示
|
|
输出:
|
|
这里使用了异步线程,但是还是同一个线程池,我们可以单独指定一个异步处理线程池
springmvc中使用异步请求
演示
|
|
输出:
|
|
异步的拦截器:
- 原生的AsyncListener
- SpringMVC:实现AsyncHandlerInterceptor
实际应用场景
springmvc实现上述方式:
|
|
3s之后会出现超时
给deferredResult设置值
可以将deferredResult保存在Queue中,在另一个线程中取出deferredResult并将值设置回去
queue:
|
|
另一个线程:
这里以另一个请求作为演示
|
|
先调用/createOrder,在3s之内调用/create,