第 7 节 @Configuration注解与Java类配置

1 基于 xml 文件的应用配置

​ 对于 SpringBoot 框架的使用老鸟可能会对 Java 类配置和 xml 文件配置的对应关系有清楚的了解,不过为了照顾部分 SpringBoot 框架使用新手同学,在这里分析一下两者的对应关系和如何使用 Java 类配置来替代 XML 文件配置。

​ 传统的 Spring 应用一般都需要两个 xml 配置文件,分别是 Spring 框架的配置文件 applicationContext.xml 和 Spring MVC 的配置文件 dispatcher-servlet.xml 配置文件。这两个配置的文件的顶层 XML 标签都是 beans,关于这两个配置文件的内容如下所示。

1.1 Spring 的 xml 文件配置

​ Spring 配置主要是在 applicationContext.xml (由于最终还需要在 web.xml 中配置,故该文件的名字可以修改,SpringMVC 的配置文件名字也是类似,不再赘述)文件中定义:主要是进行 Spring 主容器的相关配置,如启用组件扫描,配置数据库数据源等。

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context.xsd" >

    <!-- 组件扫描 -->
    <context:component-scan base-package="com.yzxie.demo.springdemo.context.xml" />

    <!--配置 Java bean 对象 -->
    <bean id="demoModel" class="com.yzxie.demo.springdemo.context.xml.DemoModel">
        <property name="name" value="xyz"/>
    </bean>

    <!-- 省略其他配置内容 -->

</beans>

​ Java bean 对象使用 标签来配置,Java 对象的成员变量使用 标签来配置。

​ 而对于其他实现某个功能的相关配置,则需要配置对应的标签,例如 Java bean 对象自动扫描是通过配置 <context:component-scan /> 标签来开启,并且可以在该标签通过 base-package 属性来配置需要扫描的包路径。关于更多可以使用的标签可以参考 Spring 官方的相关文档来拓展学习。

tips:Spring 框架是应用启动的入口,而 applicationContext.xml 文件是 Spring 框架的总配置文件,所有 Spring 框架提供的其他功能,如 Spring 事务机制对应的相关功能 Java bean 对象,都需要在 applicationContext.xml 文件配置,从而可以在应用程序启动时被加载和使用。或者如果使用额外的配置文件,则需要在 applicationContext.xml文件引入或者在应用配置文件 web.xml 中引入。

1.2 SpringMVC 的 xml 文件配置

​ Spring MVC 配置文件为 dispatcher-servlet.xml,主要在该文件进行 MVC 相关功能的配置,如页面模板引擎,静态资源文件等。SpringMVC 在内部会使用一个子容器来管理这些配置对应的 bean 对象。

<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:mvc="http://www.springframework.org/schema/mvc"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
   http://www.springframework.org/schema/beans     
   http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
   http://www.springframework.org/schema/context 
   http://www.springframework.org/schema/context/spring-context-4.3.xsd
   http://www.springframework.org/schema/mvc
   http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
   
    <!-- 启用注解驱动,如@Controller, @Service等注解的使用 --> 
    <mvc:annotation-driven />

    <!-- 静态资源文件,如前端js等 -->
    <mvc:resources location="/static" mapping="/static/**" />

    <!-- thymeleaf前端模板技术的相关配置 -->
    <bean id="templateResolver"
           class="org.thymeleaf.templateresolver.ServletContextTemplateResolver">
      <property name="prefix" value="/WEB-INF/templates/" />
      <property name="suffix" value=".html" />
      <property name="templateMode" value="HTML" />
      <property name="characterEncoding" value="UTF-8" />
    </bean>

    <!-- 省略其他配置 -->
</beans>

知识拓展:

对于 Java 企业级应用来说,还存在数据库、定时任务等配置,所以如果都在 applicationContext.xml 文件配置,则会导致该文件内容太大。

为了解决这个问题, Spring 框架支持在其他 xml 文件配置各个功能,如在一个 spring-db.xml 文件进行数据库相关的配置,然后在 Spring 框架的总配置文件 applicationContext.xml 中通过 < import /> 标签来引入这些额外的子配置文件或者如上 tips 所述,在应用配置文件 web.xml 中引入。

2 @Configuration 注解与 Java 类配置

2.1 SpringBoot 框架的 ”零“ xml 文件配置

​ SpringBoot 框架是完全不需要通过 xml 文件来进行应用配置的,而是通过 Java 类来进行应用配置。由前一节的分析可知,SpringBoot 框架提供的 @SpringBootApplication 注解包含的其中一个注解是 @SpringConfiguration 注解,而这个注解与 @Configuration 注解是等效的,@Configuration 注解是从 Spring 3.x 版本提供的用于实现基于 Java 类进行应用配置的。在 SpringBoot 应用中,应用启动类 Application 就是一个配置类。

