SpringMVC配置原理

谈及Spring的Java配置,核心类就是WebMvcConfigurationSupport
我们从WebMvcConfigurationSupport这个类开始逐步深入了解Spring的配置原理。

WebMvcConfigurationSupport

这是提供SpringMVC Java config配置的主要类。通常通过将@EnableWebMvc添加到@Configuration注解的类来导入它。另一种更高级的方式是直接扩展这个类,并根据需要重写其方法,记住要将@Configuration添加到扩展的子类中,并添加@Bean到重写的@Bean方法。有关更多详细信息,请参阅@EnableWebMvc的Javadoc。

这个类会注册下面的HandlerMappings:
  • RequestMappingHandlerMapping排序索引为0,将请求映射到控制器方法。
  • HandlerMapping排序索引为1,直接映射URL路径到视图名称。
  • BeanNameUrlHandlerMapping排序索引为2,以将URL路径映射到控制器bean名称。
  • HandlerMapping排序索引为Integer.MAX_VALUE-1,以提供静态资源请求。
  • HandlerMapping排序索引为Integer.MAX_VALUE,将请求转发到默认的servlet。
注册这些HandlerAdapter:
  • RequestMappingHandlerAdapter用于使用控制器方法处理请求。
  • HttpRequestHandlerAdapter用于使用HttpRequestHandlers处理请求。
  • SimpleControllerHandlerAdapter用于使用interface-based控制器处理请求。
用这个异常解析器链注册一个HandlerExceptionResolverComposite:
  • ExceptionHandlerExceptionResolver用于通过@ExceptionHandler方法处理异常。
  • ResponseStatusExceptionResolver用于使用@ResponseStatus注解的异常。
  • DefaultHandlerExceptionResolver用于解析已知的Spring异常类型
注册AntPathMatcher和UrlPathHelper以供以下用户使用:
  • RequestMappingHandlerMapping
  • ViewControllers的HandlerMapping
  • 用于服务资源的HandlerMapping

请注意,这些bean可以使用PathMatchConfigurer进行配置。

默认情况下,RequestMappingHandlerAdapter和ExceptionHandlerExceptionResolver都使用以下默认实例进行配置:

  • 一个ContentNegotiationManager
  • 一个DefaultFormattingConversionService
  • 一个org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean(如果JSR-303的实现存在于类路径中)
  • 一系列HttpMessageConverters,这取决于类路径上可用的第三方库。

@Configuration

首先看下声明:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    String value() default "";
}

注意,@Component@Configuration元注解,也即它具备@Component的特性。

指示一个类声明一个或多个@Bean方法,并且可以由Spring容器处理,以便在运行时为这些bean生成bean定义和处理请求,例如:

@Configuration
 public class AppConfig {

     @Bean
     public MyBean myBean() {
         // instantiate, configure and return bean ...
     }
 }

引导@Configuration类

通过AnnotationConfigApplicationContext

@Configuration类通常使用AnnotationConfigApplicationContext或web版本AnnotationConfigWebApplicationContext进行引导。 前者的一个简单例子如下:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
 ctx.register(AppConfig.class);
 ctx.refresh();
 MyBean myBean = ctx.getBean(MyBean.class);
 // use myBean ...

有关更多详细信息,请参阅AnnotationConfigApplicationContext Javadoc,有关web.xml配置说明,请参阅AnnotationConfigWebApplicationContext

通过Spring <beans> XML

作为直接针对AnnotationConfigApplicationContext注册@Configuration类的替代方法,可以在Spring XML文件中将@Configuration类声明为<bean>定义:

 <beans>
    <context:annotation-config/>
    <bean class="com.acme.AppConfig"/>
 </beans>

在上面的示例中,为了启用ConfigurationClassPostProcessor和其他与注解有关的后置处理器来处理@Configuration类,需要<context:annotation-config/>

通过组件扫描

@Component@Configuration元注解,因此@Configuration类是组件扫描的候选对象(通常使用Spring XML的<context:component-scan/>元素)。

