外置的tomcat:
tomcat启动加载web.xml,通过web.xml配置初始化spring容器,并加载dispatcherservlet
内嵌tomcat:
springboot启动,初始化spring容器加载bean,然后启动内嵌tomcat,通过servlet3.1规范的 ServletContainerInitializer加载dispatcherservlet
我会一边贴出代码,一边解释下部分代码
git地址:https://gitee.com/chuanxin1123/yboot
项目结构:
首先是maven依赖:
org.springframework
spring-webmvc
5.2.7.RELEASE
org.apache.tomcat.embed
tomcat-embed-jasper
8.5.23
那么我们在这里分析一下springboot启动流程:
1.初始化ioc
为什么要先初始化ioc,而不是先启动tomcat?因为ioc是必须的,而tomcat可以被其他中间件替换
2.启动tomcat
3.将dispatcherServlet放入到ioc容器,并加载到tomcat上下文中去
那么我们先实现第一步:初始化ioc
@ComponentScan("com.chuan")
public class StartApplication {
public static void main(String[] args) throws Exception{
//1.初始化ioc,
// 为什么要先初始化ioc,而不是先启动tomcat,因为ioc是必须的,而tomcat可以被其他中间件替换
AnnotationConfigWebApplicationContext ac=new AnnotationConfigWebApplicationContext();
ac.register(StartApplication.class); //手动去注册一个bean
ac.refresh(); //刷新
}
}
然后将dispatcherServlet注入到ioc中,这里为什么不能写在主方法中?因为写在主方法中就是强引用了,必须要使用servlet方式处理并且必须是web项目了。
@Configuration
public class MyConfig {
@Bean
public DispatcherServlet getDispatcherServlet(){
return new DispatcherServlet();
}
}
实现第二步:启动tomcat,写一个接口和tomcat实现类
public interface WebServerFactory {
//使用接口增加了扩展性,不一定非要用tomcat
void createServer() throws Exception;
}
@Component
public class MyTomcat implements WebServerFactory{
public void createServer() throws Exception {
Tomcat tomcat=new Tomcat();
tomcat.setPort(8081);
tomcat.addWebapp("/","D://soft");
tomcat.start();
//因为 tomcat.start();是非阻塞型的,所以要阻塞一下,不能让服务停止。
tomcat.getServer().await();
}
}
然后修改主方法,调用tomcat启动
@ComponentScan("com.chuan")
public class StartApplication {
public static void main(String[] args) throws Exception{
//1.初始化ioc,
// 为什么要先初始化ioc,而不是先启动tomcat,因为ioc是必须的,而tomcat可以被其他中间件替换
AnnotationConfigWebApplicationContext ac=new AnnotationConfigWebApplicationContext();
ac.register(StartApplication.class); //手动去注册一个bean
ac.refresh(); //刷新
//2.启动tomcat
WebServerFactory factory = ac.getBean(WebServerFactory.class);
factory.createServer();
//3.将dispatcherServlet放入到ioc容器,并加载到tomcat上下文中去
//注意dispatcherServlet不能放在启动里,因为也可以被reactive响应式编程替换掉servlet,也可以不是web项目
}
}
但现在tomcat和dispatcherServlet并没有关联起来,需要进行第三步操作:将dispatcherServlet放入到ioc容器,并加载到tomcat上下文中去
创建一个servlet作为顶层servlet,实现了一个ApplicationContextAware 接口。
ApplicationContextAware接口中setApplicationContext方法让Spring容器传递自己生成的ApplicationContext给我们使用,然后通过ac获取到DispatcherServlet的实例。
@Component
public class MyServlet implements ApplicationContextAware {
private static ApplicationContext context;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context=applicationContext;
}
//从ac中获取实例
public static DispatcherServlet getDispatcherServlet(){
return context.getBean(DispatcherServlet.class);
}
}
然后通过子类将DispatcherServlet添加到tomcat上下文中去。
这里有两种写法:
1.实现WebApplicationInitializer接口(springboot封装了ServletContainerInitializer接口)
2.实现ServletContainerInitializer接口(spi3.0规范)
从servlet3.0开始,web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册servlet或者filtes等,servlet规范中通过ServletContainerInitializer实现此功能。每个框架要使
用ServletContainerInitializer就必须在对应的jar包的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内容指定具体的
ServletContainerInitializer实现类,那么,当web容器启动时就会运行这个初始化器做一些组件内的初始化工作。
实现了onStartup后,在运行中会自动执行该方法,也正是该方法替代了spring中的web.xml,实现了无配置。
写法1:
public class MyServletInit extends MyServlet implements WebApplicationInitializer {
public void onStartup(ServletContext servletContext) throws ServletException {
ServletRegistration.Dynamic dynamic =servletContext.addServlet("MyDispatcherServlet",super.getDispatcherServlet());
dynamic.setLoadOnStartup(1);
dynamic.addMapping("/");
}
}
写法2:
public class MyServletInit extends MyServlet implements ServletContainerInitializer {
@Override
public void onStartup(Set> set, ServletContext servletContext) throws ServletException {
ServletRegistration.Dynamic dynamic =servletContext.addServlet("MyDispatcherServlet",super.getDispatcherServlet());
dynamic.setLoadOnStartup(1);
dynamic.addMapping("/");
}
}
但需要多出一步,在resources下创建一个META-INF/services目录(不是一个叫META-INF.services的文件夹),在该目录下创建一个文件名为javax.servlet.ServletContainerInitializer的文件,里面内容为该实现类的全路径(我的spi3.0实现是简写了)
com.chuan.config.MyServletInit
最后写一个controller启动:
@RestController
public class MyController {
@RequestMapping("/test")
public String test(){
return "test";
}
}
结果:启动成功,并且DispatcherServlet功能也正常。
比如通过web.xml方式启动:
org.springframework.web.context.ContextLoaderListener
contextConfigLocation
/WEB-INF/applicationContext.xml
app
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:springmvc.xml
1
app
/*
其中的ContextLoaderListener可以被下面代码替代
AnnotationConfigWebApplicationContext ac=new AnnotationConfigWebApplicationContext();
ac.register(StartApplication.class); //手动去注册一个bean
ac.refresh();
而DispatcherServlet则可以被下面代码替代
ServletRegistration.Dynamic dynamic =servletContext.addServlet("MyDispatcherServlet",super.getDispatcherServlet());
dynamic.setLoadOnStartup(1);
dynamic.addMapping("/");