Spring Boot 是如何整合 Spring MVC 的?

Java大联盟

共 7450字,需浏览 15分钟

 ·

2020-09-18 19:01

  Java大联盟

  帮助万千Java学习者持续成长

关注



作者|koala

blog.csdn.net/believer123/java/article/details/70196572


B 站搜索:楠哥教你学Java

获取更多优质视频教程


通过 Spring Boot 整合各个框架是越来越方便了,整合 Spring MVC 只需要添加对应的 starer 依赖即可。


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>


而且还配备了 Tomcat 的 starter。


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>


这样,只需要根据自身需求,设置配置文件。启动web服务器只需要运行 java application 就可以了,不再需要部署到 Tomcat 服务了。


之前一直很好奇,使用 Spring MVC 时需要在 web.xml 上配置DispatcherServlet。而整合了 Spring Boot 后为什么就不需要配置了,下面就进行完整的分析。


看着累?可以直接看步骤 7,核心分析。


1、寻找入口,找到 WebServlet 自动配置类:

EmbeddedServletContainerAutoConfiguration。


org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration{
    ...省略代码
}


Spring Boot 自动配置功能类都以 AutoConfiguration 结尾。


2、注入需要的 Bean


从类上的注解可以看出,导入了 BeanPostProcessorsRegistrar,来添加EmbeddedServletContainerCustomizerBeanPostProcessor。首先会查看工程是否有自定的。EmbeddedServletContainerCustomizerBeanPostProcessor,如果没有,则注入默认的。EmbeddedServletContainerCustomizerBeanPostProcessor。代码如下:


@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry) {
            if (this.beanFactory == null) {
                return;
            }
            if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(
    EmbeddedServletContainerCustomizerBeanPostProcessor.class, true,
                    false))) {
                registry.registerBeanDefinition( "embeddedServletContainerCustomizerBeanPostProcessor",
                        new RootBeanDefinition(
        EmbeddedServletContainerCustomizerBeanPostProcessor.class));
            }
            if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(
                    ErrorPageRegistrarBeanPostProcessor.class, truefalse))) {
                registry.registerBeanDefinition("errorPageRegistrarBeanPostProcessor",
                        new RootBeanDefinition(
                                ErrorPageRegistrarBeanPostProcessor.class));

            }
        }


实现 ImportBeanDefinitionRegistrar 接口,实现注入需要的 Bean 到Spring 容器中,Mybatis(MapperScannerRegistrar)也是通过此接口来完成 Mapper 类的定义。


3、步骤 2 注入了 bean:


EmbeddedServletContainerCustomizerBeanPostProcessor,该类实现了在 ConfigurableEmbeddedServletContainer 对象初始化前,进行行必要的参数配置。

1、获取所有EmbeddedServletContainerCustomizer对象

2、调用EmbeddedServletContainerCustomizer.customize方法

3、EmbeddedServletContainerCustomizer实现类根据自身需求设置WebServlet容器参数(如:端口号、连接数等等)

核心代码如下:


@Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)throws BeansException {
        if (bean instanceof ConfigurableEmbeddedServletContainer) { postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
        }
        return bean;
    }
private void postProcessBeforeInitialization(ConfigurableEmbeddedServletContainer bean) {
        for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
            customizer.customize(bean);
        }
    }


ConfigurableEmbeddedServletContainer:是 Web 容器的接口,默认注入的有。


    

BeanPostProcessor :是 Spring 容器的回调接口,在所有 Bean 初始化之前和之后分别回调此接口的 postProcessBeforeInitialization,postProcessAfterInitialization 方法。这样就可以根据需求在 Bean 初始化前后配置设置需要的功能。

通过步骤 1-3 完成了 Web 容器启动前的参数配置功能。


4、EmbeddedWebApplicationContext 入场 Spring 容器配置加载完成后,会回调 EmbeddedWebApplicationContext.refresh 方法。

EmbeddedWebApplicationContext 在执行 refresh 方法中,调用了onRefresh 方法进行 ServletContainer 配置。代码如下:


@Override
    public final void refresh() throws BeansException, IllegalStateException {
             ...省略
            super.refresh();
             ...省略
    }

    @Override
    protected void onRefresh() {
        ...省略
        //创建ServletContainer
        createEmbeddedServletContainer();
         ...省略
    }


