【Spring】内嵌Tomcat&去Xml&调试Mvc
菜瓜:今天听到个名词“父子容器”,百度了一下,感觉概念有点空洞,这是什么核武器?
水稻:你说的是SpringMvc和Spring吧,其实只是一个概念而已,用来将两个容器做隔离,起到解耦的作用,其中子容器可以拿到父容器的bean,父容器拿不到子容器的。但是SpringBoot出来之后这个概念基本就被淡化掉,没有太大意义,SpringBoot中只有一个容器了。
菜瓜:能不能给个demo?
水稻:可以。由于现在SpringBoot已经大行其道,Mvc你可能接触的少,甚至没接触过。
- 早些年启动一个Mvc项目费老鼻子劲了,要配置各种Xml文件(Web.xml,spring.xml,spring-dispather.xml),然后开发完的项目要打成War包发到Tomcat容器中
- 现在可以直接引入Tomcat包,用main方法直接调起。为了调试方便,我就演示一个Pom引入Tomcat的例子
- ①启动类
package com.vip.qc.mvc; import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.startup.Tomcat;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; /**
* 参考: * https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-servlet
* <p>
* 嵌入tomcat,由Tomcat发起对Spring容器的初始化调用过程
* <p>
* - 启动过程
* * - Servlet规范,Servlet容器在启动之后会SPI加载META-INF/services目录下的实现类并调用其onStartup方法
* * - Spring遵循规范实现了ServletContainerInitializer接口。该接口在执行时会收集WebApplicationInitializer接口实现类并循环调用其onStartup方法
* * - 其中AbstractDispatcherServletInitializer
* * * - 将spring上下文放入ContextLoaderListener监听器,该监听会发起对refresh方法的调用
* * * - 注册dispatcherServlet,后续会由tomcat调用HttpServletBean的init方法,完成子容器的refresh调用
* *
*
* @author QuCheng on 2020/6/28.
*/
public class SpringWebStart { public static void main(String[] args) {
Tomcat tomcat = new Tomcat();
try {
// 此处需要取一个目录
Context context = tomcat.addContext("/", System.getProperty("java.io.tmp"));
context.addLifecycleListener((LifecycleListener) Class.forName(tomcat.getHost().getConfigClass()).newInstance());
tomcat.setPort(8081);
tomcat.start();
tomcat.getServer().await();
} catch (LifecycleException | ClassNotFoundException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
} static class MyWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { private final static String PACKAGE_PATH = "com.vip.qc.mvc.controller";
private final static String PACKAGE_PATH_CHILD = "com.vip.qc.mvc.service"; @Override
protected String[] getServletMappings() {
return new String[]{"/"};
} @Override
protected Class<?>[] getRootConfigClasses() {
// spring 父容器
return new Class[]{AppConfig.class};
} @Override
protected Class<?>[] getServletConfigClasses() {
// servlet 子容器
return new Class[]{ServletConfig.class};
} @Configuration
@ComponentScan(value = PACKAGE_PATH_CHILD, excludeFilters = @ComponentScan.Filter(classes = Controller.class))
static class AppConfig {
} @Configuration
@ComponentScan(value = PACKAGE_PATH, includeFilters = @ComponentScan.Filter(classes = Controller.class))
static class ServletConfig {
}
} }- ②Controller&Service
package com.vip.qc.mvc.controller; import com.vip.qc.mvc.service.ServiceChild;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import javax.annotation.Resource; /**
* @author QuCheng on 2020/6/28.
*/
@Controller
public class ControllerT implements ApplicationContextAware { @Resource
private ServiceChild child; @RequestMapping("/hello")
@ResponseBody
public String containter() {
child.getParent();
System.out.println("parentContainer");
return "containter";
} @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("子容器" + applicationContext);
System.out.println("子容器中获取父容器bean" + applicationContext.getBean(ServiceChild.class));
}
} package com.vip.qc.mvc.service; import com.vip.qc.mvc.controller.ControllerT;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service; /**
* @author QuCheng on 2020/6/28.
*/
@Service
public class ServiceChild implements ApplicationContextAware { // @Resource
private ControllerT controllerT; public void getParent() { System.out.println(controllerT);
} @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("父容器" + applicationContext);
try {
System.out.println("父容器中获取子容器bean" + applicationContext.getBean(ControllerT.class));
} catch (NoSuchBeanDefinitionException e) {
System.out.println("找不到子容器的bean");
}
}
} // 调用SpringWebStart的main方法启动-会有如下打印
父容器Root WebApplicationContext, started on Sun Jun 28 22:03:52 CST 2020
找不到子容器的bean
子容器WebApplicationContext for namespace 'dispatcher-servlet', started on Sun Jun 28 22:03:58 CST 2020, parent: Root WebApplicationContext
子容器中获取父容器beancom.vip.qc.mvc.service.ServiceChild@4acfc43a- Demo比较简单,不过也能反映父子容器的关系
菜瓜:嗯,效果看到了,能不能讲一下启动过程
水稻:稍等,我去下载源码。上面代码演示中已经提前说明了,父子容器的加载是Tomcat依据Servlet规范发起调用完成的
- spring-web源码包的/META-INF中能找到SPI的实际加载类SpringServletContainerInitializer#onStartup()方法会搜集实现WebApplicationInitializer接口的类,并调用其onStartup方法
- 上面MyWebApplicationInitializer启动类是WebApplicationInitializer的子类,未实现onStartup,实际调用的是其抽象父类AbstractDispatcherServletInitializer的方法。跟进去
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//① 创建Spring父容器上下文-对象放入ContextLoadListener,后续调起完成初始化,
super.onStartup(servletContext);
//② 创建DispatcherServlet对象,后续会由tomcat调用其init方法,完成子容器的初始化工作
registerDispatcherServlet(servletContext);
} // ①进来
protected void registerContextLoaderListener(ServletContext servletContext) {
// 此处会回调我们启动类的getRootConfigClasses()方法 - 父容器配置
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
istener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
} // ②进来
protected void registerDispatcherServlet(ServletContext servletContext) {
。。。
// 此处会回调我们启动类的getServletConfigClasses()方法 - 子容器配置
WebApplicationContext servletAppContext = createServletApplicationContext();
。。。
// 初始化的dispatcherServlet,会加入Tomcat容器中-后续调用
// FrameworkServlet#initServletBean()会完成上下文初始化工作
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
。。。
}
菜瓜:这样容器就可以用了吗?
水稻:是的,这样就可以直接在浏览器上面访问http://localhost:8081/hello,不过这是一个最简陋的web项目
菜瓜:懂了,最简陋是什么意思
水稻:如果我们想加一些常见的Web功能,譬如说拦截器,过滤器啥的。可以通过@EnableWebMvc注解自定义一些功能
package com.vip.qc.mvc; import com.vip.qc.mvc.interceptor.MyInterceptor1;
import com.vip.qc.mvc.interceptor.MyInterceptor2;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.annotation.Resource; /**
* @author QuCheng on 2020/6/28.
*/
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer { @Resource
private MyInterceptor1 interceptor1;
@Resource
private MyInterceptor2 interceptor2; @Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor1).addPathPatterns("/interceptor/**");
registry.addInterceptor(interceptor2).addPathPatterns("/interceptor/**");
}
} package com.vip.qc.mvc.interceptor; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; /**
* @author QuCheng on 2020/6/28.
*/
@Configuration
public class MyInterceptor1 implements HandlerInterceptor { @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("嘻嘻 我是拦截器1 pre");
return true;
} @Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("嘻嘻 我是拦截器1 post");
} @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("嘻嘻 我是拦截器1 after");
}
} package com.vip.qc.mvc.interceptor; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; /**
* @author QuCheng on 2020/6/28.
*/
@Configuration
public class MyInterceptor2 implements HandlerInterceptor { @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("嘻嘻 我是拦截器2 pre");
return true;
} @Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("嘻嘻 我是拦截器2 post");
} @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("嘻嘻 我是拦截器2 after");
} }
菜瓜:我知道,这里还有个Mvc请求调用流程和这个拦截器有关。而且这个拦截器不是MethodInterceptor(切面)
水稻:没错,说到这里顺便复习一下Mvc的请求过程
- 请求最开始都是通过Tomcat容器转发过来的,调用链:HttpServlet#service() -> FrameworkServlet#processRequest() -> DispatcherServlet#doDispather()
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
。。。
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 1.返回一个持有methodHandler(按照URL匹配得出的被调用bean对象以及目标方法)调用链(拦截器链)对象
mappedHandler = getHandler(processedRequest);
。。。
// 2.按照我们现在写代码的方式,只会用到HandlerMethod,其他三种基本不会用
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
。。。
// 3.前置过滤器 - 顺序调用
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 4.Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
。。。
applyDefaultViewName(processedRequest, mv);
// 5.后置过滤器 - 逆序调用
mappedHandler.applyPostHandle(processedRequest, response, mv);
。。。
// 6.处理试图 - 内部render
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// 异常处理
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
// 异常处理
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
。。。
菜瓜:这个之前看过不少,百度一大堆,不过还是源码亲切
总结:
- 目前基本互联网项目都是SpringBoot起手了,再难遇到SpringMvc的项目,不过熟悉该流程有利于我们更加深刻的理解Ioc容器
- Mvc拦截器链也是日常开发中会用到的功能,顺便熟悉一下请求的执行过程
【Spring】内嵌Tomcat&去Xml&调试Mvc的更多相关文章
- 学习Tomcat(七)之Spring内嵌Tomcat
前面的文章中,我们介绍了Tomcat容器的关键组件和类加载器,但是现在的J2EE开发中更多的是使用SpringBoot内嵌的Tomcat容器,而不是单独安装Tomcat应用.那么Spring是怎么和T ...
- 精尽Spring Boot源码分析 - 内嵌Tomcat容器的实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- Spring Boot移除内嵌Tomcat,使用非web方式启动
前言:当我们使用Spring Boot编写了一个批处理应用程序,该程序只是用于后台跑批数据,此时不需要内嵌的tomcat,简化启动方式使用非web方式启动项目,步骤如下: 1.在pom.xml文件中去 ...
- Spring Boot 内嵌Tomcat的端口号的修改
操作非常的简单,不过如果从来没有操作过,也是需要查找一下资料的,所以,在此我简单的记录一下自己的操作步骤以备后用! 1:我的Eclipse版本,不同的开发工具可能有所差异,不过大同小异 2:如何进入对 ...
- Spring Boot内嵌Tomcat session超时问题
最近让Spring Boot内嵌Tomcat的session超时问题给坑了一把. 在应用中需要设置session超时时间,然后就习惯的在application.properties配置文件中设置如下, ...
- spring boot 2 内嵌Tomcat Stopping service [Tomcat]
我在使用springboot时,当代码有问题时,发现控制台打印下面信息: Connected to the target VM, address: '127.0.0.1:42091', transpo ...
- 如何优雅的关闭基于Spring Boot 内嵌 Tomcat 的 Web 应用
背景 最近在搞云化项目的启动脚本,觉得以往kill方式关闭服务项目太粗暴了,这种kill关闭应用的方式会让当前应用将所有处理中的请求丢弃,响应失败.这种形式的响应失败在处理重要业务逻辑中是要极力避免的 ...
- 查看和指定SpringBoot内嵌Tomcat的版本
查看当前使用的Tomcat版本号 Maven Repository中查看 比如我们需要查Spring Boot 2.1.4-RELEASE的内嵌Tomcat版本, 可以打开链接: https://mv ...
- 基于内嵌Tomcat的应用开发
为什么使用内嵌Tomcat开发? 开发人员无需搭建Tomcat的环境就可以使用内嵌式Tomcat进行开发,减少搭建J2EE容器环境的时间和开发时容器频繁启动所花时间,提高开发的效率. 怎么搭建内嵌To ...
随机推荐
- Angular 从入坑到挖坑 - 路由守卫连连看
一.Overview Angular 入坑记录的笔记第六篇,介绍 Angular 路由模块中关于路由守卫的相关知识点,了解常用到的路由守卫接口,知道如何通过实现路由守卫接口来实现特定的功能需求,以及实 ...
- ionic3跳转页面的方法
ionic3很好很强大,有人喷有人赞.不想参与其中,个人认为如果能很好的满足需求,好坏都是无所谓的,最合适的才是最好的.总结下最近使用ionic3的一些知识点,方便以后查询.多句嘴:会ionic3和只 ...
- (Java实现) 洛谷 P1106 删数问题
题目描述 键盘输入一个高精度的正整数NN(不超过250250位) ,去掉其中任意kk个数字后剩下的数字按原左右次序将组成一个新的正整数.编程对给定的NN和kk,寻找一种方案使得剩下的数字组成的新数最小 ...
- Java 第十一届 蓝桥杯 省模拟赛 无向连通图最少包含多少条边
无向连通图最少包含多少条边 题目 问题描述 一个包含有2019个结点的无向连通图,最少包含多少条边? 答案提交 这是一道结果填空的题,你只需要算出结果后提交即可.本题的结果为一个整数,在提交答案时只填 ...
- Java实现 LeetCode 500 键盘行
500. 键盘行 给定一个单词列表,只返回可以使用在键盘同一行的字母打印出来的单词.键盘如下图所示. 示例: 输入: ["Hello", "Alaska", & ...
- Java实现 蓝桥杯VIP 算法训练 递归求二进制表示位数
问题描述 给定一个十进制整数,返回其对应的二进制数的位数.例如,输入十进制数9,其对应的二进制数是1001,因此位数是4. 样例输入 一个满足题目要求的输入范例. 9 样例输出 与上面的样例输入对应的 ...
- Java实现 LeetCode 1013 将数组分成和相等的三个部分
1013. 将数组分成和相等的三个部分 给你一个整数数组 A,只有可以将其划分为三个和相等的非空部分时才返回 true,否则返回 false. 形式上,如果可以找出索引 i+1 < j 且满足 ...
- java实现第六届蓝桥杯垒骰子
垒骰子 题目描述 赌圣atm晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体. 经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥! 我们先来规范一下骰 ...
- linux 删除文件后 df 查看磁盘空间并没有释放
1.错误现象 Linux 磁盘空间总是报警,查到到大文件,删除之后,df看到磁盘空间并没有释放. 用du -sh ./* | sort -nr (查看当前目录下文件的大小)通过查找了下发现文件被mys ...
- svn版本库的使用
简单案例: 1.不管在哪个盘下(比如:C盘.D盘等),都先创建文件夹(这里我新建的文件夹是“项目”),创建好了就点击去,如下: 2.在自己新建的文件夹中再新建一个新的文件夹,如下: 3.选中你的文件夹 ...