手写springboot内嵌tomcat启动

外置和内嵌tomcat区别

外置的tomcat:
tomcat启动加载web.xml,通过web.xml配置初始化spring容器,并加载dispatcherservlet
内嵌tomcat:
springboot启动,初始化spring容器加载bean,然后启动内嵌tomcat,通过servlet3.1规范的  ServletContainerInitializer加载dispatcherservlet

代码示例及流程说明

我会一边贴出代码,一边解释下部分代码

git地址:https://gitee.com/chuanxin1123/yboot

项目结构:

首先是maven依赖:

  <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <version>8.5.23</version>
        </dependency>

那么我们在这里分析一下springboot启动流程:

1.初始化ioc

为什么要先初始化ioc,而不是先启动tomcat?因为ioc是必须的,而tomcat可以被其他中间件替换

2.启动tomcat

3.将dispatcherServlet放入到ioc容器,并加载到tomcat上下文中去

扫描二维码关注公众号,回复: 11621600 查看本文章

那么我们先实现第一步:初始化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<Class<?>> 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

比如通过web.xml方式启动:

<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-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("/");

猜你喜欢

转载自blog.csdn.net/qq_39404258/article/details/108464255
今日推荐