SpringBoot是如何内置的Tomcat?
在SpringBoot之前,可能读者在接触Spring、SpringMVC等框架的web项目都会涉及到一个web.xml,这在笔者SpringMVC源码解析的文章中也是采用的这种方式,为了将我们的DispatcherServlet注册进来,我们会在web.xml里面添加一些东西,如下:
SpringMVC
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring-mvc.xml
1
true
SpringMVC
/
(注意:建议读者了解一些笔者SpringMVC源码章节的内容再看本章效果更佳)
当然用过SpringBoot的小伙伴们肯定都知道,我们并没有接触到什么web.xml。也没有搞一个Tomcat进去,而是直接就能使用SpringMVC的注解进行项目开发,那么SpringBoot到底做了什么事情呢?下面我们就来对SpringBoot如何内置tomcat以及整合SpringMVC的核心设计进行分享。
没有web.xml,我们能启动SpringMVC吗?
没有web.xml,我们能启动SpringMVC吗?答案是肯定的,首先我们看到Spring官网
上面的意思很简单就是说通常我们的DispatcherServlet作为一个servlet需要在tomcat的web.xml里面声明,但是现在有了更新的方式,通过Java代码进行注册,实现很简单只需要实现WebApplicationInitializer这个接口,下面我们在验证一下这个问题,我们用一个就简单地web项目进行实现
可以看到tomcat在启动的时候进入了这个方法,说明这个方法只要我们实现就能在tomcat启动的时候做一些事情,而这里的参数servletContext我们可以看看他的API,
可以看到它能够添加我们的Servlet,那这就很简单了,我们可以通过实现这个tomcat提供的外置接口来做拓展将我们的SpringMVC封装进去,这就解决了我们的问题“”没有web.xml,我们依旧能启动SpringMVC。
Tomcat如何内置?
在解释如何内置Tomcat的问题中,我们先解释这个问题,在SpringBoot之前我们应该会接触过用Maven插件安装Tomcat,然后启动整个项目,如下
我们点开这个插件可以看到
它内部依赖了很多tomcat的包,这里我们看到一个embed的包,为什么我们提到它,下面就来解释tomcat如何内置的问题。
首先将我们的Spring源码工程加入一些配置
然后我们在写一个test类
public static void main(String[] args) {
Tomcat tom = new Tomcat();
tom.setPort(8080);
// tomcat启动
tom.start();
//阻塞 ,等待前端连接 tom.getServer().await();
tom.getServer().await();
}catch (Exception e)
{
e.printStackTrace();
}
}
}
看到这个test类,读者可能有些震惊,,原来tomcat也可以new出来,没错tomcat为什么称为容器这也不是没有道理,就好比我们称Spring容器无非就是Spring内部装了很多Bean对象,而tomcat只是装了很多servlet而已,它依旧能够被我们直接调用,当然目前我们启动的tomcat只是一个空壳容器,下面我们就来模拟SpringBoot如何内置tomcat且整合Spring的。
模拟SpringBoot内置tomcat且整合Spring
首先写一个test
public class Test {
public static void main(String[] args) {
Tomcat tom = new Tomcat();
tom.setPort(8080);
try{
File file=new File(System.getProperty("java.io.tmpdir"));
//获取项目编译后的claess 路径
String path = Test.class.getResource("/").getPath();
//获取webapp 文件
String filePath = new File("src/main/webapp").getAbsolutePath();
//然后将webapp下的项目添加至tomcat的context容器(context对应一个运行的项目)
Context context =tom.addWebapp("/",filePath); //参数1:一般是项目名 对应请求url中的项目名
//webResourceRoot 用于加载 项目的class文件
WebResourceRoot webResource = new StandardRoot(context);
webResource.addPreResources(new DirResourceSet(webResource,"/WEB-INF/classes",path,"/"));
// tomcat启动
tom.start();
//阻塞 ,等待前端连接 tom.getServer().await();
tom.getServer().await();
}catch (Exception e)
{
e.printStackTrace();
}
}
}
然后再写一个配置类
@Configuration
@ComponentScan("SpringMVC.**")
/*@EnableWebMvc*/
public class AppConfig {
}
注意ComponentScan的路径,可能和我的不一样,最后在写一个WebApplicationInitializer
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletCxt) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletCxt.addServlet("SpringMVC", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/*");
System.out.println("WebApplicationInitializer");
}
}
好了准备工作完毕我们写一个测试类
@Controller
public class UserController {
@RequestMapping("/home")
public String home(){
System.out.println("-------------");
return "home";
}
}
运行我们的tomcat,运行完毕后我们看到了我们的回调函数已被执行
并且已经初始化了我们的SpringMVC
我们在测试一下我们的UserController
可以看到我们内置成功,而SpringBoot是这样做的吗?这里笔者可以说明SpringBoot不是这样做的,我们先看到Test类的 Context context =tom.addWebapp("/",filePath); 这段代码,我们可以看到我们执行了addwebapp,这句代码是什么意思?就是说我们将这个项目当成为了web项目,那么什么是web项目?个人认为web项目和普通项目的区别就是有一个包路径叫webapp,因此如果我们执行的addwebapp,那么tomcat就会认为我们的项目是web项目,于是它就会去做很多很多的事情包括我们的WebApplicationInitializer ,我们测试一下如果我们去掉addwebapp
public class Test {
public static void main(String[] args) {
Tomcat tom = new Tomcat();
tom.setPort(8080);
try{
File file=new File(System.getProperty("java.io.tmpdir"));
//获取项目编译后的claess 路径
String path = Test.class.getResource("/").getPath();
//获取webapp 文件
String filePath = new File("src/main/webapp").getAbsolutePath();
//然后将webapp下的项目添加至tomcat的context容器(context对应一个运行的项目)
//Context context =tom.addWebapp("/",filePath); //参数1:一般是项目名 对应请求url中的项目名
Context context =tom.addContext("/",filePath);
//webResourceRoot 用于加载 项目的class文件
WebResourceRoot webResource = new StandardRoot(context);
webResource.addPreResources(new DirResourceSet(webResource,"/WEB-INF/classes",path,"/"));
// tomcat启动
tom.start();
//阻塞 ,等待前端连接 tom.getServer().await();
tom.getServer().await();
}catch (Exception e)
{
e.printStackTrace();
}
}
}
让我们将项目改成 Context context =tom.addContext("/",filePath);
我们的日志就不会打印WebApplicationInitializer,并且也不会显示找不到web.xml这条日志,也就是说SpringBoot的目的就是想要完全脱离传统的web工程的模式,这也是为什么SpringBoot适合用来做前后端分离的原因。好了回到我们的主题,那么既然不能回调我们的WebApplicationInitializer方法,那么SpringBoot又如何去启动我们的Spring呢?这也很简单,我们直接将Spring放入我们的Main函数里如下:
public class Test {
public static void main(String[] args) {
Tomcat tom = new Tomcat();
tom.setPort(8080);
try{
File file=new File(System.getProperty("java.io.tmpdir"));
//获取项目编译后的claess 路径
String path = Test.class.getResource("/").getPath();
//获取webapp 文件
String filePath = new File("src/main/webapp").getAbsolutePath();
//然后将webapp下的项目添加至tomcat的context容器(context对应一个运行的项目)
//Context context =tom.addWebapp("/",filePath); //参数1:一般是项目名 对应请求url中的项目名
Context context =tom.addContext("/",filePath);
//webResourceRoot 用于加载 项目的class文件
WebResourceRoot webResource = new StandardRoot(context);
webResource.addPreResources(new DirResourceSet(webResource,"/WEB-INF/classes",path,"/"));
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
tom.addServlet(context,"SpringMVC",servlet);
context.addServletMapping("/*","SpringMVC");
// tomcat启动
tom.start();
//阻塞 ,等待前端连接 tom.getServer().await();
tom.getServer().await();
}catch (Exception e)
{
e.printStackTrace();
}
}
}
然后测试同样会有这样的效果