新的servlet3.0规范,定义了很多web注解,web工程可以不需要web.xml文件了。首先我们来创建一个没有web.xml的maven工程
<!-- 原生web程序,没有集成spring,所以是war包 -->
<packaging>war</packaging>
<!-- 添加servlet依赖,Servlet3.0在tomcat7+使用 -->
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<!-- 使用该插件后,eclipse就不会包遗失web.xml文件 -->
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
使用注解创建Servlet、Filter、Listener
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
public void sayHello() throws Exception{
}
}
@WebFilter("/*")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
@WebListener
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
通过ServletContainerInitializer注册Servlet、Filter、Listener
//创建ServletContainerInitializer的实现类
//web容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口等)传递过来;
@HandlesTypes(value={HelloService.class}) //注意:该类必须是一个父类或接口,下面必须有实现类,否则启动报错
public class MyServletContainerInitializer implements ServletContainerInitializer {
//web程序启动时,会调用该方法
@Override
public void onStartup(Set<Class<?>> arg0, ServletContext sc) throws ServletException {
for (Class<?> claz : arg0) {
//claz.isInterface 是否是接口 claz.里面还有其他方法
System.out.println(claz); //输出class cn.fg.service.HelloServiceImpl 它是HelloService的实现类
}
//注册Servlet
ServletRegistration.Dynamic servlet = sc.addServlet("userServlet", new UserServlet()); //可以new,也可以UserServlet.class
servlet.addMapping("/user"); //配置servlet的映射信息
//注册Listener
sc.addListener(UserListener.class);
//注册Filter FilterRegistration
FilterRegistration.Dynamic filter = sc.addFilter("userFilter", UserFilter.class);
//配置Filter的映射信息,第二个参数为是否排在上一个Filter之后,第1个参数为null,默认DispatcherType.REQUEST
filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
}
}
//web程序启动时为什么会调用onStartup()
1. 扫描所有jar包classpath目录下的META-INF/services/javax.servlet.ServletContainerInitializer这样一个文件(包括lib下的jar包)
2. 文件中的内容就是ServletContainerInitializer实现类的全类名,例如cn.fg.servlet.MyServletContainerInitializer
Servlet3.0 与 SpringMVC 分析
1、web容器在启动的时候,会扫描spring-web-xxx.jar下的META-INF/services/javax.servlet.ServletContainerInitializer
2、加载这个文件指定的类org.springframework.web.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();
使用非web.xml的方式配置SpringMVC
//web容器启动的时候创建对象;调用方法来初始化容器以及前端控制器
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
//获取根容器的配置类;(Spring的配置文件) 父容器;
@Override
protected Class<?>[] getRootConfigClasses() {
// TODO Auto-generated method stub
return new Class<?>[]{RootConfig.class};
}
//获取web容器的配置类(SpringMVC配置文件) 子容器;
@Override
protected Class<?>[] getServletConfigClasses() {
// TODO Auto-generated method stub
return new Class<?>[]{WebConfig.class};
}
//获取DispatcherServlet的映射信息
// /:拦截所有请求(包括静态资源(xx.js,xx.png)),但是不包括*.jsp;
// /*:拦截所有请求;连*.jsp页面都拦截;jsp页面是tomcat的jsp引擎解析的;
@Override
protected String[] getServletMappings() {
// TODO Auto-generated method stub
return new String[]{"/"};
}
}
//Spring的容器不扫描controller;父容器
@ComponentScan(value="com.atguigu",excludeFilters={
@Filter(type=FilterType.ANNOTATION,classes={Controller.class})
})
public class RootConfig {
}
//SpringMVC只扫描Controller;子容器
//useDefaultFilters=false 禁用默认的过滤规则;
@ComponentScan(value="com.atguigu",includeFilters={
@Filter(type=FilterType.ANNOTATION,classes={Controller.class})
},useDefaultFilters=false)
public class WebConfig {
}
使用Java类配置Spring MVC
@EnableWebMvc //启用springmvc注解 等同于 <mvc:annotation-driven />
//本来是implements WebMvcConfigurer接口的,但是WebMvcConfigurer接口的方法太多了,用不到这些,所有我们继承了WebMvcConfigurerAdapter
public class WebConfig extends WebMvcConfigurerAdapter {
//配置视图解析器 等同于 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">......</bean>
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/page/", ".jsp");
}
//配置静态资源的处理 等同于 <mvc:default-servlet-handler />
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
//配置拦截器 等同于<mvc:interceptors>......</mvc:interceptors>
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyFirstInterceptor()).addPathPatterns("/**");
}
}
Servlet 异步请求
//非异步请求会占用tomcat线程一直到线程结束才会释放
@WebServlet(value="/async",asyncSupported=true) //开启异步支持
public class HelloAsyncServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
AsyncContext startAsync = req.startAsync(); //获取AsyncContext
startAsync.start(new Runnable() { //启动一个线程,这里可以使用自定义的线程池处理业务逻辑
@Override
public void run() {
sayHello(); //调用业务逻辑
startAsync.complete(); //异步处理完成
ServletResponse response = asyncContext.getResponse(); //拿到Response
response.getWriter().write("hello async..."); //输出响应内容
}
});
}
public void sayHello() {
}
}
注意:一旦servlet开启异步,对应的flter也要开启异步,否则会报错
Spring MVC 的异步请求(Callable)
//注意开启servlet异步支持
@Controller
public class AsyncController {
@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);
System.out.println("副线程开始..."+Thread.currentThread()+"==>"+System.currentTimeMillis());
return "Callable<String> async01()";
}
};
System.out.println("主线程结束..."+Thread.currentThread()+"==>"+System.currentTimeMillis());
return callable;
}
/**
* 执行原理
* 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...
afterCompletion...
异步的拦截器:
1)、原生API的AsyncListener
2)、SpringMVC:实现AsyncHandlerInterceptor;
*/
}
public class MyFirstInterceptor implements HandlerInterceptor {
//目标方法运行之前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// TODO Auto-generated method stub
System.out.println("preHandle..."+request.getRequestURI());
return true;
}
//目标方法执行正确以后执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
System.out.println("postHandle...");
}
//页面响应以后执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// TODO Auto-generated method stub
System.out.println("afterCompletion...");
}
}
Spring MVC 的异步请求(DeferredResult)
@RequestMapping("test04")
public DeferredResult<String> test04(){
DeferredResult<String> deferredResult = new DeferredResult<>(5000l,"error"); //如果超时还没有拿到消息,则跳转error页面
MyCacheUtil.setCache("001", deferredResult); //使用缓存模拟发送消息到mq(消息中间件)
return deferredResult;
}
//这里我们请求receive,来模拟监听mq(消息中间件)得到消息
@ResponseBody
@RequestMapping("receive")
public DeferredResult<String> receive(){
//使用缓存模拟取出消息
DeferredResult<String> deferredResult = (DeferredResult<String>) MyCacheUtil.getCache("001");
//设置Result的值,值的类型是根据泛型定的。该值就是上面test04的返回值,然后会跳转test01页面
deferredResult.setResult("test01");
return deferredResult;
}
参考手册:
https://docs.spring.io/spring/docs/5.2.5.RELEASE/spring-framework-reference/web.html#spring-web
https://docs.spring.io/spring/docs/4.3.26.RELEASE/spring-framework-reference/htmlsingle/#mvc