本文主要内容是根据一个常见的springmvc 配置文件,剖析分解每个标签的工作内容。

  1. 一个非常熟悉的springmvc配置样例

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:mvc="http://www.springframework.org/schema/mvc"
   xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

   <!-- Scans the classpath of this application for @Components to deploy as beans -->
   <context:component-scan base-package="org.springframework.samples.mvc.basic" />

   <!-- Configures the @Controller programming model -->
   <mvc:annotation-driven />

   <!-- Forwards requests to the "/" resource to the "welcome" view -->
   <mvc:view-controller path="/" view-name="welcome"/>

   <!-- Configures Handler Interceptors -->   
   <mvc:interceptors>
      <!-- Changes the locale when a 'locale' request parameter is sent; e.g. /?locale=de -->
      <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
   </mvc:interceptors>

   <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources/ directory -->
   <mvc:resources mapping="/resources/**" location="/resources/" />

   <!-- Saves a locale change using a cookie -->
   <bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver" />

   <!-- Application Message Bundle -->
   <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
      <property name="basename" value="/WEB-INF/messages/messages" />
      <property name="cacheSeconds" value="0" />
   </bean>

   <!-- Resolves view names to protected .jsp resources within the /WEB-INF/views directory -->
   <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
      <property name="prefix" value="/WEB-INF/views/"/>
      <property name="suffix" value=".jsp"/>
   </bean>
</beans>

接下来,逐个标签进行分析

2.<context:component-scan>标签

public class ContextNamespaceHandler extends NamespaceHandlerSupport {

   public void init() {
       ...
      registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
      ...  
   }

ComponentScanBeanDefinitionParser负责具体解析工作。

2.1 属性值

public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {

   private static final String BASE_PACKAGE_ATTRIBUTE = "base-package";

   private static final String RESOURCE_PATTERN_ATTRIBUTE = "resource-pattern";

   private static final String USE_DEFAULT_FILTERS_ATTRIBUTE = "use-default-filters";

   private static final String ANNOTATION_CONFIG_ATTRIBUTE = "annotation-config";
   
   private static final String NAME_GENERATOR_ATTRIBUTE = "name-generator";
   
   private static final String SCOPE_RESOLVER_ATTRIBUTE = "scope-resolver";
   
   private static final String SCOPED_PROXY_ATTRIBUTE = "scoped-proxy";

   private static final String EXCLUDE_FILTER_ELEMENT = "exclude-filter";

   private static final String INCLUDE_FILTER_ELEMENT = "include-filter";

   private static final String FILTER_TYPE_ATTRIBUTE = "type";

   private static final String FILTER_EXPRESSION_ATTRIBUTE = "expression";
   ...
   }

首先在ComponentScanBeanDefinitionParser类中定义了</context:component-scan>元素的所有属性值

  • base-package:为必须配置属性,指定了spring需要扫描的跟目录名称,可以使用”,” “;” “\t\n(回车符)”来分割多个包名

