1. Servlet3.0 新特性
1.1 Servlet 新特性
web 容器在启动的时候,会扫描每个 jar 包下的 META-INF/services/javax.servlet.ServletContainerInitializer
,可以使用 @HandlesTypes
将指定的类传入到 onStartup(Set<Class<?>> set, ServletContext servletContext)
方法中的 set 集合里
1.2 环境搭建
代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见
tutorial-spring-framework/tutorial-spring-framework-servlet3.0
工程
1.2.1 配置文件
1. pom.xml
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
2. META-INF/services/javax.servlet.ServletContainerInitializer
pers.masteryourself.tutorial.spring.framework.servlet3.MyServletContainerInitializer
1.2.2 核心代码
1. MyServletContainerInitializer
@HandlesTypes(ServletInitializer.class)
public class MyServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
// 打印 @HandlesTypes 传入的类
for (Class<?> clazz : set) {
System.out.println("@HandlesTypes 传入的类有:" + clazz.getName());
}
// 注册组件
servletContext.addServlet("MyHttpServlet", MyHttpServlet.class)
.addMapping("/servlet3");
servletContext.addListener(MyServletContextListener.class);
servletContext.addFilter("MyFilter", MyFilter.class)
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
}
}
2. Spring MVC 定制
2.1 Spring MVC 集成原理
在 spring-web-xxx.RELEASE.jar
里,META-INF/services/javax.servlet.ServletContainerInitializer
所对应的内容是 org.springframework.web.SpringServletContainerInitializer
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
它在 onStartup()
方法里会去回调每一个 initializer 的 onStartup(servletContext)
方法,然后分别初始化 Spring 容器和 Spring MVC 容器,最后通过 ContextLoaderListener#contextInitialized()
和 HttpServletBean#init()
完成容器的 refresh()
操作
启动 Spring 容器和 Spring MVC 容器
2.2 环境搭建
代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见
tutorial-spring-framework/tutorial-spring-framework-web
工程
2.2.1 配置文件
1. pom.xml
<packaging>war</packaging>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
2.2.2 核心代码
1. MyWebAppInitializer
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 获取 Spring 配置,相当于 Spring 父容器
*
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
/**
* 获取 SpringMVC 配置文件,相当于 Spring 子容器
*
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
/**
* 获取 DispatcherServlet 的映射信息
* /:拦截所有请求(包括静态资源(xx.js,xx.png)),但是不包括 *.jsp
* /*:拦截所有请求;连 *.jsp 页面都拦截;jsp 页面是 tomcat 的 jsp 引擎解析的
*
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
2. SpringConfig
@ComponentScan(value = "pers.masteryourself.tutorial.spring.framework.web", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
})
public class SpringConfig {
}
3. SpringMvcConfig
@ComponentScan(value = "pers.masteryourself.tutorial.spring.framework.web", includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
}, useDefaultFilters = false)
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
/**
* 视图解析器
*
* @param registry
*/
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/views/", ".jsp");
}
/**
* 静态资源访问
*
* @param configurer
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
/**
* 拦截器
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor afterCompletion");
}
}).addPathPatterns("/**");
}
}
3. 异步处理
代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见
tutorial-spring-framework/tutorial-spring-framework-async
工程
3.1 异步 Callable
3.1.1 核心代码
1. AsyncController
@Controller
public class AsyncController {
/**
* 如果方法返回 {@link Callable},Spring 将会异步处理,将任务提交到 {@link org.springframework.core.task.TaskExecutor},使用一个隔离的线程执行
* {@link org.springframework.web.servlet.DispatcherServlet} 和所以的 {@link javax.servlet.Filter} 退出 web 容器的线程,但是 response 保持打开状态
* {@link Callable} 返回结果,Spring MVC 将请求重新派发给容器,恢复之前的处理,根据 {@link Callable} 返回的结果,Spring MVC 继续进行视图渲染流程(接收请求 -> 视图渲染)
*
* @return
*/
@RequestMapping(value = "/async")
@ResponseBody
public Callable<String> async() {
System.out.println("主线程开始:" + Thread.currentThread().getName());
Callable<String> result = () -> {
System.out.println("任务线程开始:" + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(2);
System.out.println("任务线程结束:" + Thread.currentThread().getName());
return "success";
};
System.out.println("主线程结束:" + Thread.currentThread().getName());
return result;
// 主线程开始:http-nio-8080-exec-4
//主线程结束:http-nio-8080-exec-4
//任务线程开始:MvcAsync1
//任务线程结束:MvcAsync1
}
}
3.2 长连接 DeferredResult
3.2.1 核心代码
1. AsyncController
@Controller
public class DeferredResultController {
private Queue<DeferredResult<String>> queue = new ConcurrentLinkedQueue<>();
/**
* 可以将返回值定义成 {@link DeferredResult},一旦调用 setResult() 方法,就会返回结果
*
* @return
*/
@ResponseBody
@RequestMapping("/createOrder")
public DeferredResult<String> createOrder() {
DeferredResult<String> deferredResult = new DeferredResult<>((long) 3000, "create fail...");
queue.add(deferredResult);
return deferredResult;
}
@ResponseBody
@RequestMapping("/doCreate")
public String doCreate() {
DeferredResult<String> deferredResult = queue.poll();
if (deferredResult == null) {
return null;
}
deferredResult.setResult(UUID.randomUUID().toString());
return "success";
}
}