@Configuration类不仅可以通过组件扫描进行引导,还可以自己使用@ComponentScan注解来配置组件扫描:

@Configuration
 @ComponentScan("com.acme.app.services")
 public class AppConfig {
     // various @Bean definitions ...
 }

有关详细信息,请参阅@ComponentScan javadoc。

@ComponentScan

配置用于@Configuration类的组件扫描指令。提供与Spring XML <context:component-scan>元素并行的支持。

可以指定basePackageClasses()或basePackages()(或其别名value())来定义要扫描的特定类包。如果未定义特定的包,则将从声明此注解的类的包中进行扫描。

请注意,<context:component-scan>元素具有annotation-config属性; 但是,这个注解没有。这是因为在几乎所有使用@ComponentScan的情况下,默认的annotation config processing(例如处理@Autowired之类)is assumed。此外,当使用AnnotationConfigApplicationContext和web版本AnnotationConfigWebApplicationContext时,annotation config processors总是被注册,这意味着任何试图在@ComponentScan级别禁用它们的尝试都将被忽略。有关使用示例,请参阅@Configuration的Javadoc。

💡

  • <context:annotation-config/>启用ConfigurationClassPostProcessor和其他与注解有关的后置处理器来处理@Configuration类。
  • <context:component-scan>annotation-config属性作用同<context:annotation-config/>
  • @ComponentScan默认的annotation config processing(例如处理@Autowired之类)is assumed。此外,当使用AnnotationConfigApplicationContext和web版本AnnotationConfigWebApplicationContext时,annotation config processors总是被注册。

使用外部的值

使用Environment API

通过使用@Autowired@Inject注解将Spring Environment注入@Configuration类,来查找外部的值:

@Configuration
 public class AppConfig {

     @Inject Environment env;

     @Bean
     public MyBean myBean() {
         MyBean myBean = new MyBean();
         myBean.setName(env.getProperty("bean.name"));
         return myBean;
     }
 }

通过Environment解析的属性属于一个或多个"属性源"对象,而@Configuration类可以使用@PropertySources注解向Environment对象提供属性源:

@Configuration
 @PropertySource("classpath:/com/acme/app.properties")
 public class AppConfig {

     @Inject Environment env;

     @Bean
     public MyBean myBean() {
         return new MyBean(env.getProperty("bean.name"));
     }
 }

有关更多详细信息,请参阅Environment和@PropertySource Javadoc。

使用@Value注解

外部的值可以通过@Value注解注入到@Configuration类中:

@Configuration
 @PropertySource("classpath:/com/acme/app.properties")
 public class AppConfig {

     @Value("${bean.name}") String beanName;

     @Bean
     public MyBean myBean() {
         return new MyBean(beanName);
     }
 }

这种方法在使用Spring的PropertySourcesPlaceholderConfigurer时非常有用,通常通过XML <context:property-placeholder/>来启用。

有关使用BeanFactoryPostProcessor类型(PropertySourcesPlaceholderConfigurer)的详细信息,请参阅下面有关使用@ImportResource导入Spring XML来构造@Configuration类的部分,@Value Javadoc,@Bean Javadoc。

构造@Configuration类

用@Import注解

@Configuration类可以使用@Import注解构造,与<import>在Spring XML中的工作方式相似。 由于@Configuration类对象是作为容器内的Spring bean进行管理的,因此可以使用@Autowired@Inject注入导入的配置:

@Configuration
 public class DatabaseConfig {

     @Bean
     public DataSource dataSource() {
         // instantiate, configure and return DataSource
     }
 }

 @Configuration
 @Import(DatabaseConfig.class)
 public class AppConfig {

     @Inject DatabaseConfig dataConfig;

     @Bean
     public MyBean myBean() {
         // reference the dataSource() bean method
         return new MyBean(dataConfig.dataSource());
     }
 }

