【小家Spring】Spring中@PropertySource和@ImportResource的区别,以及各自的实现原理解析(上)

简介: 【小家Spring】Spring中@PropertySource和@ImportResource的区别,以及各自的实现原理解析(上)

前言


@PropertySource和@ImportResource或许很多人都用过,并且都还没有用错。但是若真把他俩拿过来一起的时候,却有点傻傻分不清楚了。


是的,他俩都是向容器中导入Bean/属性信息,但是使用起来还是有很大的区别的,因此本文主要针对于他俩的区别,顺便从一定的原理的角度做一个解释,希望大家以后能区分开来。


在讲解之前,可以记住一个通用的的结论:


@PropertySource用于导入.properties的属性配置文件(能导入yaml吗,且继续往下看吧)

@ImportResource用于导入.xml的Bean信息的配置文件(能导入,properties吗,且继续看~)


@ImportResource


指示包含要导入的bean定义的一个或多个资源。它的功能比较像@Import注解,就是向容器内导入Bean。只是@ImportResource它导入的是一个xml配置文件,然后通过解析xml文件的方式再把解析好的Bean信息导入到Spring容器内。


我个人认为:这个注解它是Spring拿出来的一个过渡性产品,因为Spring3.0推荐使用全注解驱动后,所有的Bean都完全可以用注解来代替了。而Spring提供这个注解主要是为了向下兼容,便于老项目进行迁移。

其实使用XML是一种非常不好的选择,Java工程师就应该着眼于java应用上,而不是一会schema,一会DTD之类的


当然既然Spring提供了这个功能,有的时候还是非常有用的。比如当DUBBO还没有跟上注解只能使用xml的时候,这个导入注解就能发挥非常重要的作用了~


使用Demo


比如我在classpath下有这个xml文件:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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.xsd">
    <bean id="myPerson" class="com.fsx.bean.Person">
        <property name="name" value="fsx"/>
        <property name="age" value="18"/>
    </bean>
</beans>


在配置类上导入此资源:


@Configuration
@ImportResource(locations = "classpath:spring-beans.xml")
public class RootConfig {
}


单元测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class})
public class TestSpringBean {
    @Autowired
    private ApplicationContext applicationContext;
    @Test
    public void test1() {
        Object myPerson = applicationContext.getBean("myPerson");
        System.out.println(myPerson); // Person{name='fsx', age=18}
    }
}


这个myPerson这个Bean能够被我正常获取到。

那么它能够导入非xml文件吗???其实这个待我解释完它的原理后,这个问题就不攻自破了~


实现原理剖析


解析配置类、Bean定义的前部分原理这里就不在叙述了,还不太清楚的建议参见博文:

【小家Spring】Spring解析@Configuration注解的处理器:ConfigurationClassPostProcessor(ConfigurationClassParser)


下面我们直接定位到解析@ImportResource注解的源码处:

class ConfigurationClassParser {
  ...
  @Nullable
  protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
      throws IOException {
      //1、解析嵌套内部类
      //2、解析@PropertySource  === 这是下面的内容 ====
    // 相当于拿到所有的PropertySource注解,注意PropertySources属于重复注解的范畴~~~
    for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
        sourceClass.getMetadata(), PropertySources.class,
        org.springframework.context.annotation.PropertySource.class)) {
      // 这个判断目前来说是个恒等式~~~  所以的内置实现都是子接口ConfigurableEnvironment的实现类~~~~
      // processPropertySource:这个方法只真正解析这个注解的地方~~~
      if (this.environment instanceof ConfigurableEnvironment) {
        processPropertySource(propertySource);
      } else {
        logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
            "]. Reason: Environment must implement ConfigurableEnvironment");
      }
    }
      //3、解析@ComponentScan
      //4、解析@Import
      //5、解析@ImportResource 
    //拿到这个注解~~~~~~~~~~~
    AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
    if (importResource != null) {
      String[] resources = importResource.getStringArray("locations");
      // readerClass 这个在自定义规则也是非常重要的一块内容~~~~~
      Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
      for (String resource : resources) {
        // 显然它还支持${}这种方式去环境变量里取值的~~~比如spring-beans-${profie}.xml等
        String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
        // 此处仅仅是吧注解解析掉,然后作为属性添加到configClass里面去,还并不是它真正的执行时机~~~~~
        configClass.addImportedResource(resolvedResource, readerClass);
      }
    }
      //6、解析@Bean
      //7、解析接口default方法~~~ 也可以用@Bean标注
      //8、解析super class父类
  }
}


