Spring WebMVC零配置启动原理

首先我们看看Spring WebMVC官网介绍的第一句话

Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, “Spring Web MVC,” comes from the name of its source module (spring-webmvc), but it is more commonly known as “Spring MVC”.

Spring Web MVC是在Servlet API上构建的原始Web框架,从一开始就包含在Spring框架中。第一句很重要。

 第二点 DispatcherServlet 最终继承的父类 是 HttpServlet 所以说上面一句话很重要。

 官方文档介绍的DispatcherServlet 就提到了如何快速的配置一个web应用

 比如 web.xml 和 springmvc-servlet.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/app-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <!-- 扫描springmvc-servlet.xml -->
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- 拦截形式 -->
    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>

</web-app>

 同时官网也给出了这样一段使用Java config技术实现的可以看到和上面的配置基本一样,那么我们是不是就可以去掉web.xml了呢

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("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}

答案可以的。那问题是为什么实现了WebApplicationInitializer这个接口就可以了。为什么启动容器就会执行onStartup方法呢。

因为servlet 3.0之后的一个规范,像一般的容器如tomcat,jetty等等都去实现了这个规范,所以启动容器的时候会执行这个方法。而我们的spring mvc中有一个类也去实现了这个规范,所以验证了第一句话 Spring Web MVC是在Servlet API上构建的原始Web框架。我们来看看这个类 和 WebApplicationInitializer 这个接口有什么联系。

// 这个接口 ServletContainerInitializer 是servlet 的接口
// 而注解里面的 WebApplicationInitializer.class 就是我们刚刚实现的接口,在这个方法里面会执行所有实现了
// WebApplicationInitializer 这个接口重写的 onStartup 方法。在这段代码的最后就能看看循环执行
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    public SpringServletContainerInitializer() {
    }

    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
        List<WebApplicationInitializer> initializers = new LinkedList();
        Iterator var4;
        if (webAppInitializerClasses != null) {
            var4 = webAppInitializerClasses.iterator();

            while(var4.hasNext()) {
                Class<?> waiClass = (Class)var4.next();
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
                    } catch (Throwable var7) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
        } else {
            servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
            AnnotationAwareOrderComparator.sort(initializers);
            var4 = initializers.iterator();

            while(var4.hasNext()) {
                WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
                initializer.onStartup(servletContext);
            }

        }
    }
}

那另外一个问题就是我们web.xml中指定扫描的文件springmvc-servlet.xml不就扫描不到了吗?那该怎么办呢。

我们回到上面可以看到这样一句代码 ac.register(AppConfig.class);相信看过spring的同志一眼就看出来了,Java config技术实现的springmvc-servlet.xml这个文件的扫描工作,但是我们的web.xml和springmvc-servlet.xml在实际项目中会配置更多的便签比如视图解析器啊,消息转换器,监听器和拦截器。我们现在来看看这个AppConfig里面可以做些什么。

官方 MVC Config 配置文档 添加这个注解 和实现这个接口里面的方法(包含了几乎所有的配置可以使用Javaconfig技术实现的)

比如我们视图转跳,和消息转换器重写该接口中的方法即可完成配置

@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
    registry.jsp("/page/",".html");
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(new FastJsonHttpMessageConverter());
}

当然还需要申明配置类和扫描目录

@Configuration
@ComponentScan("com")
@EnableWebMvc

通过这里我们就去掉了web.xml 和 springmvc-servlet.xml 然后就可以通过内嵌的tomcat起启动容器了。下面是我的环境

去掉web.xml

public class MyWebApplicationInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
        ac.register(AppConfig.class);
        DispatcherServlet servlet = new DispatcherServlet(ac);
        ServletRegistration.Dynamic registration = servletContext.addServlet("springmvc", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("*.do");
    }
}

去掉springmvc-servlet.xml

@Configuration
@ComponentScan("com")
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {}

那内嵌tomcat如何来实现呢。我们知道springboot就是通过main方法来启动tomcat容器的。我们也来一个

添加依赖(springboot的内嵌tomcat不是使用添加依赖的方式,而是实例化了并且交给spring容器去管理了我们下次介绍springboot内嵌tomcat的原理2019-12-10号之前完成)

依赖如下

<dependency>
  <groupId>org.apache.tomcat.embed</groupId>
  <artifactId>tomcat-embed-core</artifactId>
  <version>8.5.31</version>
</dependency>
public class App {
    public static void main(String[] args) throws Exception{
        Tomcat tomcat = new Tomcat();
        tomcat.setPort(80);
        Context context = tomcat.addContext("/", System.getProperty("java.io.tmpdir"));
        context.addLifecycleListener((LifecycleListener) Class.forName(tomcat.getHost().getConfigClass()).newInstance());
        tomcat.start();
        tomcat.getServer().await();
    }
}

然后我们可以自己写一个测试的controller来测试一下。在main方法跑起来之后会看到这样的一个日志信息

 下一篇介绍 Spring WebMVC初始化Controller流程

猜你喜欢

转载自blog.csdn.net/qq_38108719/article/details/103443557