现在,AppConfig和导入的DatabaseConfig都可以通过在Spring上下文中注册AppConfig来引导:new AnnotationConfigApplicationContext(AppConfig.class);

用@Profile注解

@Configuration类可以使用@Profile注解标记,以表明只有给定的一个或多个profile处于active时才应该处理它们:

@Profile("embedded")
 @Configuration
 public class EmbeddedDatabaseConfig {

     @Bean
     public DataSource dataSource() {
         // instantiate, configure and return embedded DataSource
     }
 }

 @Profile("production")
 @Configuration
 public class ProductionDatabaseConfig {

     @Bean
     public DataSource dataSource() {
         // instantiate, configure and return production DataSource
     }
 }

有关更多详细信息,请参阅@Profile和Environment javadocs。

使用@ImportResource注解导入Spring XML

如上所述,@Configuration类可以在Spring XML文件中声明为常规的Spring <bean>定义。也可以使用@ImportResource注解将Spring XML配置文件导入到@Configuration类中。 从XML导入的Bean定义可以使用@Autowired@Inject注入:

@Configuration
 @ImportResource("classpath:/com/acme/database-config.xml")
 public class AppConfig {

     @Inject DataSource dataSource; // from XML

     @Bean
     public MyBean myBean() {
         // inject the XML-defined dataSource bean
         return new MyBean(this.dataSource);
     }
 }
嵌套的@Configuration类

@Configuration类可以如下嵌套在一起:

@Configuration
 public class AppConfig {

     @Inject DataSource dataSource;

     @Bean
     public MyBean myBean() {
         return new MyBean(dataSource);
     }

     @Configuration
     static class DatabaseConfig {
         @Bean
         DataSource dataSource() {
             return new EmbeddedDatabaseBuilder().build();
         }
     }
 }

当引导这样的配置时,只有AppConfig需要针对应用上下文进行注册。由于是一个嵌套的@Configuration类,DatabaseConfig将被自动注册。这样可以避免使用@Import注解。

配置延迟初始化

默认情况下,@Bean方法将在容器引导时被迫切地实例化。
为了避免这种情况,可以将@Configuration@Lazy注解结合使用,以表明在类中声明的所有@Bean方法在默认情况下是懒惰地初始化的。请注意@Lazy也可以用于单独的@Bean方法。

Testing对@Configuration类的支持

spring-test模块中提供的Spring TestContext框架提供@ContextConfiguration注解,从Spring 3.1开始,它可以接受一个@Configuration Class对象的数组:

@RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(classes={AppConfig.class, DatabaseConfig.class})
 public class MyTests {

     @Autowired MyBean myBean;

     @Autowired DataSource dataSource;

     @Test
     public void test() {
         // assertions against myBean ...
     }
 }

有关详细信息,请参阅TestContext框架参考文档。

使用@Enable注解启用内置的Spring功能

诸如异步方法执行,计划任务执行,注解驱动事务管理,甚至Spring MVC等Spring特性可以使用各自的@Enable*注解在@Configuration类中启用和配置。有关详细信息,请参阅@EnableAsync@EnableScheduling@EnableTransactionManagement@EnableAspectJAutoProxy@EnableWebMvc

@EnableWebMvc

看下声明:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

将此注解添加到@Configuration类中,从WebMvcConfigurationSupport导入Spring MVC配置,例如:

@Configuration
 @EnableWebMvc
 @ComponentScan(basePackageClasses = { MyConfiguration.class })
 public class MyWebConfiguration {

 }

要自定义导入的配置,请实现WebMvcConfigurer接口,或者更好的方式是扩展包含一系列空方法的基类WebMvcConfigurerAdapter并覆盖单个方法,例如:

@Configuration
 @EnableWebMvc
 @ComponentScan(basePackageClasses = { MyConfiguration.class })
 public class MyConfiguration extends WebMvcConfigurerAdapter {

           @Override
           public void addFormatters(FormatterRegistry formatterRegistry) {
         formatterRegistry.addConverter(new MyConverter());
           }

           @Override
           public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
         converters.add(new MyHttpMessageConverter());
           }

     // More overridden methods ...
 }

