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;
}
再分别定义该接口的两个名为ServletStartInitialOne
和ServletStartInitialTwo
的具体实现。
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.ServletStartInitialOne
和org.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 结论
你能看见的,往往只是冰山一角。但恰恰是这一角冰山,让你产生了高山仰止的敬畏之感。能介绍的代码很少,真正想彻底了解,还是要自己去翻源代码。