tips:相对于基于 xml 文件进行配置,使用 Java 类以代码编程的方式来进行应用配置的好处是可以更加灵活地实例化 bean 对象和处理 bean 之间的依赖关系,以及实现类型安全的配置管理,避免在 xml 文件中可能出现拼写错误等导致配置出错的问题。

2.2 基于 @Configuration 注解与 Java 类的应用配置

​ Java 类配置是指通过在一个 Java 类中来维护以上的 xml 文件的相关配置内容,并且需要在该类使用 @Configuration 注解来使该类被 Spring 框架识别为配置类。

​ Java 配置类相当于 xml 文件最外层的 beans 标签,类的每个方法则是对应到 beans 标签的每个子标签。例如,对该配置类的某个方法使用 @Bean 注解,相当于在 xml 文件定义了一个 标签,所以在该方法内部会创建一个对象并返回,此时该对象会被 Spring IOC 容器管理。

​ 除此之外,还可以在该类上使用额外的 Java 注解来代替一个 beans 标签内部的功能子标签。例如,使用 @ComponentScan 注解替代 xml 文件的 <context:component-scan /> 标签来启用组件自动扫描功能。

​ 如下使用一个 Java 配置类来替代以上的 applicationContext.xml 配置文件:

@Configuration // 相对于 <beans> 标签
@ComponentScan(basePackages = "com.yzxie.demo.springdemo.context.xml") // 相当于 <context:component-scan /> 标签
public class ApplicationConfig {

    @Bean // 相当于 <bean> 标签
    public DemoModel demoModel() {
        DemoModel demoModel = new DemoModel();
        demoModel.setName("xyz");
        return demoModel;
    }
}

拓展知识:

Spring 框架的功能注解替代 XML 文件的功能子标签包括:

  1. 使用 @ComponentScan 注解来替代 <context:component-scan /> 标签,实现组件自动扫描;
  2. 使用 @PropertySource 注解来替代 <context:property-placeholder />标签,实现对如 properties 配置文件的引入,注意 @ImportSource 注解与这个注解类似,不过 @ImportSource 一般是用于引入 xml 配置文件;
  3. 使用 @EnableWebMvc 注解来替代 <mvc:annotation-driven /> ,启用 SpringMVC 功能;
  4. 使用 @Import 注解来替代 标签,引入其他 Java 配置类;
  5. 使用 @EnableTransactionManagement 注解来替代 <tx:annotation-driven /> 来开启基于注解的事务管理。

​ 与基于 XML 文件的配置可以在多个 XML 文件进行配置类似,基于 @Configuration 注解实现的 Java 类配置也可以使用多个 Java 类作为配置类,然后在主配置类中使用 @Import 注解来引入其他多个使用了 @Configuration 注解的 Java 配置类。

tips:基于 Java 类进行配置与基于 XML 文件配置不是只能二选一的,而是可以一起使用的,这个也是为了处理版本的兼容问题。

具体为如果应用是使用 XML 文件来进行应用配置,则可以将使用了 @Configuration 注解的配置类包含在 <context:component-scan /> 标签的 base-package 所对应的包路径中;如果是使用 Java 类配置,则可以使用 @ImportSource 注解来引入 XML 配置文件。

3 @Configuration注解的实现原理

​ 使用 @Configuration 注解的类就可以被 Spring 识别为配置类,并处理该类上的相关功能注解,如 @ComponentScan,同时解析该类内部使用了 @Bean 注解的方法为 Spring IOC 容器的 bean 对象,这些在 Spring 内部的实现原理是如何?下面我们来进行详细分析。

3.1 bean 工厂后置处理器:ConfigurationClassPostProcessor

​ 由之前的第 5 节关于 Spring IOC 容器的初始化流程的分析可知,在 Spring 框架启动并初始化 Spring IOC 容器时,会首先从 xml 文件或者基于组件扫描获取 Java 类的元数据并创建对应的 BeanDefinition 对象保存到内部的 BeanFactory 接口实现类 bean 工厂的 BeanDefinition 对象集合中,然后会通过 bean 工厂后置处理器 BeanFactoryProcessor 接口的实现类来对该 BeanDefinition 对象集合进行处理。