上面分析了,真正解析这个文件,然后把Bean定义加入到容器的行为:


class ConfigurationClassBeanDefinitionReader {
  // 从ConfigurationClass里面真正的加载Bean定义信息~~~
  private void loadBeanDefinitionsForConfigurationClass(
      ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
    if (trackedConditionEvaluator.shouldSkip(configClass)) {
      String beanName = configClass.getBeanName();
      if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
        this.registry.removeBeanDefinition(beanName);
      }
      this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
      return;
    }
    // 如果这个配置类是被@Import的,那就第一个执行了~~~
    if (configClass.isImported()) {
      registerBeanDefinitionForImportedConfigurationClass(configClass);
    }
    // 加载标注了@Bean的~~
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
      loadBeanDefinitionsForBeanMethod(beanMethod);
    }
    //这个是我们今天关心的:解析@ImportedResource里面具体的Bean定义信息~~~
    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
  }
  private void loadBeanDefinitionsFromImportedResources(Map<String, Class<? extends BeanDefinitionReader>> importedResources) {
    Map<Class<?>, BeanDefinitionReader> readerInstanceCache = new HashMap<>();
    // 因为可以导入多个资源  所以这里遍历
    importedResources.forEach((resource, readerClass) -> {
      // Default reader selection necessary?
      // 从这里能够看出来,若我们自己没有指定BeanDefinitionReader,那它最终默认会采用XmlBeanDefinitionReader
      // ~~~~~这就是为什么默认情况下,只支持导入xml文件的原因~~~~~
      if (BeanDefinitionReader.class == readerClass) {
        if (StringUtils.endsWithIgnoreCase(resource, ".groovy")) {
          // When clearly asking for Groovy, that's what they'll get...
          readerClass = GroovyBeanDefinitionReader.class;
        }
        else {
          // Primarily ".xml" files but for any other extension as well
          readerClass = XmlBeanDefinitionReader.class;
        }
      }
      BeanDefinitionReader reader = readerInstanceCache.get(readerClass);
      if (reader == null) {
        try {
          // Instantiate the specified BeanDefinitionReader
          // 拿到入有一个入参为BeanDefinitionRegistry的构造函数~~
          reader = readerClass.getConstructor(BeanDefinitionRegistry.class).newInstance(this.registry);
          // Delegate the current ResourceLoader to it if possible
          if (reader instanceof AbstractBeanDefinitionReader) {
            AbstractBeanDefinitionReader abdr = ((AbstractBeanDefinitionReader) reader);
            abdr.setResourceLoader(this.resourceLoader);
            abdr.setEnvironment(this.environment);
          }
          readerInstanceCache.put(readerClass, reader);
        } catch (Throwable ex) {
          throw new IllegalStateException(
              "Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]");
        }
      }
      // TODO SPR-6310: qualify relative path locations as done in AbstractContextLoader.modifyLocations
      // 处理classpath:spring-beans.xml这种资源加载进来~~
      // 最终委托给的是`PathMatchingResourcePatternResolver`来加载这个资源,所以支持classpath*  也支持ant风格的通配符
      reader.loadBeanDefinitions(resource);
    });
  }
}


这样这个xml就会被解析完成,里面所有定义的Bean的定义信息就会被加载进容器里。


从源码中可以看出:默认情况下只支持导入xml格式的文件,并且要求遵循spring-beans.xsd。除非你在注解里可以自定义BeanDefinitionReader。它内置有三个实现类:

  1. PropertiesBeanDefinitionReader:一种简单的属性文件格式的bean definition解析器,提供以Map/Properties类型ResourceBundle类型定义的bean的注册方法。

 employee.(class)=MyClass       // bean is of class MyClass
 employee.(abstract)=true       // this bean can't be instantiated directly
 employee.group=Insurance       // real property
 employee.usesDialUp=false      // real property (potentially overridden)
 salesrep.(parent)=employee     // derives from "employee" bean definition
 salesrep.(lazy-init)=true      // lazily initialize this singleton bean
 salesrep.manager(ref)=tony     // reference to another bean
 salesrep.department=Sales      // real property
 techie.(parent)=employee       // derives from "employee" bean definition
 techie.(scope)=prototype       // bean is a prototype (not a shared instance)
 techie.manager(ref)=jeff       // reference to another bean
 techie.department=Engineering  // real property
 techie.usesDialUp=true         // real property (overriding parent value)
 ceo.$0(ref)=secretary          // inject 'secretary' bean as 0th constructor arg
 ceo.$1=1000000                 // inject value '1000000' at 1st constructor arg


