0
点赞
收藏
分享

微信扫一扫

SpringMVC无web.xml原理


从Servlet3.0开始就可以不需要web.xml了,而Spring MVC中也很好的支持了这一个特性。

简单使用

gradle配置

description = "Spring MVC Demo"

apply plugin: "groovy"
apply plugin: "kotlin"

dependencies {
    compile(project(":spring-webmvc"))
    compileOnly 'javax.servlet:javax.servlet-api:3.1.0'
    compile 'org.apache.tomcat.embed:tomcat-embed-core:8.5.23'
    compile 'org.apache.tomcat:tomcat-jasper:8.5.16'
    compile("org.slf4j:slf4j-api:1.7.30")
    compile("org.slf4j:slf4j-log4j12:1.7.30")
}

实现WebApplicationInitializer

package com.morris.spring.mvc.config;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;

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("/*");
    }
}

配置类

package com.morris.spring.mvc.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

// 只扫描@Controller注解
 only scan which class annotated by @Controller
@Configuration
@ComponentScan(value = "com.morris.spring.mvc", useDefaultFilters = false, includeFilters = {
		@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class)
})
public class WebConfig {
}

Controller类

package com.morris.spring.mvc.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("hello")
public class HelloController {

    @RequestMapping("index")
    @ResponseBody
    public String hello() {
        return "hello morris";
    }

}

启动类

package com.morris.spring.mvc;

import org.apache.catalina.LifecycleException;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.webresources.DirResourceSet;
import org.apache.catalina.webresources.StandardRoot;

import javax.servlet.ServletException;
import java.io.File;

public class Application {

    public static void main(String[] args) throws LifecycleException, ServletException {
        Tomcat tomcat = new Tomcat(); // 创建Tomcat容器
        tomcat.setPort(8080); // 端口号设置
        String basePath = System.getProperty("user.dir");
        tomcat.setBaseDir(basePath);

        //改变文件读取路径,从resources目录下去取文件
        StandardContext ctx = (StandardContext) tomcat.addWebapp("/", basePath + File.separator + "spring-mvc-demo/src" + File.separator + "main" + File.separator + "resources");
        // 禁止重新载入
        ctx.setReloadable(false);
        // class文件读取地址
        File additionWebInfClasses = new File(basePath, "spring-mvc-demo/build/classes/java/main");
        // 创建WebRoot
        WebResourceRoot resources = new StandardRoot(ctx);
        // tomcat内部读取Class执行
        resources.addPreResources(new DirResourceSet(resources, "/WEB-INF/classes", additionWebInfClasses.getAbsolutePath(), "/"));

        ctx.setResources(resources);
        tomcat.start();
        // 异步等待请求执行
        tomcat.getServer().await();
    }
}

浏览器输入http://localhost:8080/hello/index即可看到效果。

使用AbstractAnnotationConfigDispatcherServletInitializer

AbstractAnnotationConfigDispatcherServletInitializer会创建出两个容器出来:

  • ServletApplicationContext(子容器):负责管理跟Web相关的Bean,如Controller
  • RootApplicationContext(父容器):负责管理跟Service、Dao层相关的Bean

MyWebAppInitializer

