12.SpringMVC与Spring是如何联系的


title: 12.SpringMVC与Spring是如何联系的
tag: 笔记 手写SSM Web

z实现了IOC、AOP和ORM之后我们开始实现Web开发支持。

分析

我们知道SpringMVC是一个Spring内置的基于Servlet的MVC框架。而Spring的核心就是它的IOC容器,那么SpringMVC作为一个web层框架是如何与Spring的IOC去连接起来的呢?

想要将它们连接起来应当需要解决下面的问题:

  • 如何在tomcat服务器启动Servlet容器时启动IOC容器。

在这之前我们首先需要复习一下JavaWeb的内容。

Java Web应用程序

我们首先回顾下Java Web应用程序到底有几方参与。

首先,Java Web应用一般遵循**Servlet标准**,这个标准定义了应用程序可以按接口编写哪些组件:ServletFilterListener,也规定了一个服务器(如Tomcat、Jetty、JBoss等)应该提供什么样的服务,按什么顺序加载应用程序的组件,最后才能跑起来处理来自用户的HTTP请求。

Servlet规范定义的组件有3类:

  1. Servlet:处理HTTP请求,然后输出响应;
  2. Filter:对HTTP请求进行过滤,可以有多个Filter形成过滤器链,实现权限检查、限流、缓存等逻辑;
  3. Listener:用来监听Web应用程序产生的事件,包括启动、停止、Session有修改等。

这些组件均由应用程序实现。

服务器为一个应用程序提供一个“容器”,即Servlet Container,一个Server可以同时跑多个Container,不同的Container可以按URL、域名等区分,Container才是用来管理Servlet、Filter、Listener这些组件的:

image-20240222000434538

所以我们要捋清楚这些组件的创建顺序,以及谁创建谁。

对于一个Web应用程序来说,启动时,应用程序本身只是一个war包,并没有main()方法,因此,启动时执行的是Server的main()方法。以Tomcat服务器为例:

  1. 启动服务器,即执行Tomcat的main()方法;
  2. Tomcat根据配置或自动检测到一个xyz.war包后,为这个xyz.war应用程序创建Servlet容器;
  3. Tomcat继续查找xyz.war定义的Servlet、Filter和Listener组件,按顺序实例化每个组件(Listener最先被实例化,然后是Filter,最后是Servlet);
  4. 用户发送HTTP请求,Tomcat收到请求后,转发给Servlet容器,容器根据应用程序定义的映射,把请求发送个若干Filter和一个Servlet处理;
  5. 处理期间产生的事件则由Servlet容器自动调用Listener。

其中,第3步实例化又有很多方式:

  1. 通过在web.xml配置文件中定义,这也是早期Servlet规范唯一的配置方式;
  2. 通过注解@WebServlet@WebFilter@WebListener定义,由Servlet容器自动扫描所有class后创建组件,这和我们用Annotation配置Bean,由IoC容器自动扫描创建Bean非常类似;
  3. 先配置一个Listener,由Servlet容器创建Listener,然后,Listener自己调用相关接口,手动创建ServletFilter

到底用哪种方式,取决于Web应用程序自己如何编写。对于使用Spring框架的Web应用程序来说,Servlet、Filter和Listener数量少,而且是固定的,应用程序自身编写的Controller数量不定,但由IoC容器管理,因此,采用方式3最合适。

web.xml和代码方式实现

而servlet容器的初始化我们可以用这两种方式来实现:

  • web.xml:tomcat启动时会自动读取web.xml中的配置信息后创建Servlet容器
  • ServletContainerInitialize:它通过SPI机制,当web容器启动时它会找到注解@HandlesTypes中标注类的实现类去执行类中的onStarup方法,免去了web.xml的编写。

我们这里主要介绍第二种通过代码的方式实现。

代码实现容器初始化

目前实际使用中已经很少去使用xml这样的方式进行配置了,我们通常都更加倾向于去使用Java编写配置类来实现。而ServletContainerInitializer 就是提供的一种用于取代web.xml的方式,通过这种方式我们可以向servlet容器中添加servlet,listener等容器初始化操作。

ServletContainerInitializer:

public interface ServletContainerInitializer {

    void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
}
  • 这个接口仅有一个方法onStartup,听方法名可以知道,当servlet启动时调用的就是实现类的onStartup方法来进行初始化。

下面我们来看Spring中实现这个接口的类SpringServletContainerInitializer:

@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    public SpringServletContainerInitializer() {
    }
 
    public void onStartup(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)waiClass.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);
            }
 
        }
    }
}

代码意思:

  • 判断webAppInitializerClasses这个Set集合是否为空。
    • 如果不为空的话,找到这个set中不是接口,不是抽象类,并且是WebApplicationInitializer接口实现类的类,将它们保存到list中。
  • list按一定规则排序后,遍历list调用其onStartup

从上面逻辑我们可以知道:实际调用的是接口WebApplicationInitializer的实现类的onStartup,而这个接口是由@HandlesTypes({WebApplicationInitializer.class})注解指定的。

