2.ServletContainerInitalizer
容器在启动应用的时候,会扫描当前应用每一个jar包里面的src/main/webapp/META-INF/services/javax.servlet.ServletContainerInitializer指定实现的类,启动并运行这个实现类的方法。
(1).创建我们的指定扫描的文件并编辑

(2).编写被扫描的文件信息
package com.jsxs.servlet;
import com.jsxs.services.HelloService;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
import java.util.Set;
/**
* @Author Jsxs
* @Date 2023/8/27 10:41
* @PackageName:com.jsxs.servlet
* @ClassName: MyServletContainerInitalizer
* @Description: TODO
* @Version 1.0
*/
// 容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口,抽象类)传递过来
@HandlesTypes(value = {HelloService.class}) // 传入我们感兴趣的类型并赋值给set 🚹
public class MyServletContainerInitializer implements ServletContainerInitializer { // 1.继承接口并实现方法 ⭐
// 2.应用启动的时候会调用这个方法
/**
* @param set : 负责接受我们感兴趣类型的所有子类型 即HelloService🚹
* @param servletContext: 代表当前web应用的ServletContext,一个web应用相当于一个ServletContext
* @throws ServletException
*/
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
System.out.println("感兴趣的类型"+set);
for (Class<?> aClass : set) {
System.out.println(aClass);
}
}
}
1.抽象类
package com.jsxs.services;
/**
* @Author Jsxs
* @Date 2023/8/27 10:54
* @PackageName:com.jsxs.services
* @ClassName: HelloServiceAbart
* @Description: TODO 父接口的抽象子类
* @Version 1.0
*/
public abstract class HelloServiceAbart implements HelloService{
}
2.子接口: 接口继承接口
package com.jsxs.services;
/**
* @Author Jsxs
* @Date 2023/8/27 10:53
* @PackageName:com.jsxs.services
* @ClassName: HelloServiceExt
* @Description: TODO 父接口的子接口
* @Version 1.0
*/
public interface HelloServiceExt extends HelloService{
}
3.实现类
package com.jsxs.services;
/**
* @Author Jsxs
* @Date 2023/8/27 10:56
* @PackageName:com.jsxs.services
* @ClassName: HelloServiceImpi
* @Description: TODO 普通类继承接口
* @Version 1.0
*/
public class HelloServiceImpi implements HelloService{
}
(3).将要被扫描的类全限定名写入文件中
com.jsxs.servlet.MyServletContainerInitializer
• 1