package com.morris.spring.mvc.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { RootConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { WebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

MyWebAppInitializer中需要配置/,而不是/*//*到底有啥区别?

  • /:拦截所有请求(包括静态资源(xx.js,xx.png)),但是不包括*.jsp
  • /*:拦截所有请求;连*.jsp页面都拦截;jsp页面是tomcat的jsp引擎解析的(所以使用这个jsp就无法访问了)

RootConfig

package com.morris.spring.mvc.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

// 扫描除@Controller注解的其他@Component注解
 scan which class annotated by @Component and exculde @Controller
@Configuration
@ComponentScan(basePackages = "com.morris.spring.mvc", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class)
})
public class RootConfig {
}

无xml的原理分析

从servlet3.0开始,web项目中可以不需要配置web.xml,WEB容器在启动时会扫描jar中所有的/META-INF/services/javax.servlet.ServletContainerInitializer,然后调用这些类的onStartup()方法。

SpringServletContainerInitializer

spring-web项目使用了这个配置,它在其中配置的类为org.springframework.web.SpringServletContainerInitializer:

// 加载WebApplicationInitializer所有的实现(包括子接口,抽象类,类)
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

    // 加载WebApplicationInitializer所有的实现(包括子接口,抽象类,类)作为参数传入webAppInitializerClasses
    @Override
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {

        List<WebApplicationInitializer> initializers = new LinkedList<>();

        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                // Be defensive: Some servlet containers provide us with invalid classes,
                // no matter what @HandlesTypes says...
                // 不是接口,不是抽象类,是WebApplicationInitializer的子类
                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);
        for (WebApplicationInitializer initializer : initializers) {
            // 调用每个WebApplicationInitializer的onStartup方法
            initializer.onStartup(servletContext);
        }
    }

}

从SpringServletContainerInitializer的onStartup()可以发现,springmvc启动时会查找所有实现了WebApplicationInitializer的类,并实例化,最后调用其onStartup()方法。

WebApplicationInitializer

public interface WebApplicationInitializer {
    void onStartup(ServletContext servletContext) throws ServletException;
}

WebApplicationInitializer只有一个方法onStartup()。

SpringMVC无web.xml原理_tomcat

下面再来看看WebApplicationInitializer的实现类:

  • AbstractContextLoaderInitializer
  • AbstractDispatcherServletInitializer
  • AbstractAnnotationConfigDispatcherServletInitializer

AbstractContextLoaderInitializer

从类名可以看出AbstractContextLoaderInitializer是一个ContextLoader初始化器,那么它是怎么初始化ContextLoader的呢?

public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {

    public void onStartup(ServletContext servletContext) throws ServletException {
        registerContextLoaderListener(servletContext);
    }

    protected void registerContextLoaderListener(ServletContext servletContext) {
        WebApplicationContext rootAppContext = createRootApplicationContext();
        if (rootAppContext != null) {
            // 注入ContextLoaderListener
            ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
            listener.setContextInitializers(getRootApplicationContextInitializers());
            servletContext.addListener(listener);
        }
        else {
            logger.debug("No ContextLoaderListener registered, as " +
                    "createRootApplicationContext() did not return an application context");
        }
    }
}

AbstractContextLoaderInitializer使用ServletContext向Web容器中注入了一个Listener(ContextLoaderListener),相当于以前在web.xml中这样配置:

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

AbstractDispatcherServletInitializer

从类名可以看出AbstractDispatcherServletInitializer是一个DispatcherServlet初始化器,那么它是怎么初始化DispatcherServletInitializer的呢?

// DispatcherServlet初始化器
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {

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

    protected void registerDispatcherServlet(ServletContext servletContext) {
        String servletName = getServletName();
        Assert.hasLength(servletName, "getServletName() must not return null or empty");

        WebApplicationContext servletAppContext = createServletApplicationContext();
        Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

        // 创建DispatcherServlet
        FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
        Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
        dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

        // 添加到ServletContext
        ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
        if (registration == null) {
            throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
                    "Check if there is another servlet registered under the same name.");
        }

        registration.setLoadOnStartup(1);
        registration.addMapping(getServletMappings());
        registration.setAsyncSupported(isAsyncSupported());

        // 获取所有的filter
        Filter[] filters = getServletFilters();
        if (!ObjectUtils.isEmpty(filters)) {
            for (Filter filter : filters) {
                                // 添加filter
                registerServletFilter(servletContext, filter);
            }
        }

        customizeRegistration(registration);
    }

    protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {
        String filterName = Conventions.getVariableName(filter);
        Dynamic registration = servletContext.addFilter(filterName, filter);

        if (registration == null) {
            int counter = 0;
            while (registration == null) {
                if (counter == 100) {
                    throw new IllegalStateException("Failed to register filter with name '" + filterName + "'. " +
                            "Check if there is another filter registered under the same name.");
                }
                registration = servletContext.addFilter(filterName + "#" + counter, filter);
                counter++;
            }
        }

        // 默认支持异步
        registration.setAsyncSupported(isAsyncSupported());
        registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());
        return registration;
    }

}

AbstractDispatcherServletInitializer使用ServletContext向Web容器中注入了一个Servlet(DispatcherServlet),如果子类实现了getServletFilters()并添加了filter,也会将这些filter注入到Web容器中。

相当于web.xml这样配置:

<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

AbstractAnnotationConfigDispatcherServletInitializer

AbstractAnnotationConfigDispatcherServletInitializer基于模板方法模式提供了两个钩子方法供子类通过注解实现父子容器。

// 基于注解配置的DispatcherServlet初始化器
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
        extends AbstractDispatcherServletInitializer {

    protected WebApplicationContext createRootApplicationContext() {
        Class<?>[] configClasses = getRootConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            // 根据配置类创建父容器
            AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
            context.register(configClasses);
            return context;
        }
        else {
            return null;
        }
    }

    protected WebApplicationContext createServletApplicationContext() {
        // 根据配置类创建子容器
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        Class<?>[] configClasses = getServletConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            context.register(configClasses);
        }
        return context;
    }

    protected abstract Class<?>[] getRootConfigClasses();

    protected abstract Class<?>[] getServletConfigClasses();

}

总结:只需要继承抽象类AbstractAnnotationConfigDispatcherServletInitializer实现其抽象方法就可以构建一个父子容器,也就是我们上面实现的MyWebAppInitializer类。


举报

相关推荐

0 条评论