Spring Web MVC配置方式汇总(三)

​1 Spring Web MVC

本文将介绍Spring Web MVC的几种配置方法。

2 SCI补充

上一篇文章中介绍了Tomcat是如何使用类似Java SPI机制加载启动配置的,它有个专用名词叫SCI。SCI是Servlet Container Initializer的缩写,也就是Servlet容器启动器的意思。在上一篇文章中,我们给出了ServletContainerInitializer的源代码。

package javax.servlet;
import java.util.Set;

/**
 * Interface which allows a library/runtime to be notified of a web
 * application's startup phase and perform any required programmatic
 * registration of servlets, filters, and listeners in response to it.
 */
public interface ServletContainerInitializer {
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException; 
}

方法onStartup(...)是容器启动时会被调起的方法,用于完成容器的初始化操作。但是开发人员在编码时该如何设计具体的方法实现呢?更具体点,方法的入参Set<Class<?>> c在运行时传入的是什么?

下面我们对这个问题进行简要的回答。

首先我们需要了解一个名为HandlesTypes的注解。

package javax.servlet.annotation;

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

/**
 * This annotation is used to declare the class types that a
 * {@link javax.servlet.ServletContainerInitializer
 * ServletContainerInitializer} can handle.
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlesTypes {
    /**
     * The classes in which a {@link javax.servlet.ServletContainerInitializer can handle.
     */
    Class<?>[] value();
}

这个注解是一个class类型的注解,放在class定义上。注解的value属性需要设置一个class对象。简单来讲,这个注解指定特定ServletContainerInitializer能处理的对象类型。这个类型也会是方法onStartup(...)入参Set<Class<?>> c中元素的类型。

给一个直观的例子解释下:

我们先定义一个名为MyHandlerType的接口。

package org.example.initial.handler;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
public interface MyHandlerType {
    void onStartup(ServletContext servletContext) throws ServletException;
}

再分别定义该接口的两个名为ServletStartInitialOneServletStartInitialTwo的具体实现。

package org.example.initial.handler.impl;
import org.example.initial.handler.MyHandlerType;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
public class ServletStartInitialOne implements MyHandlerType {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        System.out.println("Init the servlet context with ServletStartInitialOne");
    }
}

package org.example.initial.handler.impl;
import org.example.initial.handler.MyHandlerType;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
public class ServletStartInitialTwo implements MyHandlerType {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        System.out.println("Init the servlet context with ServletStartInitialTwo");
    }
}

现在我们来设计一个叫MyServletContainerInitializer类,它实现了Tomcat中定义的ServletContainerInitializer接口。

package org.example.initial;

import org.example.initial.handler.MyHandlerType;

import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
import java.util.Set;

@HandlesTypes(MyHandlerType.class)
public class MyServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> servletStartInitials, ServletContext ctx) throws ServletException {
        for (Class<?> clazz : servletStartInitials) {
            System.out.println(MyHandlerType.class.isAssignableFrom(clazz));
            // Do Something with clazz and ctx.
        }
    }
}

MyServletContainerInitializer上定义了@HandlesTypes(MyHandleType.class)注解,告诉Tomcat该类用来处理继承自MyHandlerType的所有实现类。

onStartup(Set<Class<?>> servletStartInitials, ServletContext ctx)方法中,Tomcat会加载MyHandlerType的所有子类并构造成一个集合,通过Set<Class<?>> servletStartInitials传入。在上面的例子中,Set<Class<?>> servletStartInitials中包含了两个元素,分别是org.example.initial.handler.impl.ServletStartInitialOneorg.example.initial.handler.impl.ServletStartInitialTwo

Tomcat会负责扫描classpath路径下的所有class文件,找到所有@HandlesTypes中定义的接口实现,保存并在初始化时传递给对应的ServletContainerInitializer。具体实现在ContextConfig类的相关方法中,感兴趣的可自行阅读下源代码。

3 Spring Web MVC的集成方式

有了上面的铺垫,下面便可以开始介绍Spring Web MVC是如何利用Tomcat的SCI机制完成Web应用初始化的。

首先在spring-web的jar包中,存在一个/META-INF/services/javax.servlet.ServletContainerInitializer的文件,该文件的内容为

org.springframework.web.SpringServletContainerInitializer

根据前面的介绍,Tomcat启动时会在classpath下查找名为org.springframework.web.SpringServletContainerInitializer的java类,并在容器启动时调用它的相关方法。

package org.springframework.web;

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
 @Override
 public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
  List<WebApplicationInitializer> initializers = new LinkedList<>();
  if (webAppInitializerClasses != null) {
   for (Class<?> waiClass : webAppInitializerClasses) {
    if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
        initializers.add((WebApplicationInitializer)
        ReflectionUtils.accessibleConstructor(waiClass).newInstance());
    }
   }
  }
  ...
  for (WebApplicationInitializer initializer : initializers) {
   initializer.onStartup(servletContext);
  }
 }
}

我们把一些不重要的代码去掉,只保留核心代码。SpringServletContainerInitializer上使用了@HandlesTypes(WebApplicationInitializer.class),表示该类只用来处理WebApplicationInitializer的具体子类。WebApplicationInitializer是Spring Web中定义的接口。

onStartup(...)方法中,程序依次调用了所有WebApplicationInitializer对象的onStartup(...)方法。这个过程有点像代理模式,表明SpringServletContainerInitializer本身并不处理任何逻辑,它将容器初始化的功能代理至各个WebApplicationInitializer的具体实现。

下面是WebApplicationInitializer的接口定义,很简单。

package org.springframework.web;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
public interface WebApplicationInitializer {
 void onStartup(ServletContext servletContext) throws ServletException;
}

总结一下,Spring Web MVC通过使用Tomcat的SCI机制,开发了一个名为SpringServletContainerInitializer的具体类。SpringServletContainerInitializer这个类触发了所有WebApplicationInitializer接口实现类的onStartup(ServletContext servletContext)方法,用于完成Spring Web MVC的初始化过程。因此,我们在使用Spring Web MVC时,可以不用管底层的实现机制,直接开发我们自己的基于接口onStartup(ServletContext servletContext)的具体实现类即可。框架本身会负责调度起这些类的onStartup(...)方法。

4 后续

反射机制、SPI机制、SCI机制以及Spring Web如何和Tomcat集成的内容都介绍完了,下一篇文章中,我们可以正式开始介绍如何不使用web.xml完成Spring Web MVC的配置了。

5 结论

你能看见的,往往只是冰山一角。但恰恰是这一角冰山,让你产生了高山仰止的敬畏之感。能介绍的代码很少,真正想彻底了解,还是要自己去翻源代码。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

镜悬xhs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值