在servlet3.0之前,是使用xml配置文件来启动springmvc的。

主要是:

在web.xml里面配置一个listener以及一个dispatcherServlet,可以配置一个applicationContext的父容器配置文件;

再配置一个springmvc的配置文件,里面主要是配置component-scan和annotation-driven;

 

在servlet3.0及以后,web容器支持了基于java文件配置启动springmvc的机制。不需要配置web.xml以及springmvc的xml配置文件。这里先看一个简单的例子:

首先,定义一个应用级别的配置文件。可以直接继承AbstractAnnotationConfigDispatcherServletInitializer类方便配置。

public class MyInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}

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

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

getRootConfigClasses方法指定了spring父容器配置类;

getServletConfigClasses方法指定了springmvc子容器配置类;

getServletMappings方法指定了dispatcherServlet的路径。

SpringConfig.java:

public class SpringConfig {
}

由于不需要配置父容器,所以配置类留空。

SpringMvcConfig.java:

@ComponentScan("com.liyao.controller")
@EnableWebMvc
public class SpringMvcConfig extends WebMvcConfigurerAdapter {

}

继承自WebMvcConfigurerAdapter类,更加方便,里面提供了很多方法的默认实现。该类就是用于配置springmvc的,注入viewResolver,intercepters等等。

这里通过两个注解配置了扫包路径和基于注解。

至此,一个简单的springmvc配置完毕,写一个controller:

@RestController
public class Main {

@RequestMapping("/test")
public String test(){
return "hello";
}
}

启动即可。

 

原理

可以打开SpringServletContainerInitializer类看注释:

* Servlet 3.0 {@link ServletContainerInitializer} designed to support code-based
* configuration of the servlet container using Spring's {@link WebApplicationInitializer}
* SPI as opposed to (or possibly in combination with) the traditional
* {@code web.xml}-based approach.
*
* <h2>Mechanism of Operation</h2>
* This class will be loaded and instantiated and have its {@link #onStartup}
* method invoked by any Servlet 3.0-compliant container during container startup assuming
* that the {@code spring-web} module JAR is present on the classpath. This occurs through
* the JAR Services API {@link ServiceLoader#load(Class)} method detecting the
* {@code spring-web} module's {@code META-INF/services/javax.servlet.ServletContainerInitializer}
* service provider configuration file. See the
* <a href="http://download.oracle.com/javase/6/docs/technotes/guides/jar/jar.html#Service%20Provider">
* JAR Services API documentation</a> as well as section <em>8.2.4</em> of the Servlet 3.0
* Final Draft specification for complete details.
*

其实里面说的很明白:在Servlet3.0规范中支持了基于注解方式配置spring,用于代替传统的xml配置文件。

是基于SPI机制,在Servlet3.0容器启动时,会找实现了ServletContainerInitializer接口的类,并调用其onStartup方法。该接口在servlet规范的一部分,具体在servlet的jar包中。

上述SpringServletContainerInitializer类是spring中对该接口的实现。

看下其onStartup接口的实现:

@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {

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

if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer) 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;
}

AnnotationAwareOrderComparator.sort(initializers);
servletContext.log("Spring WebApplicationInitializers detected on classpath: " + initializers);

for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}

}

其实很简单,就是轮流调用WebApplicationInitialzer接口的onStartup方法,这个接口是spring标准中之一。

其实最开始我们的配置类继承自AbstractAnnotationConfigDispatcherServletInitializer,这个父类就是该接口的一个实现。

其onStartup方法的实现定义在其父类中AbstractDispatcherServletInitializer:

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);

registerDispatcherServlet(servletContext);
}

这里干了两件事,一是再调用父类的onStartup方法,二是注册dispatcherServlet。先看二吧:

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

WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext,
"createServletApplicationContext() did not return an application " +
"context for servlet [" + servletName + "]");

DispatcherServlet dispatcherServlet = new DispatcherServlet(servletAppContext);
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
Assert.notNull(registration,
"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[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}

customizeRegistration(registration);
}

@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
servletAppContext.register(configClasses);
}
return servletAppContext;
}

可以看到,这里创建了一个spring子容器,类型是AnnotationConfigWebApplicationContext,注意这里只是new了而已,并没有refresh;然后创建了一个dispatcherServlet,动态加载到web容器中。

另外注意,在创建容器实例时,将配置类register了:

public void register(Class<?>... annotatedClasses) {
Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");
this.annotatedClasses.addAll(Arrays.asList(annotatedClasses));
}

将java类扔进一个annotatedClasses实例中,这个register会在后面的refresh中起作用。

web容器动态加载一个servlet时,会调用其initServletBean方法,该方法中会做容器的refresh,其中有一步是loadBeanDefinitions,我们的容器是AnnotationConfigWebApplicationContext,其实现的loadBeanDefinitions方法为:

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(beanFactory);
reader.setEnvironment(getEnvironment());

ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);
scanner.setEnvironment(getEnvironment());

BeanNameGenerator beanNameGenerator = getBeanNameGenerator();
ScopeMetadataResolver scopeMetadataResolver = getScopeMetadataResolver();
if (beanNameGenerator != null) {
reader.setBeanNameGenerator(beanNameGenerator);
scanner.setBeanNameGenerator(beanNameGenerator);
beanFactory.registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator);
}
if (scopeMetadataResolver != null) {
reader.setScopeMetadataResolver(scopeMetadataResolver);
scanner.setScopeMetadataResolver(scopeMetadataResolver);
}

if (!this.annotatedClasses.isEmpty()) {
if (logger.isInfoEnabled()) {
logger.info("Registering annotated classes: [" +
StringUtils.collectionToCommaDelimitedString(this.annotatedClasses) + "]");
}
reader.register(this.annotatedClasses.toArray(new Class<?>[this.annotatedClasses.size()]));
}

if (!this.basePackages.isEmpty()) {
if (logger.isInfoEnabled()) {
logger.info("Scanning base packages: [" +
StringUtils.collectionToCommaDelimitedString(this.basePackages) + "]");
}
scanner.scan(this.basePackages.toArray(new String[this.basePackages.size()]));
}

String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
try {
Class<?> clazz = getClassLoader().loadClass(configLocation);
if (logger.isInfoEnabled()) {
logger.info("Successfully resolved class for [" + configLocation + "]");
}
reader.register(clazz);
}
catch (ClassNotFoundException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not load class for config location [" + configLocation +
"] - trying package scan. " + ex);
}
int count = scanner.scan(configLocation);
if (logger.isInfoEnabled()) {
if (count == 0) {
logger.info("No annotated classes found for specified class/package [" + configLocation + "]");
}
else {
logger.info("Found " + count + " annotated classes in package [" + configLocation + "]");
}
}
}
}
}
}

其中有一步,就是将annotatedClasses里的bean注册,以方便后面的processor可以读到这个bean并处理。

再往后,会有一个BeanFactoryPostProcessor类来处理@Conponent注解,读取配置,完成bean的注册。

 

 

再看下父类AbstractContextLoaderInitializer的onStartup方法:

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

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

@Override
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(configClasses);
return rootAppContext;
}
else {
return null;
}
}

这里创建了父容器。