EmbeddedWebApplicationContext 实现了接口ConfigurableApplicationContext, Spring 容器配置加载完成后会回调所有的 ConfigurableApplicationContext 对象的 refresh 方法。


(1)、在 onRefresh 方法中,获取 EmbeddedServletContainerFactory 对象,因为工程上使用Tomcat,所以这里就是TomcatEmbeddedServletContainerFactory


(2)、执行EmbeddedServletContainerFactory.getEmbeddedServletContainer方法


5、TomcatEmbeddedServletContainerFactory.getEmbeddedServletContainer 这里是 Tomcat 容器核心功能完的地方。主要完成了对 Tomcat 配置(不是这篇重点,省略代码),在 configureContext 方法添加 Tomcat 容器启动回调接口(重点)。


protected void configureContext(Context context,
            ServletContextInitializer[] initializers)
 
{
        TomcatStarter starter = new TomcatStarter(initializers);
        if (context instanceof TomcatEmbeddedContext) {
            // Should be true
            ((TomcatEmbeddedContext) context).setStarter(starter);
        }
        ...省略
    }


ServletContainerInitializer 是 Tomcat 容器启动的一个回调接口。

在 Tomcat 启动前,Spring Boot 通过 Tomcat Starte r完成 Servlet,Filter等 Web 组件的组注入。


6、TomcatStarter,在 Tomcat 启动后回调 onStartup。


7、EmbeddedWebApplicationContext 在 onStartup 回调中完成 Spring MVC 功能注入。


(1)、在 selfInitialize 方法中获取到所有 ServletContextInitializer 对象,并调用其 onStartup 方法


(2)、ServletContextInitializer 实现类如下:



(3)、通过上图就很清楚的说明了 Servlet,Filter 等 Web 组件实现类。


(4)、在 ServletRegistrationBean 向 ServletContainer 添加 Servlet。


@Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        Assert.notNull(this.servlet, "Servlet must not be null");
        String name = getServletName();
        if (!isEnabled()) {
            logger.info("Servlet " + name + " was not registered (disabled)");
            return;
        }
        logger.info("Mapping servlet: '" + name + "' to " + this.urlMappings);
        Dynamic added = servletContext.addServlet(name, this.servlet);
        if (added == null) {
            logger.info("Servlet " + name + " was not registered "
                    + "(possibly already registered?)");
            return;
        }
        configure(added);
    }


(5)、这里也就解释了 Spring Boot 官方文档 70.1 节上为什么是通过RegistrationBean 添加 Servlet 与 Filter 的原因了。



7.6、DispatcherServletAutoConfiguration.DispatcherServletRegistrationConfiguration 此处添加 Spring MVC 核心功能类 DispatcherServlet。


@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
        @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public ServletRegistrationBean dispatcherServletRegistration(
                DispatcherServlet dispatcherServlet)
 
{
            ServletRegistrationBean registration = new ServletRegistrationBean(
                    dispatcherServlet, this.serverProperties.getServletMapping());
            registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
            registration.setLoadOnStartup(
                    this.webMvcProperties.getServlet().getLoadOnStartup());
            if (this.multipartConfig != null) {
                registration.setMultipartConfig(this.multipartConfig);
            }
            return registration;
        }


以上只是将重要点抽出来说明,贴上全部源码也是无意义的。要理解其中过程还需要自行查看源码。


通过以上步骤分析了 Spring Boot 集成 Spring MVC 和 Tomcat 功能简要步骤。其实只要找到了入口,即可 Debug 一步一步的走下去,来查看内部实现。


总结


通过以上分析和 Mybatis 功能分析,发现满满的都是套路。在 Spring Boot 上实现自定义 Starter 功能应该都是如下套路:

1、在自定义的 XXAutoConfiguration 上 Import 一个ImportBeanDefinitionRegistrar 来注入指定 Bean。

2、添加自定义的 BeanPostProcessor 在 Bean 初始化之前或之后完成配置功能或初始化某些依赖功能。


推荐阅读

1、Spring Boot+Vue项目实战

2、B站:4小时上手MyBatis Plus

3、一文搞懂前后端分离

4、快速上手Spring Boot+Vue前后端分离


楠哥简介

资深 Java 工程师,微信号 southwindss

《Java零基础实战》一书作者

腾讯课程官方 Java 面试官今日头条认证大V

GitChat认证作者,B站认证UP主(楠哥教你学Java)

致力于帮助万千 Java 学习者持续成长。




有收获,就在看 
浏览 54
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报