如果WebMvcConfigurer没有暴露某些需要配置的高级设置,请考虑删除@EnableWebMvc注解并直接扩展WebMvcConfigurationSupportDelegatingWebMvcConfiguration,例如:

@Configuration
 @ComponentScan(basePackageClasses = { MyConfiguration.class })
 public class MyConfiguration extends WebMvcConfigurationSupport {

           @Override
           public void addFormatters(FormatterRegistry formatterRegistry) {
         formatterRegistry.addConverter(new MyConverter());
           }

           @Bean
           public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
         // Create or delegate to "super" to create and
         // customize properties of RequestMappingHandlerAdapter
           }
 }

DelegatingWebMvcConfiguration

声明如下:

@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

WebMvcConfigurationSupport的一个子类,用于检测并委托所有类型为WebMvcConfigurer的Bean,使其可以自定义由WebMvcConfigurationSupport提供的配置。 这是由@EnableWebMvc实际导入的类。

总结

上面介绍了几个核心的API,下面说下他们彼此之间是如何关联,以及如何起作用的。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

一、用户创建Java Config配置类,并使用@Configuration注解注释。

二、引导@Configuration配置类,上面提到三种方式:

  1. 通过AnnotationConfigApplicationContext

    @Configuration类通常使用AnnotationConfigApplicationContext或web版本AnnotationConfigWebApplicationContext进行引导。

    前者的一个简单例子如下:

    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class);
    ctx.refresh();
    MyBean myBean = ctx.getBean(MyBean.class);
    // use myBean ...
    
  2. 通过Spring <beans> XML

    在Spring XML文件中将@Configuration类声明为<bean>定义

    <beans>
       <context:annotation-config/>
       <bean class="com.acme.AppConfig"/>
    </beans>
    
    • <context:annotation-config/>启用ConfigurationClassPostProcessor和其他与注解有关的后置处理器来处理@Configuration类。
    • <context:component-scan>annotation-config属性作用同<context:annotation-config/>
  3. 通过组件扫描

    @Component@Configuration元注解,因此@Configuration类是组件扫描的候选对象。

    可以自己使用@ComponentScan注解来配置组件扫描:

     @Configuration
     @ComponentScan("com.acme.app.services")
     public class AppConfig {
         // various @Bean definitions ...
     }
    

    TODO @ComponentScan默认的annotation config processing(例如处理@Autowired之类)is assumed。此外,当使用AnnotationConfigApplicationContext和web版本AnnotationConfigWebApplicationContext时,annotation config processors总是被注册。

三、将@EnableWebMvc注解添加到@Configuration类中,从WebMvcConfigurationSupport导入Spring MVC配置

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

@EnableWebMvc注解通过@Import(DelegatingWebMvcConfiguration.class)导入Spring MVC配置。

DelegatingWebMvcConfiguration类中又通过如下方法注入了WebMvcConfigurer,用于导入用于的自定义配置。

可以看到,

@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (configurers == null || configurers.isEmpty()) {
return;
}
this.configurers.addWebMvcConfigurers(configurers);
}

通过@Autowired(required = false)注入了上下文中所有类型为WebMvcConfigurer的bean,其中required为false,说明自定义配置是可选的)。如果你创建的配置类实现WebMvcConfigurer接口,并交给Spring去管理,则会被注入到WebMvcConfigurerComposite中。

WebMvcConfigurerComposite的声明如下:

class WebMvcConfigurerComposite implements WebMvcConfigurer {

    private final List<WebMvcConfigurer> delegates = new ArrayList<WebMvcConfigurer>();

    public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
        if (configurers != null) {
            this.delegates.addAll(configurers);
        }
    }

WebMvcConfigurerComposite维护了一个WebMvcConfigurer的List集合,addWebMvcConfigurers方法将所有的自定义配置加入该集合中。

TODO

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容