这种方式我想说,其实我不想说什么~~~~~尴尬

2. GroovyBeanDefinitionReader:略

3. XmlBeanDefinitionReader:读取bean definition属性通过特定的xml文件。这个解析器在基于xml配置时候使用得非常之多,只是最终输给了时间几近被淘汰,此处也不用再举例了。


当然若都不满足你,你可以自己实现一个。(我相信99.99%都是没有必要的吧)。

需要特别注意的是:AnnotatedBeanDefinitionReader在基于注解的Spring项目中使用非常多,但它并不是BeanDefinitionReader的子类。它一般和ClassPathBeanDefinitionScanner一起使用


@ImportResource注解解释



// @since 3.0
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) // 它只能标注在类上
@Documented
public @interface ImportResource {
  // 路径支持${}这样动态取值~~~~   也支持ant风格的匹配  classpath*也是木有问题的
  @AliasFor("locations")
  String[] value() default {};
  @AliasFor("value")
  String[] locations() default {};
  // 上面说了,一般都不需要自定义,因为一般情况下我们都只会导入xml文件
  Class<? extends BeanDefinitionReader> reader() default BeanDefinitionReader.class;
}


需要特别注意的是,后缀名此处其实无所谓。比如你命名为spring-beans.txt也是没有问题的,但是需要保证里面的内容是xml格式的且遵循Spring Bean的schema:spring-beans.xsd就成~~ 这是需要注意的一点


关联知识

ConfigurationClassUtils里有这么一段代码:


abstract class ConfigurationClassUtils {
  static {
    candidateIndicators.add(Component.class.getName());
    candidateIndicators.add(ComponentScan.class.getName());
    candidateIndicators.add(Import.class.getName());
    candidateIndicators.add(ImportResource.class.getName());
  } 
}


可以看出标注为@ImportResource注解的Bean也会当作成一个配置类,只不过该配置类是Lite模式而已。关于什么叫Full模式什么叫Lite模式,他们有什么区别?请参考:

【小家Spring】Spring解析@Configuration注解的处理器:ConfigurationClassPostProcessor(ConfigurationClassParser)

相关文章
|
3月前
|
缓存 Java 开发者
【Spring】原理:Bean的作用域与生命周期
本文将围绕 Spring Bean 的作用域与生命周期展开深度剖析,系统梳理作用域的类型与应用场景、生命周期的关键阶段与扩展点,并结合实际案例揭示其底层实现原理,为开发者提供从理论到实践的完整指导。
526 22
|
3月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
1443 0
|
2月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
7月前
|
监控 安全 Java
Spring AOP实现原理
本内容主要介绍了Spring AOP的核心概念、实现机制及代理生成流程。涵盖切面(Aspect)、连接点(Join Point)、通知(Advice)、切点(Pointcut)等关键概念,解析了JDK动态代理与CGLIB代理的原理及对比,并深入探讨了通知执行链路和责任链模式的应用。同时,详细分析了AspectJ注解驱动的AOP解析过程,包括切面识别、切点表达式匹配及通知适配为Advice的机制,帮助理解Spring AOP的工作原理与实现细节。
1179 13
|
2月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
449 2
|
9月前
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
2224 1
|
4月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
3月前
|
XML Java 测试技术
使用 Spring 的 @Import 和 @ImportResource 注解构建模块化应用程序
本文介绍了Spring框架中的两个重要注解`@Import`和`@ImportResource`,它们在模块化开发中起着关键作用。文章详细分析了这两个注解的功能、使用场景及最佳实践,帮助开发者构建更清晰、可维护和可扩展的Java应用程序。
249 0
|
8月前
|
存储 人工智能 自然语言处理
RAG 调优指南:Spring AI Alibaba 模块化 RAG 原理与使用
通过遵循以上最佳实践,可以构建一个高效、可靠的 RAG 系统,为用户提供准确和专业的回答。这些实践涵盖了从文档处理到系统配置的各个方面,能够帮助开发者构建更好的 RAG 应用。
3717 113
|
7月前
|
前端开发 Java 数据库连接
Spring核心原理剖析与解说
每个部分都是将一种巨大并且复杂的技术理念传达为更易于使用的接口,而这就是Spring的价值所在,它能让你专注于开发你的应用,而不必从头开始设计每一部分。
226 32

热门文章

最新文章

推荐镜像

更多
  • DNS