开发Java教程Spring后端开发Spring IoC容器深度解析:Bean的生命周期与容器机制Diebug2026年08月25日 # Spring IoC容器深度解析:Bean的生命周期与容器机制 > 本文基于DDKK.COM的Spring教程系列进行二次创作,原文链接:https://ddkk.com/category/j2ee/spring/1/index.html ## 3.1 Spring IoC容器和beans的介绍 本章涵盖了Spring框架实现控制反转(IoC)[1]的原理。IoC又叫依赖注入(DI)。它描述了对象的定义和依赖的一个过程,也就是说,依赖的对象通过构造参数、工厂方法参数或者属性注入,当对象实例化后依赖的对象才被创建,当创建bean后容器注入这些依赖对象。这个过程基本上是反向的,因此命名为控制反转(IoC),它通过直接使用构造类来控制实例化,或者定义它们之间的依赖关系,或者类似于服务定位模式的一种机制。 org.springframework.beans和org.springframework.context是Spring框架中IoC容器的基础,BeanFactory接口提供一种高级的配置机制能够管理任何类型的对象。ApplicationContext是BeanFactory的子接口。它能更容易集成Spring的AOP功能、消息资源处理(比如在国际化中使用)、事件发布和特定的上下文应用层比如在网站应用中的WebApplicationContext。 总之,BeanFactory提供了配置框架和基本方法,ApplicationContext添加更多的企业特定的功能。ApplicationContext是BeanFactory的一个子接口,在本章它被专门用于Spring的IoC容器描述。更多关于使用BeanFactory替代ApplicationContext的信息请参考章节 3.16, “The BeanFactory”。 在Spring中,由Spring IoC容器管理的对象叫做beans。 bean就是由Spring IoC容器实例化、组装和以其他方式管理的对象。此外bean只是你应用中许多对象中的一个。Beans以及他们之间的依赖关系是通过容器配置元数据反映出来。 ## 3.2 容器概述 org.springframework.context.ApplicationContext接口代表了Spring Ioc容器,它负责实例化、配置、组装之前的beans。容器通过读取配置元数据获取对象的实例化、配置和组装的描述信息。它配置的0元数据用xml、Java注解或Java代码表示。它允许你表示组成你应用的对象以及这些对象之间丰富的内部依赖关系。 Spring提供几个开箱即用的ApplicationContext接口的实现类。在独立应用程序中通常创建一个ClassPathXmlApplicationContext或FileSystemXmlApplicationContext实例对象。虽然XML是用于定义配置元数据的传统格式,你也可以指示容器使用Java注解或代码作为元数据格式,但要通过提供少量XML配置来声明启用对这些附加元数据格式的支持。 在大多数应用场景中,显示用户代码不需要实例化一个或多个Spring IoC容器的实例。比如在web应用场景中,在web.xml中简单的8行(或多点)样板式的xml配置文件就可以搞定(参见第3.15.4节“Web应用程序的便利的ApplicationContext实例化”)。如果你正在使用Eclipse开发环境中的Spring Tool Suite插件,你只需要鼠标点点或者键盘敲敲就能轻松搞定这几行配置。 下图是Spring如何工作的高级展示。你应用中所有的类都由元数据组装到一起 所以当ApplicationContext创建和实例化后,你就有了一个完全可配置和可执行的系统或应用。 Figure 5.1. Spring IoC容器 ## 3.2.1 配置元数据 如上图所示,Spring IoC容器使用了一种配置元数据的形式。此配置元数据表示应用程序的开发人员告诉Spring容器怎样去实例化、配置和装备你应用中的对象。 配置元数据传统上以简单直观的XML格式提供,本章大部分都使用这种格式来表达Spring IoC容器核心概念和特性。 基于XML的元数据不是允许配置元数据的唯一形式,Spring IoC容器与实际写入配置元数据的格式是分离的。这些天许多的开发者在他们的Spring应用中选择基于Java配置。 更多关于Spring容器使用其他形式的元数据信息,请查看: 基于注解配置: 在Spring2.5中有过介绍支持基于注解的配置元数据 基于Java配置: 从Spring3.0开始,由Spring JavaConfig提供的许多功能已经成为Spring框架中的核心部分。这样你可以使用Java程序而不是XML文件定义外部应用程序中的bean类。使用这些新功能,可以查看@Configuration,@Bean,@Import和@DependsOn这些注解 Spring配置由必须容器管理的一个或通常多个定义好的bean组成。基于XML配置的元数据中,这些bean通过标签定义在顶级标签内部。在Java配置中通常在使用@Configuration注解的类中使用@Bean注解方法。 这些bean的定义所对应的实际对象就组成了你的应用。通常你会定义服务层对象,数据访问层对象(DAO),展现层对象比如Struts的Action实例,底层对象比如Hibernate的SessionFactories,JMSQueues等等。通常在容器中不定义细粒度的域对象,因为一般是由DAO层或者业务逻辑处理层负责创建和加载这些域对象。但是,你可以使用Spring集成Aspectj来配置IoC容器管理之外所创建的对象。详情请查看Spring使用AspectJ依赖注入域对象 接下来这个例子展示了基于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="..." class="..."> <!-- 在这里写 bean 的配置和相关引用 --> </bean> <bean id="..." class="..."> <!-- 在这里写 bean 的配置和相关引用 --> </bean> <!-- 在这里配置更多的bean --> </beans> id属性用来使用标识每个独立的bean定义的字符串。class属性定义了bean的类型,这个类型必须使用全路径类名(必须是包路径+类名)。id属性值可以被依赖对象引用。该例中没有体现XML引用其他依赖对象。更多请查看bean的依赖。 ## 3.11 使用JSR 330标准注解 ## 3.13 环境抽象 在应用环境中,集成在容器的抽象环境模型有两个方面:profiles和properties。只有给出的profile被激活,一组逻辑命名的bean定义才会在容器中注册。无论是在XML中或者通过注解,bean都会被分配给一个profile。环境变量对象角色和profiles的关系来决定哪个profiles(如果有)处于当前激活状态,哪个profiles默认被激活。几乎在所有的应用中,Properties都扮演了一个重要的对象,这可能有各种来源:属性文件, JVM 系统属性文件,系统环境变量,JNDI,servlet上下文参数,属性查询对象,Maps等等。环境变量对象的角色和properties的关系用于配置属性并从中解析属性提供给用户一个便捷的服务接口。 ## 3.13.1 Bean的profiles定义 Bean定义profiles是在核心容器中允许不同的bean在不同环境注册的机制。环境对于不同的用户意味着不同的东西,这个特性可以帮助许多用例,包括: 在开发中不使用内存中的数据源 VS 在质量测试或生产环境中从JNDI查找相同的数据源。 当把应用部署在可执行的环境中时注册监控基础架构 对于客户A注册的自定义实现VS.客户B部署 让我首先考虑在一个需要数据源的应用中使用这个例子。在测试环境中,配置可能如下: @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("my-schema.sql") .addScript("my-test-data.sql") .build(); } 让我现在考虑一下,如何把这个应用部署在测试环境或者生产环境中,假设应用所需的数据源将会被注册在生产应用环境中的JNDI目录。现在我们的数据源bean看起来像这样: @Bean(destroyMethod="") public DataSource dataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); } 问题就是如何根据当前的环境在这两种变量之间进行切换。随着时间的推移,Spring的用户已经设计了很多种方法来实现此功能,通常依赖于系统环境变量和包含${placeholder}的XML 语句,根据环境变量的值可以解决正确的文件路径配置。Bean定义profiles是容器为了解决这个问题而提供的一个核心功能。 如果我们概括一下上面bean定义环境变量的示例,我们最终需要在特定的上下文中注册特定的bean,而不是其他的。你可以说你想要在情形A中注册一个特定的Bean定义的profile,在情形B中是另外一个。我们首先看下如何更新我们的配置以反映这种需求。 当一个或者多个特定的profiles被激活,@Profile注解允许你指定一个有资格的组件来注册。使用我们上面的例子,我们可以按照下面的重写dataSource配置: @Configuration @Profile("dev") public class StandaloneDataConfig { @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("classpath:com/bank/config/sql/schema.sql") .addScript("classpath:com/bank/config/sql/test-data.sql") .build(); } } @Configuration @Profile("production") public class JndiDataConfig { @Bean(destroyMethod="") public DataSource dataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); } } 如前所述,使用@Bean方法,通常会选择使用程序话的JNDI查找:要么使用Spring的 JndiTemplate/JndiLocatorDelegate帮助要么直接使用上面展示的JNDI InitialContext,而不是强制声明返回类型为FactoryBean的JndiObjectFactoryBean变体。 @Profile可以被用作为创建一个自定义组合注解的元注解。下面的例子定义了一个@Production注解,它可以被用作替换@Profile(“production”)的注解。 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Profile("production") public @interface Production { } 在仅仅包含一个特殊bean的配置类中,@Profile也可以被声明在方法级别: @Configuration public class AppConfig { @Bean @Profile("dev") public DataSource devDataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("classpath:com/bank/config/sql/schema.sql") .addScript("classpath:com/bank/config/sql/test-data.sql") .build(); } @Bean @Profile("production") public DataSource productionDataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); } } 如果一个@Configuration类被标记为@Profile,那么所有的@Bean方法和@Import注解相关的类都会被忽略,除非一个或多个特别的profiles被激活。如果一个@Component或@Configuration类被标记为@Profile({“p1”, “p2”}),那么这个类将不会被注册/处理,除非被标记为’p1’和/或’p2’的profiles已经被激活。如果给出的profile的前缀带有取反的操作符(!),那么注解的元素将会被注册,除非这个profile没有被激活。例如,给出@Profile({“p1”, “!p2”}),如果profile ‘p1’是激活状态或者profile ‘p2’不是激活状态的时候才会注册。 ## 3.13.2 XML bean 定义 profiles XML对应元素的profile属性。我们上面的示例配置可以重写为下面的两个XML配置: <beans profile="dev" xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xsi:schemaLocation="..."> <jdbc:embedded-database id="dataSource"> <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/> <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/> </jdbc:embedded-database> </beans> <beans profile="production" xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="..."> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> </beans> 也可以避免在同一个文件中分割和嵌套元素: <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="..."> <!-- other bean definitions --> <beans profile="dev"> <jdbc:embedded-database id="dataSource"> <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/> <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/> </jdbc:embedded-database> </beans> <beans profile="production"> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> </beans> </beans> spring-bean.xsd 约束允许这样的元素仅作为文件中的最后一个元素。这有助于XML的灵活性,且不会产生混乱。 现在我们已经更新了我们的配置,我们仍然需要说明哪个profile是激活的。如果我们现在开始我们示例应用程序,我们将会看到一个NoSuchBeanDefinitionException被抛出,因为容器找不到一个名为dataSource的Spring bean。 激活一个profile可以通过多种方式完成,但是大多数情况下,最直接的办法就是通过存在ApplicationContext当中的环境变量的API进行编程: AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.getEnvironment().setActiveProfiles("dev"); ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class); ctx.refresh(); 除此之外,profiles也可以通过声明spring.profiles.active属性来激活,这个可以通过在系统环境变量,JVM系统属性,web.xml中的servlet上下文环境参数,甚至JNDI的入口(请参考 3.13.3, “PropertySource abstraction”)。在集成测试中,激活profiles可以通过在Spring-test模块中的@ActiveProfiles注解来声明(参见“使用profiles来配置上下文环境”章节)。 注意,profiles不是“二者选一”的命题;它可以一次激活多个profiles。以编程的方式来看,简单的传递多个profile名字给接受String 可变变量参数的setActiveProfiles()方法: ctx.getEnvironment().setActiveProfiles("profile1", "profile2"); 在声明式中,spring.profiles.active可以接受以逗号分隔的profile 名称列表: -Dspring.profiles.active="profile1,profile2" 默认的profile 默认配置文件表示默认启用的配置文件。考虑以下几点: @Configuration @Profile("default") public class DefaultDataConfig { @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("classpath:com/bank/config/sql/schema.sql") .build(); } } 如果没有profile是激活状态,上面的dataSource将会被创建;这种方式可以被看做是对一个或者多个bean提供了一种默认的定义方式。如果启用任何的profile,那么默认的profile都不会被应用。 在上下文环境可以使用setDefaultProfiles()或者spring.profiles.default属性来修改默认的profile名字。 ## 3.13.3 属性源抽象 Spring 环境抽象提供了可配置的属性源层次结构的搜索操作。为了充分的解释,请考虑下面的例子: ApplicationContext ctx = new GenericApplicationContext(); Environment env = ctx.getEnvironment(); boolean containsFoo = env.containsProperty("foo"); System.out.println("Does my environment contain the 'foo' property? " + containsFoo); 在上面的代码段中,我们看到了一个高级别的方法来要求Spring是否为当前环境定义foo属性。为了回答这个问题,环境对象对一组PropertySource对象执行搜索。一个PropertySource是对任何key-value资源的简单抽象,并且Spring 的标准环境是由两个PropertySource配置的,一个表示一系列的JVM 系统属性(System.getProperties()),一个表示一系列的系统环境变量(System.getenv())。 这些默认的属性资源存在于StandardEnvironment,可以在应用中独立使用。StandardServletEnvironment包含其他默认的属性资源,包括servlet配置和servlet上下文参数。它可以选择性的启用JndiPropertySource。详细信息请查看javadocs。 具体的说,当使用StandardEnvironment时,如果在运行时系统属性或者环境变量中包括foo,那么调用env.containsProperty(“foo”)方法将会返回true。 搜索是按照层级执行的。默认情况,系统属性优先于环境变量,所以这两个地方同时存在属性foo的时候,调用env.getProperty(“foo”)将会返回系统属性中的foo值。注意,属性值不会被合并而是被之前的值覆盖。对于一个普通的StandardServletEnvironment,它完整的层次结构如下,最顶端的优先级最高: ServletConfig参数(如果适用,例如DispatcherServlet上下文环境) ServletContext参数(web.xml中的context-param) JNDI环境变量(“java:comp/env/”) JVM系统属性(“-D”命令行参数) JVM系统环境变量(操作系统环境变量) 更重要的是,整个机制都是可配置的。也许你有个自定义的属性来源,你想把它集成到这个搜到里面。这也没问题,只需简单的实现和实例化自己的PropertySource,并把它添加到当前环境的PropertySources集合中: ConfigurableApplicationContext ctx = new GenericApplicationContext(); MutablePropertySources sources = ctx.getEnvironment().getPropertySources(); sources.addFirst(new MyPropertySource()); 在上面的代码中,MyPropertySource被添加到搜索中的最高优先级。如果它包含了一个foo属性,在任何其他的PropertySource中的foo属性之前它会被检测到并返回。MutablePropertySources API暴露了很多允许精确操作该属性源集合的方法。 ## 3.13.4 @PropertySource @PropertySource注解对添加一个PropertySource到Spring的环境变量中提供了一个便捷的和声明式的机制。 给出一个名为”app.properties”的文件,它含了testbean.name=myTestBean的键值对,下面的@Configuration类使用@PropertySource的方式来调用testBean.getName(),将会返回”myTestBean”。 @Configuration @PropertySource("classpath:/com/myco/app.properties") public class AppConfig { @Autowired Environment env; @Bean public TestBean testBean() { TestBean testBean = new TestBean(); testBean.setName(env.getProperty("testbean.name")); return testBean; } } 任何出现在@PropertySource中的资源位置占位符都会被注册在环境变量中的资源解析。 @Configuration @PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties") public class AppConfig { @Autowired Environment env; @Bean public TestBean testBean() { TestBean testBean = new TestBean(); testBean.setName(env.getProperty("testbean.name")); return testBean; } } 假设”my.placeholder”已经在其中的一个资源中被注册,例如:系统属性或环境变量,占位符将会被正确的值解析。如果没有,”default/path”将会使用默认值。如果没有默认值,而且无法解释属性,则抛出IllegalArgumentException异常。 ## 3.13.5 声明中的占位符解决方案 以前,元素中占位符的值只能被JVM系统熟悉或者环境变量解析。现在已经解决了这种情况。因为抽象的环境已经通过容器被集成了,很容易通过它来分配占位符。这意味着你可以使用任何你喜欢的方式配置:可以通过系统属性和环境变量来改变搜索优先级,或者完全删除它们;可以以适当的方式添加你自己混合属性资源。 具体来说,下面的声明无论customer属性被定义在哪里,只要它存在环境变量中就有作用: ## 3.14 注册一个加载时编织器 在类被加载进JVM时Spring使用LoadTimeWeaver进行动态转换。 为了使的load-time weaving可用,那么你只需在配置了@Configuration的类上添加@EnableLoadTimeWeaving。 @Configuration @EnableLoadTimeWeaving public class AppConfig { } 相应的xml配置使用context:load-time-weaver元素: <beans> <context:load-time-weaver/> </beans> 一旦配置了ApplicationContext,那么在ApplicationContext中的任何bean都可以实现LoadTimeWeaverAware,从而接受对类加载时编织器实例的引用。这与Spring JPA支持相结合时非常有用,JPA类转化必须使用加载时编织。可以通过javadocs的LocalContainerEntityManagerFactoryBean获取更多详细信息,对于AspectJ加载时的编织请参考: Section 7.8.4, “Load-time weaving with AspectJ in the Spring Framework”. ## 3.15 ApplicationContext的额外功能 正如本章开头所讨论的那样,org.springframework.beans.factory包提供基本的功能来管理和操作bean,包括以编程的方式。The org.springframework.context包增加了ApplicationContext接口,它继承了BeanFactory接口,除了以面向应用框架的风格扩展接口来提供一些额外的功能。很多人以完全声明的方式使用ApplicationContext,甚至没有以编程的方式去创建它,而是依赖诸如ContextLoader等支持类来自动的实例化ApplicationContext,作为Java EE web应用程序正常启动的一部分。 为了增强BeanFactory在面向框架风格的功能,上下文的包还提供了以下的功能: 通过MessageSource接口访问i18n风格的消息 通过ResourceLoader接口访问类似URL和文件资源 通过ApplicationEventPublisher接口,即bean实现ApplicationListener接口来进行事件发布 通过HierarchicalBeanFactory接口实现加载多个(分层)上下文,允许每个上下文只关注特定的层,例如应用中的web层 ## 3.15.1 使用MessageSource 国际化 ApplicationContext接口继承了一个叫做MessageSource的接口,因此它也提供了国际化(i18n)的功能。Spring也提供了HierarchicalMessageSource接口,它可以分层去解析信息。这些接口共同为Spring消息效应解析提供了基础。这些接口上定义的方法包括: String getMessage(String code, Object[] args, String default, Locale loc): 这个基础的方法用来从MessageSource检索消息。当指定的区域中没有发现消息时,将使用默认的。任何参数传递都将使用标准库提供的MessageFormat变成替换值。 String getMessage(String code, Object[] args, Locale loc): 本质上和前面提供的方法相同,只有一个区别,就是当没有指定消息,又没有发现消息,将会抛出NoSuchMessageException 异常。 String getMessage(MessageSourceResolvable resolvable, Locale locale): 所有的属性处理方法都被包装在一个名为MessageSourceResolvable的类中,你可以使用此方法。 当ApplicationContext被载入的时候,它会自动的在上下文中去搜索定义的MessageSource bean。这个bean必须有messageSource的名称。如果找到这么一个bean,所有上述方法的调用都会委托给消息源。如果没有发现消息源,ApplicationContext会尝试寻找一个同名的父消息源。如果是这样,它会将那个bean作为MessageSource。如果ApplicationContext没有找到任何的消息源,那么一个空的DelegatingMessageSource将被实例化,以便能够接受到对上述定义方法的调用。 Spring提供了ResourceBundleMessageSource和StaticMessageSource两个MessageSource实现。它们两个都实现了HierarchicalMessageSource以便处理嵌套消息。StaticMessageSource很少使用,但是它提供了通过编程的方式增加消息源。下面展示ResourceBundleMessageSource使用的例子: <beans> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basenames"> <list> <value>format</value> <value>exceptions</value> <value>windows</value> </list> </property> </bean> </beans> 在上面的例子中,假设在类路径下定义了format,exceptions和windows三个资源包。解析消息的任何请求都会通过ResourceBundles被JDK以标准方式处理。为了举例说明,假设上述两个资源包的文件内容是… # in format.properties message=Alligators rock! # in exceptions.properties argument.required=The {0} argument is required. 下面的实例展示了执行MessageSource功能的程序。记住所有的ApplicationContext的实现也是MessageSource的实现,而且它可以被强转为MessageSource接口。 public static void main(String[] args) { MessageSource resources = new ClassPathXmlApplicationContext("beans.xml"); String message = resources.getMessage("message", null, "Default", null); System.out.println(message); } 上面的程序输出的结果为: Alligators rock! 所以总结一下,MessageSource是定义在一个名为beans.xml,它存在类路径的跟目录下。messageSource bean定义通过basenames属性引用了很多的资源。在列表中传递给basenames属性的三个文件作为类路径下根目录中的文件存在,分别为format.properties, exceptions.properties, and windows.properties。 下一个例子展示传递给消息查找的参数;这些参数将会被转换为字符串并插入到消息查找的占位符中。 <beans> <!-- this MessageSource is being used in a web application --> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="exceptions"/> </bean> <!-- lets inject the above MessageSource into this POJO --> <bean id="example" class="com.foo.Example"> <property name="messages" ref="messageSource"/> </bean> </beans> public class Example { private MessageSource messages; public void setMessages(MessageSource messages) { this.messages = messages; } public void execute() { String message = this.messages.getMessage("argument.required", new Object [] {"userDao"}, "Required", null); System.out.println(message); } } 调用execute()方法的输出结果为: TheuserDao argument is required. 关于国际化(i18n),Spring的各种MessageSource实现遵循与标准JDK ResourceBundle相同的语言环境和回退规则。简而言之,并继续用前面messageSource 为例,如果你想根据英国(en-GB)解析消息,你可以创建这些文件format_en_GB.properties,exceptions_en_GB.properties, and windows_en_GB.properties。 通常,地域设置通过应用周围环境管理的。在此示例中,手动指定对(英国)区域消息进行解析。 # in exceptions_en_GB.properties argument.required=Ebagum lad, the {0} argument is required, I say, required. public static void main(final String[] args) { MessageSource resources = new ClassPathXmlApplicationContext("beans.xml"); String message = resources.getMessage("argument.required", new Object [] {"userDao"}, "Required", Locale.UK); System.out.println(message); } 上面的程序运行输出为: Ebagum lad, the ‘userDao’ argument is required, I say, required. 你也可以使用MessageSourceAware接口来获取对已定义MessageSource的引用。任何在ApplicationContext定义的bean都会实现MessageSourceAware,当bean被创建或者配置的时候,它会在应用上下文的MessageSource中被被注入。 作为ResourceBundleMessageSource的替代方法,Spring提供了一个ReloadableResourceBundleMessageSource类。这个变体支持同样打包文件格式,但是它更灵活而不是标准JDK基于ResourceBundleMessageSource的实现。特别的,它允许从任何Spring 资源位置读取文件(不仅仅是从类路径)而且还支持属性文件热加载(同时高效缓存他们)。ReloadableResourceBundleMessageSource的详细信息参考javadocs。 ## 3.15.2 标准和自定义事件 ApplicationEvent类和ApplicationListener接口提供了ApplicationContext中的事件处理。如果一个bean实现了ApplicationListener接口,然后它被部署到上下问中,那么每次ApplicationEvent发布到ApplicationContext中时,bean都会收到通知。本质上,这是观察者模型。 从Spring 4.2开始,事件的基础得到了重要的提升,并提供了基于注解模型及任意事件发布的能力,这个对象不一定非要继承ApplicationEvent。当这个对象被发布时,我们把他包装在事件中。 Spring提供了一下的标准事件: 你可以创建并发布自己的自定义事件。这个例子演示了一个继承Spring ApplicationEvent的简单类: public class BlackListEvent extends ApplicationEvent { private final String address; private final String test; public BlackListEvent(Object source, String address, String test) { super(source); this.address = address; this.test = test; } // accessor and other methods... } 为了发布一个自定义的ApplicationEvent,在ApplicationEventPublisher中调用publishEvent()方法。通常在实现了ApplicationEventPublisherAware接口并把它注册为一个Spring bean的时候它就完成了。下面的例子展示了这么一个类: public class EmailService implements ApplicationEventPublisherAware { private List<String> blackList; private ApplicationEventPublisher publisher; public void setBlackList(List<String> blackList) { this.blackList = blackList; } public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { this.publisher = publisher; } public void sendEmail(String address, String text) { if (blackList.contains(address)) { BlackListEvent event = new BlackListEvent(this, address, text); publisher.publishEvent(event); return; } // send email... } } 在配置时,Spring容器将检测到EmailService实现了ApplicationEventPublisherAware,并将自动调用setApplicationEventPublisher()方法。实际上,传入的参数将是Spring容器本身;您只需通过ApplicationEventPublisher接口与应用程序上下文进行交互。 为了自定义ApplicationEvent,创建一个试下了ApplicationListener的类并把他注册为一个Spring bean。下面例子展示这样一个类: public class BlackListNotifier implements ApplicationListener<BlackListEvent> { private String notificationAddress; public void setNotificationAddress(String notificationAddress) { this.notificationAddress = notificationAddress; } public void onApplicationEvent(BlackListEvent event) { // notify appropriate parties via notificationAddress... } } 请注意,ApplicationListener通常用你自定义的事件BlackListEvent类型参数化的。这意味着onApplicationEvent()方法可以保持类型安全,避免向下转型的需要。您可以根据需要注册许多的事件侦听器,但请注意,默认情况下,事件侦听器将同步接收事件。这意味着publishEvent()方法会阻塞直到所有的监听者都处理完。这种同步和单线程方法的一个优点是,如果事务上下文可用,它就会在发布者的事务上下文中处理。如果必须需要其他的时间发布策略,请参考javadoc的 Spring ApplicationEventMulticaster 接口。 下面例子展示了使用配置和注册上述每个类的bean定义: <bean id="emailService" class="example.EmailService"> <property name="blackList"> <list> <value>known.spammer@example.org</value> <value>known.hacker@example.org</value> <value>john.doe@example.org</value> </list> </property> </bean> <bean id="blackListNotifier" class="example.BlackListNotifier"> <property name="notificationAddress" value="blacklist@example.org"/> </bean> 把他们放在一起,当调用emailService的sendEmail()方法时,如果有任何应该被列入黑名单的邮件,那么自定义的BlackListEvent事件会被发布。blackListNotifier 会被注册为一个ApplicationListener,从而接受BlackListEvent,届时通知适当的参与者。 Spring 的事件机制的设计是用在Spring bean和相同应用上下文的简单通讯。然而,对于更复杂的企业集成需求,单独维护Spring Integration工程对构建著名的Spring编程模型轻量级,面向模式,事件驱动架构提供了完整的支持。 从Spring 4.2开始,一个事件监听器可以通过EventListener注解注册在任何managed bean的公共方法上。BlackListNotifier可以重写如下: public class BlackListNotifier { private String notificationAddress; public void setNotificationAddress(String notificationAddress) { this.notificationAddress = notificationAddress; } @EventListener public void processBlackListEvent(BlackListEvent event) { // notify appropriate parties via notificationAddress... } } 如上所示,方法签名实际上会推断出它监听的是哪一个类型的事件。这也适用于泛型嵌套,只要你在过滤的时候可以根据泛型参数解析出实际的事件。 如果你的方法需要监听好几个事件或根本没有参数定义它,事件类型也可以用注解本身指明: @EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class}) public void handleContextStart() { } 对特殊的时间调用方法,根据定义的SpEL表达式来匹配实际情况,通过条件属性注解, 也可以通过condition注解来添加额外的运行过滤,它对一个特殊事件的方法实际调用是根据它是否匹配condition注解所定义的SpEL表达式。 例如,只要事件的测试属性等于foo,notifier可以被重写为只被调用: @EventListener(condition = "#blEvent.test == 'foo'") public void processBlackListEvent(BlackListEvent blEvent) { // notify appropriate parties via notificationAddress... } 每个SpEL表达式在此评估专用的上下文。下表列出的条目存在上下文中可用,所以可以调用他们处理conditional事件: 表3.8. 存在元数据中的Event SpEL 表达式 注意,#root.event允许你访问底层的时间,即使你的方法签名实际上是指已发布的任意对象。 如果您需要发布一个事件作为处理另一个事件的结果,只需更改方法签名来返回应该被发布的事件,如下所示: @EventListener public ListUpdateEvent handleBlackListEvent(BlackListEvent event) { // notify appropriate parties via notificationAddress and // then publish a ListUpdateEvent... } 异步监听器不支持这个特性 这个新方法将对上述方法处理的每个BlackListEvent都会发布一个新的ListUpdateEvent。如果需要发布多个时间,只需要返回事件集合即可。 如果你希望一个特定的监听器去异步处理事件,只需要重新使用常规的@Async支持: @EventListener @Async public void processBlackListEvent(BlackListEvent event) { // BlackListEvent is processed in a separate thread } 当使用异步事件的时候有下面两个限制: 1、 如果事件监听器抛出异常,则不会将其传播给调用者,查看AsyncUncaughtExceptionHandler获取详细信息; 2、 此类事件监听器无法发送回复如果你需要将处理结果发送给另一个时间,注入ApplicationEventPublisher里面手动发送事件; 如果你需要一个监听器在另一个监听器调用前被调用,只需要在方法声明上添加@Order注解: @EventListener @Order(42) public void processBlackListEvent(BlackListEvent event) { // notify appropriate parties via notificationAddress... } 泛型事件 你可以使用泛型来进一步的定义事件的结构。考虑EntityCreatedEvent,T的类型就是你要创建的真实类型。你可以创建下面的监听器定义,它只接受Person类型的EntityCreatedEvent: @EventListener public void onPersonCreated(EntityCreatedEvent<Person> event) { ... } 触发了事件解析泛型参数, 由于类型擦除,只有在触发了事件解析事件监听过器滤的泛型参数(类似于PersonCreatedEvent继承了EntityCreatedEvent { … }),此操作才会起作用。 在某些情况下,如果所有的时间都遵循相同的结果(上述事件应该是这样),这可能有点冗余。在这种情况下,你可以实现ResolvableTypeProvider来引导超出框架运行是环境提供的范围: public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider { public EntityCreatedEvent(T entity) { super(entity); } @Override public ResolvableType getResolvableType() { return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource())); } } 这不仅适用于ApplicationEvent,还可以作为时间发送的任意对象。 ## 3.15.3 便捷访问低优先级的资源 为了最佳使用和理解上下文,用户应该熟悉Spring资源抽象,如章节:章节4,资源。 一个应用上下文就是一个ResourceLoader,它可以用来载入资源。一个资源本质上讲就是JDK类java.net.URL功能更丰富的版本。实际上,Resource的实现在合适的地方包装了一个java.net.URL实例。资源可以以透明的方式从任何位置获得获取低优先级的资源,包括一个标准的URL,本地文件系统,任何描述标准URL的地方,其他的一些扩展。如果资源位置的字符串是一个没有任何特殊字符前缀的简单路径,那么这些资源就来自特定的并适合实际应用程序上下文类型。 你可以将bean的配置部署到一个实现了应用上下文的特殊回调接口ResourceLoaderAware中,以便在初始化的时候应用上下文把自己作为ResourceLoader传递进去可以自动调用。你也可以暴露资源的属性类型,用于访问静态资源;它们像其他属性一样会被注入。你可以像字符串路径一样指定这些资源的属性,并依赖自动注入上下文的特殊JavaBean的属性编辑器,以便在部署bean时将这些文本字符串转换为真实的对象。 提供给ApplicationContext 构造器的位置路径或路径都是真实的资源字符串,并以简单的形式对特定的上下文进行了适当的处理。ClassPathXmlApplicationContext将简单的路径作为类路径的位置。你也可以使用特殊前缀的位置路径(资源字符串)强制从类路径或者URL加载定义信息,而不管实际的上下文类型。 ## 3.15.4 便捷的ApplicationContext 实例化web 应用程序 你可以通过声明式创建ApplicationContext的实例,例如,ContextLoader。当然你也可以通过使用一个ApplicationContext的实现用编程的方式创建ApplicationContext的实例。 你可以像下面一样通过ContextLoaderListener来注册一个ApplicationContext: <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> 监听器会检查contextConfigLocation的参数。如果参数不存在,监听器默认会使用/WEB-INF/applicationContext.xml。当参数存在的是,监听器会通过预定义的分隔符来(逗号,分号和空格)来分隔字符串,并将其作为应用程序上下文的搜索位置。它也支持Ant风格的路径模式。例如:/WEB-INF/*Context.xml,在WEB-INF目录下,/WEB-INF/**/*Context.xml,WEB-INF子目录的所有这样类似的文件都会被发现。 ## 3.15.5 Spring ApplicationContext 作为Java EE RAR文件部署 可以将Spring ApplicationContext部署为RAR文件,将上下文及其所有必需的bean类和库JAR封装在Java EE RAR部署单元中。这相当于引导一个独立的ApplicationContext,只是托管在Java EE环境中,能够访问Java EE服务器设施。 在部署无头WAR文件(实际上,没有任何HTTP入口点,仅用于在Java EE环境中引导Spring ApplicationContext的WAR文件)的情况下RAR部署是更自然的替代方案。 RAR部署非常适合不需要HTTP入口点但仅由消息端点和调度作业组成的应用程序上下文。在这种情况下,Bean可以使用应用程序服务器资源,例如JTA事务管理器和JNDI绑定的JDBC DataSources和JMS ConnectionFactory实例,并且还可以通过Spring的标准事务管理和JNDI和JMX支持设施向平台的JMX服务器注册。应用程序组件还可以通过Spring的TaskExecutor抽象实现与应用程序服务器的JCA WorkManager交互。 通过查看 SpringContextResourceAdapter类的JavaDoc,可以知道用于RAR部署中涉及的配置详细信息。 对于Spring ApplicationContext作为Java EE RAR文件的简单部署:将所有应用程序类打包到RAR文件中,这是具有不同文件扩展名的标准JAR文件。将所有必需的库JAR添加到RAR归档的根目录中。添加一个“META-INF / ra.xml”部署描述符(如SpringContextResourceAdapter的JavaDoc中所示)和相应的Spring XML bean定义文件(通常为“META-INF / applicationContext.xml”),导致RAR文件进入应用程序服务器的部署目录。 [Note] 这种RAR部署单元通常是独立的; 它们不会将组件暴露给外界,甚至不会暴露给同一应用程序的其他模块。 与基于RAR的ApplicationContext的交互通常通过发生在与其他模块共享的JMS目标的情况下。 基于RAR的ApplicationContext还会在其他情况下使用,例如调度一些作业,对文件系统中的新文件(等等)作出反应。 如果需要允许从外部同步访问,它可以做到如导出RMI端点,然后很自然的可以由同一机器上的其他应用模块使用 可以将Spring ApplicationContext部署为RAR文件,将上下文和所有他所需的bean的类和JAR库封装在Java EE RAR部署单元中。这相当于独立启动一个ApplicationContext,它在Java EE环境中可以访问Java EE服务资源。RAR部署在一些没用头信息的war文件中更自然的选择,实际上,一个war文件在没有http入口的时候,那么它就仅仅是用来在Java EE环境中启动Spring ApplicationContext。 对于不需要HTTP入口点的应用上下文来说RAR部署是一种理想的方式,而不仅是一些消息端点和计划的任务。Bean在这样的上下文中可以使用应用服务器的资源,例如:JTA事务管理器、JNDI-bound JDBC DataSources 和JMS连接工厂实例,也可以通过Spring标准事务管理器、JNDI和JMX支持来注册平台的JMX服务。应用组件也可以通过Spring 抽象TaskExecutor来和应用服务器的JCA WorkManager来进行交互。 有关RAR部署中涉及的配置详细信息请查看 JavaDoc中的SpringContextResourceAdapter。 Spring ApplicationContext 作为Java EE RAR文件的简单部署:将所有的应用类打包进一个RAR文件,它和标准的JAR文件有不同的文件扩展名。将所有需要的库JAR文件添加到RAR归档的根目录中。添加一个”META-INF/ra.xml”部署描述文件(如SpringContextResourceAdapters JavaDoc所示),并更改Spring XML中bean的定义文件(通常为“META-INF / applicationContext.xml”),将生成的rar文件放到音符服务器的部署目录。 这种RAR部署单元通常是独立的;它们不会将组建暴露给外界,甚至是同一个应用的其他模块。同基于RAR的ApplicationContext交互通常是通过模块共享的JMS来实现的。一个基于RAR的应用上下文,例如:某些调度任务,文件系统对新文件产生的响应等。如果要允许外界的同步访问,则可以导出RMI端点,这当然可能是同一台机器上的其他应用模块。 ## 3.16 BeanFactory BeanFactory为Spring的IoC功能提供了底层的基础,但是它仅仅被用于和第三方框架的集成,现在对于大部分的Spring用户来说都是历史了。BeanFactory及其相关的接口,例如:BeanFactoryAware,InitializingBean,DisposableBean,在Spring中仍然有所保留,目的就是为了让大量的第三方框架和Spring集成时保持向后兼容。通常第三方组件不会更加现代的等价物,例如:@PostConstruct 或 @PreDestroy,以便可以与JDK1.4兼容,或避免依赖JSR-250。 这部分提供了BeanFactory 和 ApplicationContext之间的背景差异以及用户怎样通过查找单例的模式来访问IoC容器。 ## 3.16.1 BeanFactory or ApplicationContext? 尽量使用ApplicationContext除非你有更好的理由不用它。 因为ApplicationContext包括了BeanFactory的所有功能,通常也优于BeanFactory,除非一些少数的场景,例如:在受资源约束的嵌入式设备上运行一个嵌入式应用,它的内存消耗可能至关重要,并且可能会产生字节。然而,对于大多数典型的企业级应用和系统来说,ApplicationContext才是你想使用的。Spring大量使用了BeanPostProcessor扩展点(以便使用代理等)。如果你仅仅只使用简单的BeanFactory,很多的支持功能将不会有效,例如:事务和AOP,但至少不会有额外的步骤。这可能会比较迷惑,毕竟配置又没有错。 下表列了BeanFactory 和 ApplicationContext接口和实现的一些特性: 用BeanFactory的实现来明确的注册一个bean的后置处理器,你需要写和下面类似的代码: DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); // populate the factory with bean definitions // now register any needed BeanPostProcessor instances MyBeanPostProcessor postProcessor = new MyBeanPostProcessor(); factory.addBeanPostProcessor(postProcessor); // now start using the factory 当使用一个BeanFactory的实现来明确的注册一个BeanFactoryPostProcessor时,你写的代码必须和下面类似: DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(new FileSystemResource("beans.xml")); // bring in some property values from a Properties file PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer(); cfg.setLocation(new FileSystemResource("jdbc.properties")); // now actually do the replacement cfg.postProcessBeanFactory(factory); 在这两种情况下,明确的注册步不是很方便,这也就是为什么在大多数支持Spring的应用中,ApplicationContext的各种实现都优于BeanFactory实现的原因之一,特别是当使用BeanFactoryPostProcessors和BeanPostProcessors的时候。这些机制实现了一些很重要的功能,例如:属性的占位替换和AOP。 ## 16、Spring源码分析 - 16-AutowiredAnnotationBeanPostProcessor AutowiredAnnotationBeanPostProcessor继承了InstantiationAwareBeanPostProcessorAdapter间接实现了SmartInstantiationAwareBeanPostProcessor接口覆盖了determineCandidateConstructors()方法,此方法在实例化bean前选取一个构造方法数组,请参考bean创建的流程/zhuanlan/j2ee/spring/7/11.html和/zhuanlan/j2ee/spring/7/12.html 先看determineCandidateConstructors()方法的前一小段是处理@Lookup注解的。 if (!this.lookupMethodsChecked.contains(beanName)) { try { ReflectionUtils.doWithMethods(beanClass, method -> { Lookup lookup = method.getAnnotation(Lookup.class); if (lookup != null) { Assert.state(beanFactory != null, "No BeanFactory available"); LookupOverride override = new LookupOverride(method, lookup.value()); try { RootBeanDefinition mbd = (RootBeanDefinition) beanFactory.getMergedBeanDefinition(beanName); mbd.getMethodOverrides().addOverride(override); } catch (NoSuchBeanDefinitionException ex) { throw new BeanCreationException(beanName, "Cannot apply @Lookup to beans without corresponding bean definition"); } } }); } catch (IllegalStateException ex) { throw new BeanCreationException(beanName, "Lookup method resolution failed", ex); } this.lookupMethodsChecked.add(beanName); } 将beanClass被@Lookup标记的方法封装到LookupOverride中,通过mbd.getMethodOverrides().addOverride(override)加入到BeanDefinition中,后面会在AbstractAutowireCapableBeanFactory的instantiateBean()方法中,会使用CglibSubclassingInstantiationStrategy的instantiate()方法创建bean对象。 @Override public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) { // Don't override the class with CGLIB if no overrides. if (!bd.hasMethodOverrides()) { Constructor<?> constructorToUse; synchronized (bd.constructorArgumentLock) { constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod; if (constructorToUse == null) { final Class<?> clazz = bd.getBeanClass(); if (clazz.isInterface()) { throw new BeanInstantiationException(clazz, "Specified class is an interface"); } try { if (System.getSecurityManager() != null) { constructorToUse = AccessController.doPrivileged( (PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor); } else { constructorToUse = clazz.getDeclaredConstructor(); } bd.resolvedConstructorOrFactoryMethod = constructorToUse; } catch (Throwable ex) { throw new BeanInstantiationException(clazz, "No default constructor found", ex); } } } return BeanUtils.instantiateClass(constructorToUse); } else { // Must generate CGLIB subclass. return instantiateWithMethodInjection(bd, beanName, owner); } } 可以看到如果!bd.hasMethodOverrides()则走instantiateWithMethodInjection()方法,这个方法内使用了CglibSubclassingInstantiationStrategy内部类CglibSubclassCreator来创建cglib的代理对象。CglibSubclassCreator使用的callback拦截器就包含LookupOverrideMethodInterceptor。 private static class LookupOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor { private final BeanFactory owner; public LookupOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) { super(beanDefinition); this.owner = owner; } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable { // Cast is safe, as CallbackFilter filters are used selectively. LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method); Assert.state(lo != null, "LookupOverride not found"); Object[] argsToUse = (args.length > 0 ? args : null); // if no-arg, don't insist on args at all if (StringUtils.hasText(lo.getBeanName())) { return (argsToUse != null ? this.owner.getBean(lo.getBeanName(), argsToUse) : this.owner.getBean(lo.getBeanName())); } else { return (argsToUse != null ? this.owner.getBean(method.getReturnType(), argsToUse) : this.owner.getBean(method.getReturnType())); } } } 如果调用的方法是被标记的,如果配置了@Lookup的value属性则从BeanFactory中取出bean name为@Lookup的value属性的bean对象返回,否则根据方法的返回类型从BeanFactory中取出相应的bean对象。 以上就是@Lookup的处理过程。 determineCandidateConstructors()方法的剩余部分就是挑选出候选的构造方法,从所有声明的构造方法选取带有@Autowired注解的,但是@Autowired的required==true的只能有一个,如果还有无参构造方法,再加上无参构造方法。如下: // Quick check on the concurrent map first, with minimal locking. Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass); if (candidateConstructors == null) { // Fully synchronized resolution now... synchronized (this.candidateConstructorsCache) { candidateConstructors = this.candidateConstructorsCache.get(beanClass); if (candidateConstructors == null) { Constructor<?>[] rawCandidates; try { rawCandidates = beanClass.getDeclaredConstructors(); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Resolution of declared constructors on bean Class [" + beanClass.getName() + "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex); } List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length); Constructor<?> requiredConstructor = null; Constructor<?> defaultConstructor = null; Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(beanClass); int nonSyntheticConstructors = 0; for (Constructor<?> candidate : rawCandidates) { // 非合成的,用户自定义的构造方法都是非合成的,JVM可能会产生合成的因此过滤掉 if (!candidate.isSynthetic()) { nonSyntheticConstructors++; } else if (primaryConstructor != null) { continue; } // 构造方法上所有注解属性 AnnotationAttributes ann = findAutowiredAnnotation(candidate); // 子类没有的话可能父类构造方法存在(覆盖了) if (ann == null) { Class<?> userClass = ClassUtils.getUserClass(beanClass); if (userClass != beanClass) { try { Constructor<?> superCtor = userClass.getDeclaredConstructor(candidate.getParameterTypes()); ann = findAutowiredAnnotation(superCtor); } catch (NoSuchMethodException ex) { // Simply proceed, no equivalent superclass constructor found... } } } if (ann != null) { // 只允许存在一个@Autowired(required=true)的构造方法 if (requiredConstructor != null) { throw new BeanCreationException(beanName, "Invalid autowire-marked constructor: " + candidate + ". Found constructor with 'required' Autowired annotation already: " + requiredConstructor); } boolean required = determineRequiredStatus(ann); if (required) { if (!candidates.isEmpty()) { throw new BeanCreationException(beanName, "Invalid autowire-marked constructors: " + candidates + ". Found constructor with 'required' Autowired annotation: " + candidate); } requiredConstructor = candidate; } candidates.add(candidate); } // 默认构造方法 else if (candidate.getParameterCount() == 0) { defaultConstructor = candidate; } } if (!candidates.isEmpty()) { // Add default constructor to list of optional constructors, as fallback. if (requiredConstructor == null) { // 如果不存在@Autowired构造方法,无惨构造方法没有@Autowired也作为候选 if (defaultConstructor != null) { candidates.add(defaultConstructor); } else if (candidates.size() == 1 && logger.isInfoEnabled()) { logger.info("Inconsistent constructor declaration on bean with name '" + beanName + "': single autowire-marked constructor flagged as optional - " + "this constructor is effectively required since there is no " + "default constructor to fall back to: " + candidates.get(0)); } } candidateConstructors = candidates.toArray(new Constructor<?>[0]); } // 只存在一个构造方法只能用它了 else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) { candidateConstructors = new Constructor<?>[] {rawCandidates[0]}; } // 如果只有两个构造方法,一个是带有@Primary一个是无参不带注解的,则这两个都作为候选构造方法 else if (nonSyntheticConstructors == 2 && primaryConstructor != null && defaultConstructor != null && !primaryConstructor.equals(defaultConstructor)) { candidateConstructors = new Constructor<?>[] {primaryConstructor, defaultConstructor}; } // 只有一个@Primary构造方法 else if (nonSyntheticConstructors == 1 && primaryConstructor != null) { candidateConstructors = new Constructor<?>[] {primaryConstructor}; } else { candidateConstructors = new Constructor<?>[0]; } this.candidateConstructorsCache.put(beanClass, candidateConstructors); } } } return (candidateConstructors.length > 0 ? candidateConstructors : null); 一个bean实例创建之后但此时还没有调用populateBean()方法完成属性的填充时,会经历MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition()方法。 @Override public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) { // InjectionMetadata可对指定对象进行注入(字段或方法) InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null); metadata.checkConfigMembers(beanDefinition); } private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) { // Fall back to class name as cache key, for backwards compatibility with custom callers. String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName()); // Quick check on the concurrent map first, with minimal locking. InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey); if (InjectionMetadata.needsRefresh(metadata, clazz)) { synchronized (this.injectionMetadataCache) { metadata = this.injectionMetadataCache.get(cacheKey); if (InjectionMetadata.needsRefresh(metadata, clazz)) { if (metadata != null) { metadata.clear(pvs); } // 构建由@Autowired、@Value、@Inject注解的字段或方法的InjectionMetadata metadata = buildAutowiringMetadata(clazz); this.injectionMetadataCache.put(cacheKey, metadata); } } } return metadata; } injectionMetadataCache是一个Map对象,缓存beanName与InjectionMetadata。InjectionMetadata对象通过buildAutowiringMetadata()方法来创建的。 private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) { LinkedList<InjectionMetadata.InjectedElement> elements = new LinkedList<>(); Class<?> targetClass = clazz; do { final LinkedList<InjectionMetadata.InjectedElement> currElements = new LinkedList<>(); ReflectionUtils.doWithLocalFields(targetClass, field -> { AnnotationAttributes ann = findAutowiredAnnotation(field); if (ann != null) { if (Modifier.isStatic(field.getModifiers())) { if (logger.isWarnEnabled()) { logger.warn("Autowired annotation is not supported on static fields: " + field); } return; } boolean required = determineRequiredStatus(ann); // 保存带有自动装配注解的非静态字段 currElements.add(new AutowiredFieldElement(field, required)); } }); ReflectionUtils.doWithLocalMethods(targetClass, method -> { Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) { return; } AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod); if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { if (Modifier.isStatic(method.getModifiers())) { if (logger.isWarnEnabled()) { logger.warn("Autowired annotation is not supported on static methods: " + method); } return; } if (method.getParameterCount() == 0) { if (logger.isWarnEnabled()) { logger.warn("Autowired annotation should only be used on methods with parameters: " + method); } } boolean required = determineRequiredStatus(ann); PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); // 保存带有自动装配注解的非静态方法 currElements.add(new AutowiredMethodElement(method, required, pd)); } }); elements.addAll(0, currElements); targetClass = targetClass.getSuperclass(); } while (targetClass != null && targetClass != Object.class); return new InjectionMetadata(clazz, elements); } buildAutowiringMetadata()方法分为对字段与方法的@Autowired(@Value,@Inject)注解处理。将标记@Autowired的费静态字段封装为AutowiredFieldElement对象,将标记@Autowired的非静态方法封装到AutowiredMethodElement对象然后都加入到InjectionMetadata中,此过程是一个循环过程此类处理完会沿着父类继续向上处理。AutowiredFieldElement与AutowiredMethodElement都是InjectionMetadata.InjectedElement的子类,都覆盖了父类的inject()方法这个下面会介绍。 通过findAutowiringMetadata()方法得到了InjectionMetadata对象之后调用了给对象的checkConfigMembers()方法。这个方法就是将上面保存的InjectedElement对象筛选出那些不存在于beanDefinition中的赋值给checkedElements。 public void checkConfigMembers(RootBeanDefinition beanDefinition) { Set<InjectedElement> checkedElements = new LinkedHashSet<>(this.injectedElements.size()); for (InjectedElement element : this.injectedElements) { Member member = element.getMember(); if (!beanDefinition.isExternallyManagedConfigMember(member)) { beanDefinition.registerExternallyManagedConfigMember(member); checkedElements.add(element); if (logger.isDebugEnabled()) { logger.debug("Registered injected element on class [" + this.targetClass.getName() + "]: " + element); } } } this.checkedElements = checkedElements; } 到这为止就是postProcessMergedBeanDefinition()方法的全部实现逻辑并没有做什么实际事情,其实是为了后面的行为做铺垫,请看postProcessPropertyValues()方法(在Spring5.1推荐使用postProcessProperties方法),此方法是在属性填充阶段被调用的。 @Override public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) { InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs); try { metadata.inject(bean, beanName, pvs); } catch (BeanCreationException ex) { throw ex; } catch (Throwable ex) { throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex); } return pvs; } @Deprecated @Override public PropertyValues postProcessPropertyValues( PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) { return postProcessProperties(pvs, bean, beanName); } 首先调用 findAutowiringMetadata()方法从缓存中再次取到InjectionMetadata对象,调用inject()方法完成autowired注入。 public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { Collection<InjectedElement> checkedElements = this.checkedElements; Collection<InjectedElement> elementsToIterate = (checkedElements != null ? checkedElements : this.injectedElements); if (!elementsToIterate.isEmpty()) { boolean debug = logger.isDebugEnabled(); for (InjectedElement element : elementsToIterate) { if (debug) { logger.debug("Processing injected element of bean '" + beanName + "': " + element); } element.inject(target, beanName, pvs); } } } 因为之前已经把类@Autowired注解标记的字段或方法封装到了响应的InjectedElement子类中了,此时依次调用子类的inject()方法完成对象的注入,下面分别看一看这两种注入的实现。 AutowiredFieldElement,调用BeanFactory的resolveDependency()方法返回一个符合字段类型的bean对象设置到此bean上。 @Override protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { Field field = (Field) this.member; Object value; // 解析过一次会缓存cachedFieldValue if (this.cached) { value = resolvedCachedArgument(beanName, this.cachedFieldValue); } else { DependencyDescriptor desc = new DependencyDescriptor(field, this.required); desc.setContainingClass(bean.getClass()); Set<String> autowiredBeanNames = new LinkedHashSet<>(1); Assert.state(beanFactory != null, "No BeanFactory available"); TypeConverter typeConverter = beanFactory.getTypeConverter(); try { value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); } catch (BeansException ex) { throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex); } synchronized (this) { if (!this.cached) { if (value != null || this.required) { this.cachedFieldValue = desc; registerDependentBeans(beanName, autowiredBeanNames); if (autowiredBeanNames.size() == 1) { String autowiredBeanName = autowiredBeanNames.iterator().next(); if (beanFactory.containsBean(autowiredBeanName) && beanFactory.isTypeMatch(autowiredBeanName, field.getType())) { // 以后注入就可以直接使用这个带有beanname的DependencyDescriptor更高效 this.cachedFieldValue = new ShortcutDependencyDescriptor( desc, autowiredBeanName, field.getType()); } } } else { this.cachedFieldValue = null; } this.cached = true; } } } // 字段赋值 if (value != null) { ReflectionUtils.makeAccessible(field); field.set(bean, value); } } AutowiredMethodElement,类似通过setter方法完成属性的注入。 @Override protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { if (checkPropertySkipping(pvs)) { return; } Method method = (Method) this.member; Object[] arguments; if (this.cached) { // Shortcut for avoiding synchronization... arguments = resolveCachedArguments(beanName); } else { Class<?>[] paramTypes = method.getParameterTypes(); arguments = new Object[paramTypes.length]; DependencyDescriptor[] descriptors = new DependencyDescriptor[paramTypes.length]; Set<String> autowiredBeans = new LinkedHashSet<>(paramTypes.length); Assert.state(beanFactory != null, "No BeanFactory available"); TypeConverter typeConverter = beanFactory.getTypeConverter(); for (int i = 0; i < arguments.length; i++) { MethodParameter methodParam = new MethodParameter(method, i); DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required); currDesc.setContainingClass(bean.getClass()); descriptors[i] = currDesc; try { Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter); if (arg == null && !this.required) { arguments = null; break; } arguments[i] = arg; } catch (BeansException ex) { throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex); } } synchronized (this) { if (!this.cached) { if (arguments != null) { Object[] cachedMethodArguments = new Object[paramTypes.length]; System.arraycopy(descriptors, 0, cachedMethodArguments, 0, arguments.length); registerDependentBeans(beanName, autowiredBeans); if (autowiredBeans.size() == paramTypes.length) { Iterator<String> it = autowiredBeans.iterator(); for (int i = 0; i < paramTypes.length; i++) { String autowiredBeanName = it.next(); if (beanFactory.containsBean(autowiredBeanName) && beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) { cachedMethodArguments[i] = new ShortcutDependencyDescriptor( descriptors[i], autowiredBeanName, paramTypes[i]); } } } this.cachedMethodArguments = cachedMethodArguments; } else { this.cachedMethodArguments = null; } this.cached = true; } } } if (arguments != null) { try { ReflectionUtils.makeAccessible(method); method.invoke(bean, arguments); } catch (InvocationTargetException ex){ throw ex.getTargetException(); } } } --- *本文基于DDKK.COM的Spring教程系列进行二次创作* *原文链接:https://ddkk.com/zhuanlan/j2ee/spring/1/8.html*