​ @Configuration 注解的类的处理也是在这个阶段完成的,具体为通过 ConfigurationClassPostProcessor 这个 BeanFactoryProcessor接口实现类来进行处理。ConfigurationClassPostProcessor 会从该集合中查找所有使用了 @Configuration 注解的类:

  1. 处理该类上的其他功能注解,如处理 @ComponentScan 注解,对该注解的 basePackages 指定的包进行扫描,实现 Java bean 对象的加载和创建对应的 BeanDefinition 对象;

  2. 解析该类内部的使用了 @Bean 注解的方法,创建对应的 BeanDefinition 注册到该集合中。

3.2 ConfigurationClassPostProcessor 的处理 @Configuration 注解的源码实现

​ 如下为 ConfigurationClassPostProcessor 处理使用了 @Configuration 注解的核心处理方法 processConfigBeanDefinitions 的源码实现:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
	String[] candidateNames = registry.getBeanDefinitionNames();
  
  // 1. 从BeanFactory中获取使用@Configuration注解的类
  for (String beanName : candidateNames) {
    BeanDefinition beanDef = registry.getBeanDefinition(beanName);
    // 判断是否使用了 @Configuration 注解
    if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
        ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
      // 省略其他代码
    }
    else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
      configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
    }
  }

  // 没有任何了使用了@Configuration注解则直接返回
  if (configCandidates.isEmpty()) {
    return;
  }

  // 省略其他代码

  // 2. 使用ConfigurationClassParser处理这些筛选出来的,使用了@Configuration注解的类上面的其他功能注解,
  // 如@ComponentScan,@Import,@PropertySource等

  ConfigurationClassParser parser = new ConfigurationClassParser(
      this.metadataReaderFactory, this.problemReporter, this.environment,
      this.resourceLoader, this.componentScanBeanNameGenerator, registry);

  Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
  Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
  do {

      // 执行解析
    parser.parse(candidates);
    parser.validate();

    Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
    configClasses.removeAll(alreadyParsed);

      // 3. 交给ConfigurationClassBeanDefinitionReader
      // 完成@Configuration注解的类内部注解的处理
      // 如@Bean,嵌套@Configuration等。
    if (this.reader == null) {
      this.reader = new ConfigurationClassBeanDefinitionReader(
          registry, this.sourceExtractor, this.resourceLoader, this.environment,
          this.importBeanNameGenerator, parser.getImportRegistry());
    }

    // 4. 注册@Bean方法对应的bean到BeanFactory
    this.reader.loadBeanDefinitions(configClasses);

  // 省略其他代码
}

代码有点长,需要认真看注释理解一下_。核心处理步骤分别为:

  1. 通过遍历 bean 工厂的 BeanDefinition 对象集合来查找和获取使用 @Configuration 注解的类;
  2. 使用 ConfigurationClassParser 处理这些筛选出来的,使用了@Configuration 注解的类上面的其他功能注解,如 @ComponentScan,@Import,@PropertySource 等;
  3. 使用 ConfigurationClassBeanDefinitionReader 来处理类内部使用了 @Bean 注解的方法,以及处理嵌套引入的使用 @Configuration 注解的其他配置类;
  4. 注册 @Bean 方法对应的 BeanDefinition 对象到 bean 工厂的 beanDefinition 对象集合中。

tips:Spring 框架为了实现拓展性,通过定义后置处理器来对 bean 工厂和 bean 对象分别进行加工处理,其中 bean 工厂是使用 BeanFactoryPostProcessor 接口的实现类来进行后置处理,如 @ComponentScan 注解的处理,关于这个注解我们会在下一节进行详细分析,敬请期待_

bean 对象是使用 BeanPostProcessor 接口的实现类来进行后置处理,其中我们最熟悉的 @Autowired 注解就是通过 BeanPostProcessor 接口的实现类来进行处理,实现 bean 对象之间的依赖注入。

4 总结

​ 在本小节我们分析了 Spring 框架的两种应用配置方式,分别为基于 xml 文件的配置和基于 @Configuration 注解实现的 Java 类配置,其中后者是 SpringBoot 框架使用的配置方式。

​ 当今的 Spring 应用中都推崇使用基于 @Configuration 注解实现的 Java 类配置,这种配置方式的好处是我们可以以类型安全的方式进行应用配置,减少配置错误。同时可以更加灵活地进行 bean 对象的实例化和依赖处理,最后是 Spring 提供了丰富的配套注解来替代 xml 的配置标签,如 @ComponentScan,@EnableTransactionManagement 等,简化了配置的复杂度。

​ 在内部实现层面,Spring 框架是通过定义 bean 工厂后置处理器来实现对 @Configuration 注解的处理的。具体为通过 ConfigurationClassPostProcessor 这个 BeanFactoryPostProcessor 接口的实现类来识别 @Configuration 注解的类并处理其上的其他功能注解和内部的方法注解等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值