在servlet3.0之前,我们必须将listener、filter、servlet声明在web.xml中。在servlet3.0之后为什么我们启动springmvc就可以不需要web.xml了呢?
我们一直在说servlet3.0,但是你真的知道3.0之后有哪些改动让我们可以不需要用到web.xml了吗?
首先这是servlet3.0定义的接口,它定义的规则就是:
- 实现了这个接口的类要加在META-INF/services/javax.servlet.ServletContainerInitializer文件中才会生效
- 实现了这个接口的类,这个类上被加了@HandlesTypes注解的类会被注入到这个类的onStartup方法的第一个参数中,也就是Set集合
- servlet容器在启动的时候要调用这个类的onStartup方法
package javax.servlet;
import java.util.Set;
/**
* 定义在META-INF/services/javax.servlet.ServletContainerInitializer文件中
*
* @since Servlet 3.0
*/
public interface ServletContainerInitializer {
void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}
来看看spring中是怎样做的
spring中SpringServletContainerInitializer实现了ServletContainerInitializer接口,@HandlesTypes传入的是WebApplicationInitializer
在onStartup方法最后循环调用了WebApplicationInitializer的onStartup方法
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(@Nullable 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)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).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);
}
}
}
}
接下来看看spring官网,官方推荐我们正确打开springmvc的方式如下,现在就知道为什么要这么用了吧
最后还有一个点就是,凭什么启动servlet容器的时候会进到我的SpringServletContainerInitializer
需要清楚的一点就是,servlet只是定义了规则,实现是servlet容器供应商来实现的,如tomcat jetty 等
以tomcat为例子,tomcat在启动Context之前会读取classpath路径下的META-INF/services/javax.servlet.ServletContainerInitializer文件,然后将读取到ServletContainerInitializer对象作为key,@HandlesType上的类做为value存入StandardContext中的initializers
然后在初始化StandardContext的时候遍历这个set,逐个执行他们的onStartup方法,这样就可以无需web.xml,将DispatcherServlet添加到servlet容器中
public class StandardContext extends ContainerBase
implements Context, NotificationEmitter {
private Map<ServletContainerInitializer,Set<Class<?>>> initializers =
new LinkedHashMap<ServletContainerInitializer,Set<Class<?>>>();
@Override
protected synchronized void startInternal() throws LifecycleException {
//*******此处省略*********
// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}
//******此处省略******
}
}
附上github的demo
通过maven插件运行
需要注意的是运行后的base url
访问localhost:8080/mvc/index,又来到了我们熟悉的世界“hello world"