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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 【小家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)

相关文章
|
23天前
|
存储 缓存 算法
HashMap深度解析:从原理到实战
HashMap,作为Java集合框架中的一个核心组件,以其高效的键值对存储和检索机制,在软件开发中扮演着举足轻重的角色。作为一名资深的AI工程师,深入理解HashMap的原理、历史、业务场景以及实战应用,对于提升数据处理和算法实现的效率至关重要。本文将通过手绘结构图、流程图,结合Java代码示例,全方位解析HashMap,帮助读者从理论到实践全面掌握这一关键技术。
72 13
|
2月前
|
运维 持续交付 云计算
深入解析云计算中的微服务架构:原理、优势与实践
深入解析云计算中的微服务架构:原理、优势与实践
81 1
|
1天前
|
Kubernetes Linux 虚拟化
入门级容器技术解析:Docker和K8s的区别与关系
本文介绍了容器技术的发展历程及其重要组成部分Docker和Kubernetes。从传统物理机到虚拟机,再到容器化,每一步都旨在更高效地利用服务器资源并简化应用部署。容器技术通过隔离环境、减少依赖冲突和提高可移植性,解决了传统部署方式中的诸多问题。Docker作为容器化平台,专注于创建和管理容器;而Kubernetes则是一个强大的容器编排系统,用于自动化部署、扩展和管理容器化应用。两者相辅相成,共同推动了现代云原生应用的快速发展。
24 10
|
16天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
9天前
|
存储 物联网 大数据
探索阿里云 Flink 物化表:原理、优势与应用场景全解析
阿里云Flink的物化表是流批一体化平台中的关键特性,支持低延迟实时更新、灵活查询性能、无缝流批处理和高容错性。它广泛应用于电商、物联网和金融等领域,助力企业高效处理实时数据,提升业务决策能力。实践案例表明,物化表显著提高了交易欺诈损失率的控制和信贷审批效率,推动企业在数字化转型中取得竞争优势。
50 14
|
23天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
78 14
|
17天前
|
网络协议 安全 网络安全
探索网络模型与协议:从OSI到HTTPs的原理解析
OSI七层网络模型和TCP/IP四层模型是理解和设计计算机网络的框架。OSI模型包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层,而TCP/IP模型则简化为链路层、网络层、传输层和 HTTPS协议基于HTTP并通过TLS/SSL加密数据,确保安全传输。其连接过程涉及TCP三次握手、SSL证书验证、对称密钥交换等步骤,以保障通信的安全性和完整性。数字信封技术使用非对称加密和数字证书确保数据的机密性和身份认证。 浏览器通过Https访问网站的过程包括输入网址、DNS解析、建立TCP连接、发送HTTPS请求、接收响应、验证证书和解析网页内容等步骤,确保用户与服务器之间的安全通信。
74 1
|
2月前
|
XML Java 数据库连接
Spring高手之路25——深入解析事务管理的切面本质
本篇文章将带你深入解析Spring事务管理的切面本质,通过AOP手动实现 @Transactional 基本功能,并探讨PlatformTransactionManager的设计和事务拦截器TransactionInterceptor的工作原理,结合时序图详细展示事务管理流程,最后引导分析 @Transactional 的代理机制源码,帮助你全面掌握Spring事务管理。
42 2
Spring高手之路25——深入解析事务管理的切面本质
|
1月前
|
Java 关系型数据库 数据库
京东面试:聊聊Spring事务?Spring事务的10种失效场景?加入型传播和嵌套型传播有什么区别?
45岁老架构师尼恩分享了Spring事务的核心知识点,包括事务的两种管理方式(编程式和声明式)、@Transactional注解的五大属性(transactionManager、propagation、isolation、timeout、readOnly、rollbackFor)、事务的七种传播行为、事务隔离级别及其与数据库隔离级别的关系,以及Spring事务的10种失效场景。尼恩还强调了面试中如何给出高质量答案,推荐阅读《尼恩Java面试宝典PDF》以提升面试表现。更多技术资料可在公众号【技术自由圈】获取。
|
2月前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
81 8

推荐镜像

更多
下一篇
开通oss服务