前言

Spring官网的MVC模块介绍:

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框架中。正式名 称“ Spring Web MVC”来自其源模块的名称(spring-webmvc),但它通常被称为“ Spring MVC”。

早期Spring Boot未流行的时候,大家都在使用Spring MVC,并且,通常都使用的是XML配置的方式来构建Spring MVC项目。往往需要配置web.xml(配置ContextLoaderListener和DispatcherServlet)、spring-context.xml(父容器)、spring-mvc.xml(子容器)。上下文层次结构图如下:

java netty 接收xml springmvc接收xml_java

实现原理

随着Servlet3的出现,提供了共享库/运行时插件的功能。其中,新增了ServletContainerInitializer这个接口。通过SPI(Service Provider Interface)的机制,Servlet容器在启动应用的时候,会扫描当前应用每一个jar包里面的META-INF/services/javax.servlet.ServletContainerInitializer文件中指定的实现类,然后,再运行该实现类中的onStartup()方法。

java netty 接收xml springmvc接收xml_spring_02

package javax.servlet;

import java.util.Set;

public interface ServletContainerInitializer {
    // Set<Class<?>> set获取到的class、servletContext servlet上下文
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException;
}

Servlet3中还提供了注解@HandlesTypes,这个注解可以用在实现了ServletContainerInitializer接口的类上,在该注解里面我们可以写上一个类型数组,也就是说可以指定各种类型。Servlet容器在启动应用的时候,会将@HandlesTypes注解里面指定的类型下面的子类,包括实现类或者子接口等找到,在调用ServletContainerInitializer接口实现类的onStartup方法时,会传入给第一个参数Set<Class<?>> set。

package javax.servlet.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlesTypes {
    Class<?>[] value();
}

而Spring MVC正是通过这个来实现了零XML配置的。在spring-web项目中存在文件META-INF/services/javax.servlet.ServletContainerInitializer ,其中就有ServletContainerInitializer接口的实现类org.springframework.web.SpringServletContainerInitializer,并且类上使用了注解@HandlesTypes(WebApplicationInitializer.class)。所以在Servlet容器启动时候,就会自动的找到@HandlesTypes注解上标注类型的子类,包括子类或者子接口,并在调用SpringServletContainerInitializer.onStartUp() 方法时传入。

java netty 接收xml springmvc接收xml_servlet_03

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {

        List<WebApplicationInitializer> initializers = Collections.emptyList();

        if (webAppInitializerClasses != null) {
            initializers = new ArrayList<>(webAppInitializerClasses.size());
            for (Class<?> waiClass : webAppInitializerClasses) {
                // 接口和抽象类servlet容器也会给我们,但是我们不要
                // 排除接口和容器
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                        WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        // 实例化,然后添加到集合中
                        initializers.add((WebApplicationInitializer)
                                ReflectionUtils.accessibleConstructor(waiClass).newInstance());
                    }
                    catch (Throwable ex) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
            return;
        }

        servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
        AnnotationAwareOrderComparator.sort(initializers);
        // 调用initializer.onStartup  进行扩展
        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);
        }
    }
}

Spring MVC提供了WebApplicationInitializer接口的三个实现类,如下图所示:

java netty 接收xml springmvc接收xml_java_04

说明:AbstractDispatcherServletInitializer的onStartup方法会先调用super.onStartup方法,

AbstractContextLoaderInitializer的onStartup方法会调用registerContextLoaderListener方法注册ContextLoaderListener,并调用getRootConfigClasses()方法加载父容器的配置信息,创建父容器

然后调用registerDispatcherServlet方法注册DispatcherServlet,并调用getServletConfigClasses()方法加载子容器的配置信息,创建子容器。

public void onStartup(ServletContext servletContext) throws ServletException {
        //registerContextLoaderListener  ok
        super.onStartup(servletContext);
        // registerDispatcherServlet
        registerDispatcherServlet(servletContext);
    }

而最下面的子类AbstractAnnotationConfigDispatcherServletInitializer抽象类具有更强大的功能,所以我们只需要继承它就可以了。

简单应用

java netty 接收xml springmvc接收xml_mvc_05

NieWebAppInitializer.java

/**
 * tomcat作为servlet容器,基于spi机制,提供了javax.servlet.ServletContainerInitializer接口,
 * spring mvc实现类SpringServletContainerInitializer,tomcat启动时会自动调用该类的onStartup方法
 *
 * 实现类SpringServletContainerInitializer上@HandlesTypes({WebApplicationInitializer.class})注解,
 * tomcat会主动找到实现了这个@HandlesTypes注解上接口WebApplicationInitializer的实现类,
 * 并在调用SpringServletContainerInitializer.onStartup方法时传入
 * @author nie
 * @date 2022-07-22 17:08
 * @desc
 */
public class NieWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        // 父容器的配置,相当于spring-context.xml的配置
        return new Class[] {RootConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        // 子容器的配置,相当于spring-mvc.xml的配置
        return new Class[] {WebAppConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        // 相当于web.xml中DispatcherServlet配置的<url-pattern>/</url-pattern>
        return new String[] {"/"};
    }
}

RootConfig.java

/**
* 父容器的配置,相当于spring-context.xml的配置
*/
@Configuration
@ComponentScan(basePackages = "com.nie.dev",excludeFilters = {
//      @ComponentScan.Filter(type = FilterType.ANNOTATION,value={Controller.class}),
        @ComponentScan.Filter(type = ASSIGNABLE_TYPE,value =WebAppConfig.class ),
})
public class RootConfig {

}

WebAppConfig.java

/**
* 子容器的配置,相当于spring-mvc.xml的配置
*
*/
@Configuration
@ComponentScan(basePackages = {"com.nie.dev"},includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {RestController.class, Controller.class})
},useDefaultFilters =false)
@EnableWebMvc   // = <mvc:annotation-driven/>   会加入HandlerMapping、HandlerAdapter等组件
public class WebAppConfig implements WebMvcConfigurer{
    /**
    * 配置试图解析器
    */
    @Bean
    public InternalResourceViewResolver internalResourceViewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setSuffix(".jsp");
        viewResolver.setPrefix("/WEB-INF/jsp/");
        return viewResolver;
    }

}

HelloController.java

@Controller
public class HelloController {

    @RequestMapping("/hello")
    public String hello(Model model){
        model.addAttribute("msg","Hello NO XML SpringMVC");
        return "hello";
    }

    @RequestMapping("/test")
    @ResponseBody
    public String test() {
        return "Hello World@@@@@@";
    }

}

Hello.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
${msg}
</body>
</html>