因此我们再看接口WebApplicationInitializer

public interface WebApplicationInitializer {
    void onStartup(ServletContext servletContext) throws ServletException;
}
  • 该接口和ServletContainerInitializer相同也有一个onStartup但这里仅有一个servletContext上下文的参数。

总结:SpringMVC初始化servlet容器时实际上是调用WebApplicationInitializer实现类的onStartup来进行初始化的。

容器初始化过程

从SpringMVC的功能实现来说,初始化过程应该需要两个东西:

  • 启动IOC容器。
  • 创建一个Servlet来处理分发请求给Controller,SpringMVC称之为DispatcherServlet

在之前JavaWeb的介绍中我们知道,我们可以先创建一个Listener组件来完成上面两个东西的创建,我们来看SpringMVC的源码。

下面是SpringMVC提供的初始化类的继承层次图:

image-20240222234112007

这里使用了很多的模板方法设计模式,将每个继承层次的职责划分得很清楚:

AbstractContextLoaderInitializer

public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
    protected final Log logger = LogFactory.getLog(this.getClass());

    public AbstractContextLoaderInitializer() {
    }

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

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

    }

    @Nullable
    protected abstract WebApplicationContext createRootApplicationContext();

    @Nullable
    protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
        return null;
    }
}
  • 这个层次的职责是创建根IOC容器,也就是除Controller层的IOC容器。
  • 该类在onStartup调用了registerContextLoaderListener,并且又在其中调用了两个抽象方法
    • createRootApplicationContext:创建根IOC容器。
    • getRootApplicationContextInitializers:

AbstractDispatcherServletInitializer

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
    public static final String DEFAULT_SERVLET_NAME = "dispatcher";

    public AbstractDispatcherServletInitializer() {
    }

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

    protected void registerDispatcherServlet(ServletContext servletContext) {
        String servletName = this.getServletName();
        Assert.state(StringUtils.hasLength(servletName), "getServletName() must not return null or empty");
        WebApplicationContext servletAppContext = this.createServletApplicationContext();
        Assert.state(servletAppContext != null, "createServletApplicationContext() must not return null");
        FrameworkServlet dispatcherServlet = this.createDispatcherServlet(servletAppContext);
        Assert.state(dispatcherServlet != null, "createDispatcherServlet(WebApplicationContext) must not return null");
        dispatcherServlet.setContextInitializers(this.getServletApplicationContextInitializers());
        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.");
        } else {
            registration.setLoadOnStartup(1);
            registration.addMapping(this.getServletMappings());
            registration.setAsyncSupported(this.isAsyncSupported());
            Filter[] filters = this.getServletFilters();
            if (!ObjectUtils.isEmpty(filters)) {
                Filter[] var7 = filters;
                int var8 = filters.length;

                for(int var9 = 0; var9 < var8; ++var9) {
                    Filter filter = var7[var9];
                    this.registerServletFilter(servletContext, filter);
                }
            }

            this.customizeRegistration(registration);
        }
    }
    
    protected abstract WebApplicationContext createServletApplicationContext();
    
    protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
        return new DispatcherServlet(servletAppContext);
    }

    
   //....省略
  • 这个层次的职责是创建DispatcherServlet,并且DispatcherServlet也需要一个IOC容器,这个容器是管理Controller Bean的容器。

  • 这个类同样将创建IOC容器的逻辑定义为抽象方法给子类去实现。

AbstractAnnotationConfigDispatcherServletInitializer

现在上面的抽象类剩下了创建根IOC容器和webIOC容器的逻辑:

public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer {
    public AbstractAnnotationConfigDispatcherServletInitializer() {
    }

    @Nullable
    protected WebApplicationContext createRootApplicationContext() {
        Class<?>[] configClasses = this.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 = this.getServletConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            context.register(configClasses);
        }

        return context;
    }

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

    @Nullable
    protected abstract Class<?>[] getServletConfigClasses();
}
  • createRootApplicationContext:创建根IOC容器
  • createServletApplicationContext:创建Servlet的IOC容器

我们这里只看注解配置,因此我们用户需要继承该类并实现两个抽象方法,提供创建容器的JavaConfig类。

总结

SpringMVC通过web.xml的方式或者JavaConfig的方式创建一个Listener完成IOC容器的创建和DispatcherServlet的创建,它们分别由下面两个类完成:

  • ContextLoaderListener:一个监听器,会将传入的IOC容器存储到ServletContext中,实现根IOC容器的保存。
  • DispatcherServlet:用于分发请求给Controller

etApplicationContext`:创建Servlet的IOC容器

我们这里只看注解配置,因此我们用户需要继承该类并实现两个抽象方法,提供创建容器的JavaConfig类。

总结

SpringMVC通过web.xml的方式或者JavaConfig的方式创建一个Listener完成IOC容器的创建和DispatcherServlet的创建,它们分别由下面两个类完成:

  • ContextLoaderListener:一个监听器,会将传入的IOC容器存储到ServletContext中,实现根IOC容器的保存。
  • DispatcherServlet:用于分发请求给Controller
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值