(4).运行结果
我们发现不管是子类 子接口 还是抽象类 实现类都被打印出全限定名了。
3.ServletContext 注册三大组件
过滤器 + 监听器 + Servlet
1.要注册的过滤器
package com.jsxs.servlet;
import javax.servlet.*;
import java.io.IOException;
/**
* @Author Jsxs
* @Date 2023/8/27 12:28
* @PackageName:com.jsxs.servlet
* @ClassName: UserFilter
* @Description: TODO 过滤器: 使用的是Servlet的包
* @Version 1.0
*/
public class UserFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
// 过滤请求,假如为false那么就拦截,true就放行
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 过滤请求
System.out.println("UserFilter...");
// 放行
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
2.要注册的监听器
package com.jsxs.servlet;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* @Author Jsxs
* @Date 2023/8/27 12:34
* @PackageName:com.jsxs.servlet
* @ClassName: UserListener
* @Description: TODO 监听项目的启动和停止
* @Version 1.0
*/
public class UserListener implements ServletContextListener {
// 监听项目的开始和初始化
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("UserListener---项目初始化成功....");
}
// 监听销毁
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("UserListener---项目关闭成功....");
}
}
3. 要注册的Servlet
package com.jsxs.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Author Jsxs
* @Date 2023/8/27 12:39
* @PackageName:com.jsxs.servlet
* @ClassName: UserServlet
* @Description: TODO
* @Version 1.0
*/
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("tomcate...");
}
}
4.进行注册的操作
package com.jsxs.servlet;
import com.jsxs.services.HelloService;
import javax.servlet.*;
import javax.servlet.annotation.HandlesTypes;
import java.util.EnumSet;
import java.util.Set;
/**
* @Author Jsxs
* @Date 2023/8/27 10:41
* @PackageName:com.jsxs.servlet
* @ClassName: MyServletContainerInitalizer
* @Description: TODO
* @Version 1.0
*/
// 容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口)传递过来
@HandlesTypes(value = {HelloService.class}) // 传入我们感兴趣的类型并赋值给set 🚹
public class MyServletContainerInitializer implements ServletContainerInitializer { // 1.继承接口并实现方法 ⭐
// 2.应用启动的时候会调用这个方法
/**
* @param set : 负责接受我们感兴趣类型的所有子类型 即HelloService🚹
* @param servletContext: 代表当前web应用的ServletContext,一个web应用相当于一个ServletContext
* @throws ServletException
*/
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
System.out.println("感兴趣的类型"+set);
for (Class<?> aClass : set) {
System.out.println(aClass);
}
// 这里我们向容器中注册监听组件和过滤组件 ⭐⭐
// (1).添加Servlet组件 -> 具体的映射请求 ⭐⭐⭐
ServletRegistration.Dynamic userServlet = servletContext.addServlet("UserServlet", new UserServlet());
// 配置要映射请求的路径
userServlet.addMapping("/user");
// (2).过滤器⭐⭐⭐
FilterRegistration.Dynamic userFilter = servletContext.addFilter("UserFilter", UserFilter.class);
// 配置映射的信息
userFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),true,"/*"); // 请求方式 是否为true 映射路径
// (3).添加监听器⭐⭐⭐
servletContext.addListener(UserListener.class);
}
}
4.SpringMVC整合分析
1.web容器在启动的时候,会扫描每个jar包下的src/main/webapp/META-INF/services/javax.servlet.ServletContainerInitializer的全限定名
2.根据全限定名加载这个文件指定的类
3.Spring的应用一启动会加载感兴趣的WebApplicationInitializer下的所有组件
4.并且为WebApplicationInitializer组件创建对象(组件不是接口,不是抽象类)
(1).AbstractContextLoaderInitializer 创建跟容器,createRootApplicationContext();
(2).AbstractDispatcherServletInitializer
1.创建一个web的ioc容器 createServletApplicationContext()
2.创建一个DispatchServlet createDispatchServlet()
3.将创建的DispatchServlet添加到ServletContext中
(3). AbstractAnnotationConfigDispatcherServletInitializer注解方式配置的DispatcherServlet初始化器
1.创建更容器: createRootApplicationContext()
getRootConfigClasses(); 传入一个配置类
2.创建web的ioc容器 createServletApplicationContext()
总结:
以注解的方式启动SpringMvc,继承AbstractAnnotationConfigDispatcherServletInitializer实现抽象发发指定DipatcherServlet的配置信息
5.Servlet 异步
(1).AsyncContext 实现异步处理
有时候我们在处理一个数据的时候,会有相对应的时间限制。在实际的用户体验中可能会产生很糟糕的效应。所以我们要使用异步的方式来减少时间差。
package com.jsxs.servlet;
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;
/**
* @Author Jsxs
* @Date 2023/8/27 16:10
* @PackageName:com.jsxs.servlet
* @ClassName: HelloAsyncServlet
* @Description: TODO
* @Version 1.0
*/
@WebServlet(value = "/async",asyncSupported = true) // 设置映射的的路径和支持异步 ⭐
public class HelloAsyncServlet extends HttpServlet {
@Override
protected void doGet(final HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
final AsyncContext asyncContext = req.startAsync(); // 2.开启异步模式 ⭐⭐
System.out.println("主线程----------------------");
// 3.通过使用线程的方式进行异步处理 ⭐⭐⭐
asyncContext.start(new Runnable() {
@Override
public void run() {
System.out.println("hello-------------------------------------");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 4. 通知异步处理完毕 ⭐⭐⭐⭐
asyncContext.complete();
// 5.获取到异步的上下文 ⭐⭐⭐⭐⭐
AsyncContext asyncContext1 = req.getAsyncContext();
// 6.获取响应 ⭐⭐⭐⭐⭐⭐
ServletResponse response = asyncContext1.getResponse();
try {
response.getWriter().write("hello async...");
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}

(2).Callable 实现异步处理
package com.jsxs.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.concurrent.Callable;
/**
* @Author Jsxs
* @Date 2023/8/27 16:37
* @PackageName:com.jsxs.controller
* @ClassName: AsyncController
* @Description: TODO
* @Version 1.0
*/
@Controller
public class AsyncController {
@RequestMapping("/async")
@ResponseBody
public Callable<String> async01(){
System.out.println("主线程开始"+System.currentTimeMillis());
Callable<String> callable=new Callable<String>(){
@Override
public String call() throws Exception {
System.out.println("副线程开始A"+System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("副线程结束B"+System.currentTimeMillis());
return "Callabled";
}
};
System.out.println("主线程结束"+System.currentTimeMillis());
return callable;
}
}
(3).DeferredResult 实现异步

我们以创建订单为例,创建订单的请求一进来,应用1就要启动一个线程,来帮我们处理这个请求。如果假设应用1并不能创建订单,创建订单需要应用2来完成,那么此时应该怎么办呢?应用1可以把创建订单的消息存放在消息中间件中,比如RabbitMQ、Kafka等等,而应用2就来负责监听这些消息中间件里面的消息,一旦它发现有创建订单这个消息,那么它就进行相应处理,然后将处理完成后的结果,比如订单的订单号等等,再次存放在消息中间件中,接着应用1再启动一个线程,例如线程2,来监听消息中间件中的返回结果,只要订单创建完毕,它就会拿到返回的结果(即订单的订单号),最后将其响应给客户端。
1.创建订单
package com.meimeixia.controller;
import java.util.concurrent.Callable;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;
@Controller
public class AsyncController {
@ResponseBody
@RequestMapping("/createOrder")
public DeferredResult<Object> createOrder() {
/*
* 在创建DeferredResult对象时,可以像下面这样传入一些参数哟!
*
* 第一个参数(timeout): 超时时间。限定(请求?)必须在该时间内执行完,如果超出时间限制,那么就会返回一段错误的提示信息(timeoutResult)
* 第二个参数(timeoutResult):超出时间限制之后,返回的错误提示信息
*/
DeferredResult<Object> deferredResult = new DeferredResult<>((long)3000, "create fail..."); ⭐
return deferredResult;
}
@ResponseBody
@RequestMapping("/async01")
public Callable<String> async01() {
System.out.println("主线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("副线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
Thread.sleep(2000); // 我们来睡上2秒
System.out.println("副线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
// 响应给客户端一串字符串,即"Callable<String> async01()"
return "Callable<String> async01()";
}
};
System.out.println("主线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
return callable;
}
}

很明显这是超出时间限制了,应该是相当于创建订单失败了吧!那接下来,我们应该怎么办呢?真令人头疼😭
OK,我们不妨来模拟一个队列。首先新建一个类,例如DeferredResultQueue,如下所示。
2.模拟队列
package com.meimeixia.service;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.springframework.web.context.request.async.DeferredResult;
public class DeferredResultQueue {
// DeferredResult对象临时保存的地方
private static Queue<DeferredResult<Object>> queue = new ConcurrentLinkedQueue<DeferredResult<Object>>(); ⭐
// 临时保存DeferredResult对象的方法 ⭐⭐
public static void save(DeferredResult<Object> deferredResult) {
queue.add(deferredResult);
}
// 获取DeferredResult对象的方法 ⭐⭐⭐
public static DeferredResult<Object> get() {
/*
* poll():检索并且移除,移除的是队列头部的元素
*/
return queue.poll();
}
}
然后,修改一下AsyncController中的createOrder方法。上面我也已经说过了,该方法并不能真正地来处理创建订单的请求。即使如此,那也没关系,因为我们可以在该方法中先把new出来的DeferredResult对象临时保存起来。
3.将正在创建中的订单临时保存起来
package com.meimeixia.controller;
import java.util.concurrent.Callable;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;
import com.meimeixia.service.DeferredResultQueue;
@Controller
public class AsyncController {
@ResponseBody
@RequestMapping("/createOrder")
public DeferredResult<Object> createOrder() {
/*
* 在创建DeferredResult对象时,可以像下面这样传入一些参数哟!
*
* 第一个参数(timeout): 超时时间。限定(请求?)必须在该时间内执行完,如果超出时间限制,那么就会返回一段错误的提示信息(timeoutResult)
* 第二个参数(timeoutResult):超出时间限制之后,返回的错误提示信息
*/
DeferredResult<Object> deferredResult = new DeferredResult<>((long)3000, "create fail...");
DeferredResultQueue.save(deferredResult); ⭐// 保存起来
return deferredResult;
}
@ResponseBody
@RequestMapping("/async01")
public Callable<String> async01() {
System.out.println("主线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("副线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
Thread.sleep(2000); // 我们来睡上2秒
System.out.println("副线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
// 响应给客户端一串字符串,即"Callable<String> async01()"
return "Callable<String> async01()";
}
};
System.out.println("主线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
return callable;
}
}
当然了,实际开发中应该有一个别的线程来专门监听这个事,啥事?我猜应该是在其他的地方临时保存DeferredResult对象这件事吧!不过,我们在这儿并不会这样做,而是再在AsyncController中编写一个方法,例如create,该方法才是真正来处理创建订单的请求的。
4.真正创建订单号(监听)
@ResponseBody
@RequestMapping("/create")
public String create() {
// 在这模拟创建订单
String order = UUID.randomUUID().toString();
/*
* 如果我们想在上一个请求(即createOrder)中使用订单,那么该怎么办呢?从临时保存DeferredResult对象的地方获取
* 到刚才保存的DeferredResult对象,然后调用其setResult方法设置结果,例如设置订单的订单号
*/
DeferredResult<Object> deferredResult = DeferredResultQueue.get(); // ⭐
deferredResult.setResult(order); // 设置结果
// 这儿给客户端直接响应"success===>订单号"这样的字符串,不要再跳转页面了
return "success===>" + order;
}
至此,可以看到,只要客户端发送一个createOrder
请求进来创建订单,那么服务端就会先将new出来的DeferredResult对象临时保存起来。等到create方法触发并把订单号设置进去之后,在createOrder方法中就会立即得到返回结果,即订单号。
最后,我们来重启项目进行测试。重启成功之后,先来访问createOrder请求,以便来创建订单,但是订单必须得在3秒内创建完,所以一旦访问了createOrder请求后,你必须立即访问create请求来真正创建订单,而且至少得在3秒内完成。
这时,你会看到什么结果呢?可以看到访问create请求之后,直接给浏览器页面响应了一个success===>4e7e3e4c-27d2-4989-87c1-00d545e05feb这样的字符串,其中4e7e3e4c-27d2-4989-87c1-00d545e05feb就是所创建订单的订单号,如下图所示。

切到访问createOrder请求的浏览器窗口之后,你也可以在浏览器页面中看到所创建订单的订单号,即4e7e3e4c-27d2-4989-87c1-00d545e05feb,如下图所示。