  • resource-pattern:配置扫描资源格式.默认”**/*.class

  • use-default-filters:是否使用默认扫描策略,默认为”true”,会自动扫描指定包下的添加了如下注解的类,@Component, @Repository, @Service,or @Controller

  • annotation-config:是否启用默认配置,默认为”true”,该配置会在BeanDefinition注册到容器后自动注册一些BeanPostProcessors对象到容器中.这些处理器用来处理类中Spring’s @Required and
    @Autowired,  JSR 250’s @PostConstruct, @PreDestroy and @Resource (如果可用),
    JAX-WS’s @WebServiceRef (如果可用), EJB 3’s @EJB (如果可用), and JPA’s
    @PersistenceContext and @PersistenceUnit (如果可用),但是该属性不会处理Spring’s @Transactional 和 EJB 3中的@TransactionAttribute注解对象,这两个注解是通过<tx:annotation-driven>元素处理过程中对应的BeanPostProcessor来处理的.

  • include-filter:如果有自定义元素可以在该处配置

  • exclude-filter:配置哪些类型的类不需要扫描,如上面指定了类中添加了”Controller”元素的类不扫描.
    注意:</context:component-scan>元素中默认配置了annotation-config,所以不需要再单独配置</annotation-config>元素.


2.2 解析

public BeanDefinition parse(Element element, ParserContext parserContext) {
   String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE),
         ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

   // Actually scan for bean definitions and register them.
   ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
   Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
   registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

   return null;
}

ClassPathBeanDefinitionScanner

ClassPathBeanDefinitionScanner主要用来完成类路径下符合条件的bean扫描功能,并将扫描出来的bean注册到指定的BeanFactory中。
- 默认过滤器主要扫描@Component @Repository @Service @Controller注解的类,同样可以通过配置类扫描过滤器来扫描自定义注解的类。
- 当类路径下有javax.annotation.ManagedBean和javax.inject.Named类库时支持这2个注解扫描。


3 <mvc:annotation-driven />标签

public class MvcNamespaceHandler extends NamespaceHandlerSupport {

   public void init() {
      registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
      registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
      registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());     
      registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
      registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
   }

}

AnnotationDrivenBeanDefinitionParser具体负责解析工作。

3.2 具体解析方法

class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
...
public BeanDefinition parse(Element element, ParserContext parserContext) {
   Object source = parserContext.extractSource(element);

   CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
   parserContext.pushContainingComponent(compDefinition);
   
   RootBeanDefinition annMappingDef = new RootBeanDefinition(DefaultAnnotationHandlerMapping.class);
   annMappingDef.setSource(source);
   annMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
   annMappingDef.getPropertyValues().add("order", 0);
   String annMappingName = parserContext.getReaderContext().registerWithGeneratedName(annMappingDef);

   RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
   RuntimeBeanReference validator = getValidator(element, source, parserContext);
   
   RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
   bindingDef.setSource(source);
   bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
   bindingDef.getPropertyValues().add("conversionService", conversionService);
   bindingDef.getPropertyValues().add("validator", validator);

   RootBeanDefinition annAdapterDef = new RootBeanDefinition(AnnotationMethodHandlerAdapter.class);
   annAdapterDef.setSource(source);
   annAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
   annAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
   annAdapterDef.getPropertyValues().add("messageConverters", getMessageConverters(source));
   String annAdapterName = parserContext.getReaderContext().registerWithGeneratedName(annAdapterDef);

   RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
   csInterceptorDef.setSource(source);
   csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);    
   RootBeanDefinition mappedCsInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
   mappedCsInterceptorDef.setSource(source);
   mappedCsInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
   mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
   mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
   String mappedInterceptorName = parserContext.getReaderContext().registerWithGeneratedName(mappedCsInterceptorDef);
   
   parserContext.registerComponent(new BeanComponentDefinition(annMappingDef, annMappingName));
   parserContext.registerComponent(new BeanComponentDefinition(annAdapterDef, annAdapterName));
   parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));
   parserContext.popAndRegisterContainingComponent();
   
   return null;
}
...
}

(1)注册DefaultAnnotationHandlerMapping bean,具体负责请求与@Controller的mapping.

(2)注册AnnotationMethodHandlerAdapter bean,调用@Controller的方法,并注入webBindingInitializer属性和messageConverters。

(3)其中webBindingInitializer指定conversionService,validator属性。

3.1

Configures the conversionService if specified, otherwise defaults to a fresh ConversionService instance created by the default FormattingConversionServiceFactoryBean.
3.2

Configures the validator if specified, otherwise defaults to a fresh Validator instance created by the default LocalValidatorFactoryBean if the JSR-303 API is present on the classpath.
3.3

Configures standard HttpMessageConverters, including the Jaxb2RootElementHttpMessageConverter if JAXB2 is present on the classpath, and the MappingJacksonHttpMessageConverter if Jackson is present on the classpath.

4.<mvc:view-controller>标签

ViewControllerBeanDefinitionParser 负责具体解析。

主要完成 注册一个ParameterizableViewController.和SimpleUrlHandlerMapping ,SimpleControllerHandlerAdapter

SimpleUrlHandlerMapping注册的名称为"org.springframework.web.servlet.config.viewControllerHandlerMapping";

5.<mvc:interceptors>标签

注册一组MappedInterceptor 。有公共和私有之分。

<bean>标签对应公共interceptor;

<mvc:interceptor和<mvc:mapping>对应Mapping私有。

6.<mvc:resources>标签

ResourcesBeanDefinitionParser负责解析。

完成工作

(6.1) 注册一个 ResourceHttpRequestHandler

(6.2)注册一个 SimpleUrlHandlerMapping for mapping resource 

(6.3) 可能会注册一个HttpRequestHandlerAdapter.

(由AbstractHttpRequestHandlerBeanDefinitionParser.registerHandlerAdapterIfNecessary实现)

@Override
public void doParse(Element element, ParserContext parserContext) {
   Object source = parserContext.extractSource(element);
   registerResourceMappings(parserContext, element, source);
}