引言
使用版本 5.2.5RELEASE
这一部分的文档覆盖了几乎所有的Spring 框架的技术,Spring框架最主要的控制反转(IOC容器),在对Spring框架的IOC 容器的进行了充分的考虑讨论过后,还对Spring的面向切面编程(AOP) 进行了全面的介绍,Spring 框架拥有自己的AOP框架,从理论上来说可以满足企业的80%的需求.还提供了Spring与AspectJ集成的内容(目前在特性方面是最丰富的,当然也是Java企业空间中最成熟的AOP实现)。
这个章节讲的是Spring 的控制反转的容器IOC
这个章节讲的是Spring框架的实现控制反转(IOC)的原理,IOC容器也被理解成依赖注入,在此过程中,对象通过他们的构造参数,工厂方法的参数,或者工厂方法放回的对象在进行设置参数.来定义他们自己 的依赖(这个依赖指的是就是对象内部所持有的外部对象).然后在容器创建的时候在注入这些依赖.这个过程从根本上来说就是摆脱bean (对象) 自己控制其直接依赖.
Spring框架的两个基础包 org.springframework.beans 和org.springframework.context 是Spring IOC 容器的基础.BeanFactory接口提供一个高级的配置管理机制,可以管理任何类型的对象,ApplicationContext 是它的一个子接口,(ApplicationContext是一个接口,继承了多个接口)它增加了如下功能
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
1.更加轻松的集成了Spring AOP 的功能
2.资源消息处理,也就是国际化处理
3.事件发布
4.应用层特定的上下文WebApplicationContext 用于WEB 应用
public interface WebApplicationContext extends ApplicationContext
org.springframework.context.ApplicationContext
这个接口代表的IOC 容器 并且它负责bean的
实例化,配置,和组装(对象的依赖注入).容器通过读取配置的元数据来指示什么样的对象去实例化,配置,和组装.一般配置元数据的代表是 XML,java 的注解,或者java代码.它使得你可以表达组成你应用的对象,以及丰富这些对象之间的依赖关系.
Spring 提供了一些关于ApplicationContext 的实现,在独立的应用中,它可以去创建例如ClassPathXmlApplicationContext
or FileSystemXmlApplicationContext
的实例,然而XML作为定义配置元数据的传统方式,可以通过提供少量XML配置来声明性地启用对这些其他元数据格式的支持从而指示容器将Java注解或代码用作元数据
在绝大部分的应用场景中,不必要显示的使用代码去实例化一个或者多个IOC 容器 ,例如在web 使用的场景中
应用程序的web.xml文件中的简单的八行 样板Web描述符XML通常就足够了
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xmlparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
如果 使用Spring Tools for Eclipse(Eclipse支持的开发环境),则只需单击几下鼠标或击键即可轻松创建此样板配置。
下图显示了Spring的工作原理的高级视图,你的应用class 结合通过下面这样的方式,在ApplicationContext被创建和初始化,你有一个完全配置和可执行的系统
根据上图所示,Spring 的容器消费了一个配置元数据,这个配置元数据代表这你,作为一个应用开发人员,告诉容器怎么去实例化,配置,组装你应用里面的对象;
传统上,配置元数据以简单直观的XML格式提供,这是本章大部分内容用来传达Spring IoC容器的关键概念和功能的内容。
**注意:**基于XML的方式不是配置元数据的唯一方式,配置文件的写入方式对于Spring 容器的配置来说没有一点关系,也就是说无论你用哪种方式表达你的元数据 和最后Spring 容器初始化你的bean没有什么关系.现在大多数的开发者使用的是基于注解的配置;
有关在Spring容器中使用其他形式的元数据的信息,请参见:
1.基于注解的配置:Spring 2.5引入了对基于注解的配置元数据的支持。
2.基于Java的配置:从Spring 3.0开始,Spring JavaConfig项目提供的许多功能已成为核心Spring Framework的一部分。 因此, 可以使用Java而不是XML文件来定义应用程序类外部的bean。 要使用这些新功能,请参见@ Configuration,@ Bean,@ Import和@DependsOn注解。
Spring的配置是由容器管理的至少一个,通常是一个以上bean 定义的配置文件组成,XML 配置一般是使用 标签 ,而java代码通常是使用@Bean 配合@Configuration一起使用
这些bean的定义对应于一个实际的对象去组成你的应用,通常是定义业务层面的对象,数据持久层对象,表达/控制器对象 例如Struct的Action对象,基础的对象比如说是hibernate的SessionFactories,JMS的Queue.以此类推,通常,不会在容器中配置细粒度的实体对象,因为创建和加载域对象通常是DAO和业务逻辑的责任.但是,你可以使用Spring和AspectJ的集成来配置在IOC容器控制之外的创建的对象
示例 XML 方式去管理bean
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="..."> ==>1 , 2
bean>
<bean id="..." class="...">
bean>
beans>
1.id 属性是一个字符串类型用于标识单个bean定义
2.class 属性定义了bean的类型 和全类名
id属性的值是指协作对象。 在此示例中未显示用于引用协作对象的XML。就是说这个id 可以在xml被其他的bean定义所引用;例如
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
beans>
在本地的一个路径或者多个路径的字符串作为ApplicationContext
构造器参数去让容器通过不同格式的资源文件加载元数据,例如本地的文件系统,类路径 等等
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
注:在学完关于Spring 的IOC 容器后,你或许想知道关于Spring Resource其他知识,它提供了一种方便的机制来从URI语法中定义的位置读取InputStream,尤其是,如应用程序上下文和资源路径中所述,Application Contexts and Resource Paths.
services.xml
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
bean>
beans>
daos.xml
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountDao"
class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
bean>
<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
bean>
beans>
在上面的例子中,业务层对象PetStoreServiceImpl 依赖于两个数据持久层对象itemDao,accountDao
PetStoreServiceImpl 的属性名称依赖于java Bean的名称 (就是其他bean的id)
构建xml方式配置元数据
使得bean的定义跨越多个XML文件是很有用的,经常,每个独立的XML 配置文件,在你的组织架构中代表一个业务层代码或者是某个模块,
你可以使用应用上下文的构造器去加载这些bean的定义的XML 片段,该构造函数采用多个资源位置,可以代替的是 我们可以使用 y元素去加载bean的定义通过其他的一个或多个文件
<beans>
<import resource="services.xml"/>
<import resource="resources/themeSource.xml"/>
<import resource="resources/messageSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
beans>
在上面的示例中,其他的bean的定义在这三个文件中 services.xml,resources/messageSource.xml,resources/messageSource.xml.这些文件的位置都是相对于当前的文件位置 也就是,标签里面的数据路径就是相对于次文件的相对路径,所以说是services.xml 和本文件是相同的目录下面的文件, messageSource.xml,themeSource.xml 是资源路径下面的文件,如你所见 “/” 是被忽略的.然而,这些路径都是相对的,最好是不用使用斜杆"/" ,这些文件的内容被导入,包括文件中的顶层元素 ,但是都需要转训验证基于Spring Schema 对于定义bean的xml 规则.
**注:**有可能,但是不推荐的一种寻找父级目录的写法 “…/”,就这样可能会创建基于当前应用的一个对外部文件的依赖关系.特别是使用类路径结合上面所说的这样:classpath:…/services.xml.运行的时候会根据类路径选择相对的路径,我们查看这个父级路径,当我们的类路径修改时,可能会因为这个而导致一选择不同的父级目录而导致错一些问题; 你可以使用绝对路径来代替相对路径 比如说是file:C:/config/services.xml
or classpath:/config/services.xml
.然而,你需要意思到你将给你的应用耦合到了这些绝对路径.如此情况下,需要保持一些间接的关系和这些绝对路径,比如说是 通过在运行时针对JVM系统属性解析的“ $ {………}”占位符
命名空间本身提供了导入指令功能,Spring提供的一系列XML名称空间中提供了超出普通bean定义的其他配置功能.比如 the context
and util
.
Groovy Bean bean定义 DSL
作为外部化配置元数据的另一个示例,Bean定义也可以在Spring的Groovy Bean定义DSL中表达,这是从Grails框架中得知的。 通常,这种配置位于“ .groovy”文件中,其结构如以下示例所示
beans {
dataSource(BasicDataSource) {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
settings = [mynew:"setting"]
}
sessionFactory(SessionFactory) {
dataSource = dataSource
}
myService(MyService) {
nestedBean = { AnotherBean bean ->
dataSource = dataSource
}
}
}
这种配置样式在很大程度上等同于XML bean定义,甚至支持Spring的XML配置名称空间。 它还允许通过importBeans指令导入XML bean定义文件。
ApplicationContext
是高级工厂的接口,该工厂维持了一些bean的注册表信息和它们的依赖,通过使用方法
T getBean(String name, Class<T> requiredType)
你可以在容器里面检索一些实例.ApplicationContext
允许你读取bean的定义并且访问它们,如下所示
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
使用Groovy配置,引导看起来非常相似。 它有一个不同的上下文实现类,该类可识别Groovy(但也了解XML Bean定义)。 以下示例显示了Groovy配置:
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
许多该接口的灵活变体接口GenericApplicationContext
结合一个阅读代理,就是把读取文件方式代理给其他的接口 比如说是 XmlBeanDefinitionReader
==>专门读XML 文件 如下所示
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
还可以将GroovyBeanDefinitionReader用于Groovy文件,如以下示例所示:
GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();
你可以在同一个ApplicationContext 上面配置多个阅读代理去处理不同种类的配置文件
你可以使用getBean 这样的方法来检索你的beans ,这个接口还有几种其他的方法来获取bean对象,但是,在理想情况下,在你的应用代码里面不应该这样使用它们,确实,你的应用代码里面不应该调用getBean() 方法,从而完全不依赖于Spring APIs,比如说是Spring 集成了各种web 应用框架 的依赖注入来集成controllers 的对象或者JSF-managed 的对象,让 通过元数据(例如自动装配注解)声明对特定bean的依赖性
Spring IoC容器管理一个或多个bean。 这些bean是使用 提供给容器的配置元数据创建的(例如,以XML 定义的形式)。
在容器本身内,这些beans的定义代表对象就是BeanDefinition
对象 ,它包含以下元信息
带包名的类名称,通常情况下,是真是实现类的名称;
Bean行为配置元素,用于声明Bean在容器中的行为(作用域,生命周期回调等)scope, lifecycle callbacks…
引用其他bean完成其工作所需的bean。 这些引用也称为协作者或依赖项
要在新创建的对象中设置的其他配置设置,例如,池的大小限制或在管理连接池的bean中使用的连接数
此元数据转换为构成每个bean定义的一组属性。 下表描述了这些属性:
属性 | 意思 |
---|---|
Class | Instantiating Beans,实例化的bean |
Name | Naming Beans bean命名 |
Scope | bean 的范围 |
Constructor arguments | 依赖追的属性 |
Properties | 依赖追的属性 |
Autowiring mode | 制动注入的协作项 |
Lazy initialization mode | 懒加载的bean |
Initialization method | 初始化方法回调方法 |
Destruction method | 对象销毁的回调方法 |
Bean的定义信息除了包含有关如何创建一个特定的bean之外,ApplicationContext
接口的实现也支持注册在容器外部定义的队形 ,就比如说@Bean 注解返回一个自己创建的bean 然后注册到容器里面去,这是通过通过getBeanFactory()访问ApplicationContext的BeanFactory来完成的.它将返回一个BeanFactory 的实现DefaultListableBeanFactory
,它支持注册通过registerSingleton() 和registerBeanDefinition()方法,但是,典型的应用程序只能与通过常规bean定义元数据定义的bean一起使用。
注:Bean定义信息和手动创建的bean的当立对象需要尽早的注册,为了在自动注入或者下一个步骤找的一个正确的原因;原则上是后来的会覆盖前面的bean 和bean的定义信息,官方不允许在运行时注册bean 对象,应为这样会导致并发访问的问题,和bean容器不一致的问题;
每一个bean具有一个或者多个标识,这些标识必须是独特的在承载bean 的容器里面,也就是说在IOC 容器里面一个唯一的标识/多个唯一的标识 代表着一个bean ,一个bean 通常有一个标识. 然而,如果它需要更多地,其他的可以视为别名;
在基于XML 的bean的元数据配置中,我们可以使用id 属性,name 属性或者两个一起标识这个bean,id属性可让 精确指定一个id。 按照惯例,这些名称是字母数字(“ myBean”,“ someService”等),但它们也可以包含特殊字符,如果你想为bean指定一些其他的别名,这个时候你就可以使用name 属性.以逗号(,),分号(;)分隔或空白.;回顾历史版本记录,在Spring的3.1之前.id的属性被定义为xsd:ID 类型,该类型限定了一些可能的字符.从Spring3.1之后 它被开始定义成了xsd:String 类型.注意的是 Bean ID 的唯一性还是被容器所强制执行,尽管不在由XML 解析器来执行
你不需要为一个bean提供名字和或者id,如果你没有明确的申请name/id 属性,容器会为bean生成一个唯一的名字,然而 如果你想使用名称来引用该bean,通过使用ref元素或服务定位器样式查找, 必须提供一个名称。 不提供名称的动机与使用内部bean和自动装配合作者有关
Bean 的命名约定
约定是在命名bean时使用标准java约定,上周末小写的驼峰命名,比如accountManager
, accountService
bean 的命名可以使得我们的配置信息更加简单的阅读和理解,而且 如果你是用AOP ,在将建议应用于一组与名称相关的bean时,它会很有帮助
**注:**当在类路径下面组件扫描的时候,Spring会自动为那些没有命名的bean 生成一个名字;遵循前面描述的规则
使用简单的类名并将其初始字符转换为小写,但是,在(不寻常的)特殊情况下,如果有多个字符并且第一个和第二个字符均为大写,原始的类名称保留下来。 这些规则与java.beans.Introspector.decapitalize(Spring在此使用)定义的规则相同。比如 类名AbbCdd 对应spring自动生成的就是abbCdd ,但是类名是ABbCdd的话,默认的名字就是ABbCdd 就是类名不会改变
在bean的定义外名声明bean的别名
在bean的定义内部,你可以使用一个或者多个名字为bean命名,通过结合多个一个独立的id 属性和多个name属性组合来标记一个唯一的bean ,这些名称可以是同一bean的等效别名,在某些情况下很有用,例如通过使用特定于该组件本身的Bean名称,让应用程序中的每个组件都引用一个公共依赖项。
在实际定义bean的地方制定所有的别名不是总是足够的,然而,有时候我们在其他地方自定bean的别名是可取的.这在大型系统中通常是这种情况,在大型系统中,配置在每个子系统之间分配,每个子系统都有自己的对象定义集.在基于XML的配置元数据中,可以使用元素来完成此操作。 以下示例显示了如何执行此操作
<alias name="fromName" alias="toName"/>
在这种情况下,在使用该别名定义之后,也可以将名为fromName的bean(在同一容器中)称为toName。
在举个例子,在子系统A中的配置元数据文件中通过名字subsystemA-dataSource
. 来指定数据源,在子系统B中的配置元数据文件中通过名字subsystemB-dataSource
. 来指定数据源;组成使用这两个子系统的主应用程序时,主应用程序通过myApp-dataSource的名称引用数据源。要使所有三个名称都引用同一个对象,可以将以下别名定义添加到配置元数据中:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
现在,每个组件和主应用程序都可以通过唯一的名称引用数据源,并保证不与任何其他定义冲突(有效地创建名称空间),但它们引用的是同一bean。
基于java 的bean配置
如果使用Javaconfiguration,则@Bean注解可用于提供别名。 有关详细信息,请参见使用@Bean注解。
bean的定义实际上就是创建一个或多个对象的"配方/食谱也行",当容器被要求拿到bean对象时,容器根据bean的名字拿到bean的配方 再去创建一个具体的实例对象.
在基于XML 的配置文件中,你指定使用类去获取对象,就是相当于在配置文件中匹配class 属性的元素,也是因为该标签用使用class 属性 ,所以通过class 可以找到bean 元素,这个类属性是定义bean的定义的时候强制使用的 (有关异常,请参见使用实例工厂方法实例化和Bean定义继承。)可以通过以下两种方式之一使用Class属性:
通常情况下,要指定需要构造的bean ,容器会直接的使用反射的方法去调用bean的构造器,在某种程度上就相当于直接的new 对象操作
要指定包含要创建对象的静态工厂方法的实际类,在不太常见的情况下,容器将在类上调用静态工厂方法以创建Bean。 从静态工厂方法的调用返回的对象类型可以是同一类,也可以是完全不同的另一类。
内部类名称
如果要为静态嵌套类配置Bean定义,则必须使用嵌套类的二进制名称。
例如,如果 在com.example包中有一个名为SomeThing的类,并且此SomeThing类具有一个名为OtherThing的静态嵌套类,则bean定义上的class属性的值为com.example.SomeThing $ OtherThing。
请注意,名称中使用$字符将嵌套的类名与外部类名分开。
使用构造函数实例化
当你通过构造方法创建一个bean,所有的类都是可以被Spring兼容和使用,类的开发不需要该类实现特定的接口或者编写一些指定的代码.建档的指定bean类就足够了.由于依赖于何种IOC容器,你或许需要一个默认的空参构造器
Spring的IOC 容器可以管理任何你想让它管理的类,它不仅仅是限制于管理真正的javaBeans.大部分的Spring 使用者实际上更喜欢实际的javaBean ,它们只有一个默认的无参构造,和一些适当的getter setter使用基于XML的配置元数据,可以如下指定bean类:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
有关向构造函数提供参数(如果需要)并在构造对象之后设置对象实例属性的机制的详细信息,请参见注入依赖项。
使用静态方法实例化
你可以创建一个静态工厂方法去定义一个bean ,通过使用class 属性来指定包含的静态工厂方法的类,并使用名为factory-method的属性指定工厂方法本身的名称。你可以调用这个方法(使用可选参数,稍后会讲)放回一个活动对象.该对象然后被视为通过构造函数创建.此类bean定义的一种方法是在旧版代码中调用静态工厂;
下面的这个bean定义被一个工厂方法创建 这个定义不指定放回的类型class,而仅指定工厂方法的类==>也就是说指定工厂类 和工厂类创建对象的一个方法 也叫工厂方法,这里bean的定义就只定义这个工厂方法,和工厂类,工厂方法必须是静态的方法
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
以下示例显示了一个可与前面的bean定义一起使用的类
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
有关为工厂方法提供(可选)参数以及在对象从工厂返回后设置对象实例属性的机制的详细信息,请参阅详细信息中的依赖关系和配置。
使用实例工厂方法实例化
类似通过静态工厂方法实例化,使用实例工厂方法实例化就是在容器里面调用一个bean的非静态的方法来创建一个新的bean ,使用上述的这个机制,标签的类属性就是为空,在factory-bean 属性里面,指定当前容器(或者父级容器)里面的一个bean,和该bean中的实例化方法名,容器会反射调用该方法去创建对象,方法在属性factory-method 里面设置,下面就是设置样例
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
bean>
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
对应得容器里面的bean 代码
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
一个工厂类可以有多个工厂方法
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
bean>
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
<bean id="accountService"
factory-bean="serviceLocator"
factory-method="createAccountServiceInstance"/>
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
这种方法表明,工厂Bean本身可以通过依赖项注入(DI)进行管理和配置。 详细信息,请参见依赖性和配置。
注意: 在Spring 的文档 factory ,factory-bean 是指的是在Spring容器中配置的bean,它通过实例或静态工厂方法创建对象 相比之下,FactoryBean(注意大小写)是指特定于Spring的FactoryBean实现类。
确定bean的运行时类型
确定一个bean运行时的类型不是一个简单的事情,bean元数据定义里面指定类只是初始类引用,可能和声明的工厂方法组合或成为FactoryBean 类,这个就是可能导致bean的类型和运行时类型不一致,或在实例工厂方法的情况下根本不设置(而是通过指定的工厂bean名称解析),而且AOP代理可以使用基于接口的封装的代理bean 实例,也就是说AOP 实例化了一个接口来生产了一个代理类Bean 对象,而目标bean的实际类型(仅是其实现的接口)的暴露程度有限;
找出特定bean的实际运行时类型的推荐方法是对指定bean名称的BeanFactory.getType调用。这考虑了以上所有情况,并返回了BeanFactory.getBean调用将为同一bean名称返回的对象类型。
一个典型的企业应用不会是有一个对象来组成的(或者叫spring 术语里面的bean),即使是最简单的应用程序,也有一些对象是可以协同工作的,以呈现最终用户视为一致的应用程序;下一部分说明如何从定义多个独立的bean定义到实现对象协作以实现目标的完全实现的应用程序;
依赖注入是一个过程,发生在对象定义它们的依赖的时候(就是一个对象,要依赖其他的对象去工作),只能是通过构造器参数,工厂方法参数,在构造器被执行后或者工厂方法返回对象后再对对象实例上设置的属性;在容器创建好bean后注入这些依赖;从根本上来说,和之前的通过直接的构造器实例化对象和服务定位模式定位对象来控制对象内部的依赖对象是不同的;因此称之为控制反转;通俗的来说就是好,之前我比如一个类里面需要一个对象来干活,就直接在new 一个对出来使用,现在使用了Spring之后就是容器会自动的吧依赖属性/对象 注入到所需要的的对象里面;对象的创建过程和实例化过程被框架所做了;控制权被反转
使用DI 后是的代码变得简洁,并且可以和那些依赖的对象更加有效的解耦,该队形不查找它的依赖项,并且不知道依赖项的具体位置和类,你的类变得更加容易的去测试,特别是当依赖项是在接口或抽象类上面的时候.并且可以在单元测试中使用这些实现(注入的接口:实际上是容器会放回一个具体的类对象);
DI存在两个主要变体:基于构造函数的依赖注入和基于Setter的依赖注入。
构造函数的依赖注入
基于构造函数的依赖注入是通过容器反射调用多参数的构造器函数来完成的.调用带有指定参数的静态工厂方法来构造Bean 几乎是等效的(内部也是来反射调用多参数的构造器函数来构造),并且本次讨论将构造器函数和静态工厂方法的参数看做是一样的.下面实例显示了只能通过构造器函数注入进行依赖注入的类
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
注意,该类没有什么特别的。 它是一个POJO,不依赖于特定于容器的接口,基类或注解。
构造器参数解析
构造器函数参数解析是通过使用的参数的类型来进行匹配的,如果实在bean的定义的构造器函数参数中不存在潜在的歧义,在bean定义中定义的构造器函数参数的顺序就是这些参数提供给构造器函数的顺序;
解读: 就是构造器里面可能会有多个参数 ,这些参数在构造器注入的时候 是按照构造器函数里面参数的顺序来依次进行注入的;
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
假设就是ThingTwo ThingThree类没有通过继承关联,则就不存在潜在的歧义,因此以下配置可以正常工作你不需要再 指定构造器函数参数索引和类型
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
beans>
就是顺序反了也没事 ,只要这两个类是独立关系没有继承
当引用另一个bean的时候,类型是已知的,并且可以发生匹配(与前面的示例一样).当使用简单类型时,Spring无法确定值得类型(true):spring解析成String 还是Boolean,因此在没有帮助的情况下无法按照类型进行匹配,可以考虑一下
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
构造器参数类型匹配
在上述情况下,容器可以使用简单类型来匹配指定的参数 使用type 属性
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
bean>
构造器参数索引匹配
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
bean>
除了解决多个简单值得歧义外,指定索引也解决了参数类型一样的情况
注意:参数索引是从0 开始的
构造器函数参数名字匹配
你可以指定构造器函数的的名字来消除歧义
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
bean>
请记住,要立即使用该功能,必须在启用调试标志的情况下编译代码,以便Spring可以从构造函数中查找参数名称。 如果 不能或不希望使用debug标志编译代码,则可以使用@ConstructorProperties JDK注解显式命名构造函数参数。
public class ExampleBean {
// Fields omitted 成员变量省略
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
ApplicationContext 支持构造器函数和setter 注入它自己管理的bean的方式,在构造器注入和还支持setter 注入,当你在你的beandefinition中定义或者配置了一些依赖的话,与PropertyEditor实例结合使用可将属性从一种格式转换为另一种格式, 就是上面的这种,spring里面定义的注解有Propertyxxx 这些都是会在对象实例化后可以对属性赋值/注入依赖的, 然而大多数的Spring使用者不会直接的使用这些类,而是使用XML 定义,注解组建定义(@Component
, @Controller
等等),或者使用@Bean 在@configuration 类里面,然后将这些源在内部转换为BeanDefinition的实例
注意:
构造器注入/Setter 注入
自从你可以混用构造器注入和setter 注入之后,构造器注入用于强制依赖项,setter 注入用于可选依赖项,这是一个很好的经验法则,请注意可以在setter 方法上面使用@require注解,这样该属性就是必须依赖项.但是最好使用带有参数的程序验证构造注入;
spring的开发团队推荐使用构造器注入,这样会使你的应用主键实现为不可变的对象,并且确保你的组建也就是依赖不会变成null;此外,构造函数的组建始终以完全初始化的状态返回给构造函数(就是依赖的该对象是初始化的),大量的构造函数参数是一种不好的体验,说明该类就是可能依赖的大量的其他的主键,担当了太多的职责,应该对其重构以优化更好的分离和解决问题
Setter 注入主要是应用于可选的依赖项,并且该依赖项应该有默认值,否者的话,在使用这些依赖项的时候都需要进行非空判断;setter注入的一个好处是,setter方法可使该类的对象在以后重新配置或重新注入。因此,通过JMX Bean进行管理是用于setter注入的引人注目的用例。
对具体的类使用适用的注入方式,当你使用的第三方类时, 没有相关的源码, 例如,如果第三方类未公开任何setter方法,则构造函数注入可能是DI的唯一可用形式。
依赖解析的过程
容器执行bean的依赖解析如下
在创建容器的时候,Spring容器回去校验每个bean的配置.但是在实际创建bean之后,不会这只bean属性本身.创建容器时,将创建具有单例作用域并设置为预先实例化(默认)的Bean。 范围在Bean范围中定义,否则,仅在请求时才创建Bean,创建bean可能会导致创建另一个bean,创建bean ,然后bean依赖另一个bean ,依赖的bean又依赖于其他的bean,当在创建第一个bean的时候,这些依赖之间的相互关系可能会出现的比较晚
注意: 循环依赖
如果主要使用构造函数注入,则可能会创建无法解决的循环依赖场景。举个例子,A 构造器依赖于B ,B 构造器依赖于A,如果为A和B类配置bean以相互注入,Spring IoC容器在运行时检测到此循环引用,会抛出BeanCurrentlyInCreationException
异常
一种可能的解决方案是编辑某些类的源代码,这些类的源代码由设置者而不是构造函数来配置,或者,避免构造函数注入,而仅使用setter注入。 换句话说,尽管不建议这样做,但是 可以使用setter注入配置循环依赖项。
与典型情况(没有循环依赖关系)不同,Bean A和Bean B之间的循环依赖关系迫使其中一个Bean在完全完全初始化之前被注入另一个Bean(经典的“鸡与蛋”场景)。
你通常可以信任Spring做正确的事情,它会检测配置问题,例如对不存在的bean的依赖和循环依赖的问题,在容器的加载期间,Spring尽量后面设置参数和解析依赖,当bean被真正的创建;这意味着Spring容器正确的加载后会在后面生成一个异常当你要求一个问题的创建bean/或该对象的依赖 比如这个bean缺少一个有效的参数而抛出了异常;这个可能会延迟某些配置问题的可能性,这就是为什么默认情况下ApplicationContext 实现会预先实例化单利的bean的原因,在我们真正需要这些之前 花费一些前期的时间和内存来创建它们,不会再创建ApplicationContext
发现问题,而不是稍后发现,你也可以覆盖次行为,一遍单利bean 延迟初始化,而不是预先初始化;
上面讲的就是为什么默认的单例的对象先默认的被容器初始化,优点之一就是用的时候bean已经在容器内了 用的时候直接取对象就可以,还有就是预先加载所有单例bean 会预先报告对象的异常情况(比如缺少参数)就是上文说的会延迟知道某个配置问题的可见性 比如说我创建一个又问题的bean的元数据 肯定是在容器加载的时候发现这个bean的元数据的配置问题还不是程序在跑了一段时间后;来出现问题,这样也不利于问题的集中发现和解决;
如果没有循环依赖存在,一个或者多个协作的bean被注入到一个一依赖bean,每个协作的bean对象在注入之前都是会被完全的配置,这意味着如果bean A对bean B有依赖关系,则在对bean A调用setter方法之前,Spring IoC容器会完全配置bean B,就是在setter 注入B 的时候 已经完全的创建配置好了B对象,换一句话说,实例化该bean被实例化(如果他是懒加载的) 则设置它的依赖关系,并调用它的生命周期方法 (例如配置的init mothed 或者initializingBean 的回调方法)
依赖注入举例
<bean id="exampleBean" class="examples.ExampleBean">
<property name="beanOne">
<ref bean="anotherExampleBean"/>
property>
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="i" value="1"/>
bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
以下示例显示了相应的ExampleBean类:
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
在前面的示例中,声明了setter以与XML文件中指定的属性匹配。 以下示例使用基于构造函数的DI:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg>
<ref bean="anotherExampleBean"/>
constructor-arg>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public ExampleBean(
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
this.beanOne = anotherBean;
this.beanTwo = yetAnotherBean;
this.i = i;
}
}
bean定义中指定的构造函数参数用作ExampleBean构造函数的参数
现在考虑该示例的一个变体,在该变体中,不是使用构造函数,而是指示Spring调用静态工厂方法以返回对象的实例
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
<constructor-arg ref="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value="1"/>
bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
// a private constructor
private ExampleBean(...) {
...
}
// a static factory method; the arguments to this method can be
// considered the dependencies of the bean that is returned,
// regardless of how those arguments are actually used.
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}
静态工厂方法参数也是有 来提供,与实际上使用构造函数完全相同,工厂方法放回的类型不是必须要和该类的对象的类型是一只的(虽然 上面例子上是一样的),实例(非静态)工厂方法可以以基本上相同的方式使用(除了使用factory-bean属性代替class属性之外),因此在此不讨论这些细节
上一节所讲,你可以配置bean的属性和构造函数参数使用到被IOC管理的bean 或者一些内联的值(简单值),XML的方式就是标签
和
简单值
元素的value属性将属性或构造函数参数指定为人类可读的字符串表示形式。 Spring的转换服务用于将这些值从字符串转换为属性或参数的实际类型。 以下示例显示了设置的各种值:
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
bean>
下面的示例使用p-namespace进行更简洁的XML配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="masterkaoli"/>
beans>
前面的XML更简洁。 但是,除非在创建bean定义时使用支持自动属性完成的IDE(例如IntelliJ IDEA或Eclipse的Spring Tools),否则错字是在运行时而不是设计时发现的。 强烈建议 使用此类IDE帮助。
还可以配置java.util.Properties实例,如下所示:
<bean id="mappings"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
value>
property>
bean>
Spring容器通过使用JavaBeans PropertyEditor机制将元素内的文本转换为java.util.Properties实例。 这是一个不错的捷径,并且是Spring团队更喜欢使用嵌套的元素而不是value属性样式的几个地方之一。
The idref
element
idref元素只是一种防错方法,可以将容器中另一个bean的id(字符串值-不是引用)传递给或元素。 以下示例显示了如何使用它:
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
property>
bean>
前面的bean定义片段(在运行时)与下面的片段完全等效:
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
bean>
第一种形式优于第二种形式,因为使用idref标记可使容器在部署时验证所引用的命名bean实际存在。 在第二个变体中,不对传递给客户端bean的targetName属性的值执行验证。 拼写错误仅在实际实例化客户端bean时发现(最有可能导致致命的结果)。 如果客户端Bean是原型Bean,则可能在部署容器很长时间后才发现此错字和所产生的异常。
注意:
在4.0 Bean XSD中不再支持idref元素上的local属性,因为它不再提供常规Bean引用上的值。 升级到4.0模式时,将现有的idref本地引用更改为idref bean。
元素带来价值的一个常见地方(至少在Spring 2.0之前的版本中)是在ProxyFactoryBean bean定义中的AOP拦截器的配置中。 指定拦截器名称时使用元素可防止 拼写错误的拦截器ID。
对其他Bean的引用(协作者)
ref元素是或定义元素内的最后一个元素。 在这里, 将bean的指定属性的值设置为对容器管理的另一个bean(协作者)的引用。 引用的bean是要设置其属性的bean的依赖关系,并且在设置属性之前根据需要对其进行初始化。 (如果协作者是单例bean,则它可能已经由容器初始化了。)所有引用最终都是对另一个对象的引用。 范围和验证取决于 是通过bean还是父属性指定另一个对象的ID或名称。
通过标记的bean属性指定目标bean是最通用的形式,并且允许创建对同一容器或父容器中任何bean的引用,而不管它是否在同一XML文件中。 bean属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的值之一相同。 下面的示例演示如何使用ref元素:
<ref bean="someBean"/>
通过parent属性指定目标bean将创建对当前容器的父容器中bean的引用。 parent属性的值可以与目标Bean的id属性或目标Bean的name属性中的值之一相同。 目标Bean必须位于当前容器的父容器中。 主要在具有容器层次结构并且要使用与父bean名称相同的代理将现有bean封装在父容器中时,才应使用此bean参考变量。 以下一对清单显示了如何使用parent属性:
<bean id="accountService" class="com.something.SimpleAccountService">
bean>
<bean id="accountService"
property>
bean>
注意
ref元素的local属性在4.0 Bean XSD中不再受支持,因为它不再提供常规Bean引用上的值。 升级到4.0模式时,将现有的ref本地引用更改为ref bean。
或元素内的元素定义了一个内部bean,如以下示例所示:
<bean id="outer" class="...">
<property name="target">
<bean class="com.example.Person">
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
bean>
property>
bean>
内部bean定义不需要定义的ID或名称。 如果指定,则容器不使用该值作为标识符。 容器在创建时也将忽略作用域标志,因为内部Bean始终是匿名的,并且始终与外部Bean一起创建。 不可能独立访问内部bean或将它们注入到协作bean中而不是封装在bean中。
作为一个特例,可以从自定义作用域中接收销毁回调,例如对于单例bean中包含的请求范围内的bean。 内部bean实例的创建与其包含的bean绑定在一起,但是销毁回调使它可以参与请求范围的生命周期。 这不是常见的情况。 内部bean通常只共享其包含bean的作用域。
集合
,,和元素分别设置Java集合类型List,Set,Map和Properties的属性和参数。 以下示例显示了如何使用它们
<bean id="moreComplexObject" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">[email protected]prop>
<prop key="support">[email protected]prop>
<prop key="development">[email protected]prop>
props>
property>
<property name="someList">
<list>
<value>a list element followed by a referencevalue>
<ref bean="myDataSource" />
list>
property>
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
map>
property>
<property name="someSet">
<set>
<value>just some stringvalue>
<ref bean="myDataSource" />
set>
property>
bean>
映射键或值的值或设置值也可以是以下任意元素:
bean | ref | idref | list | set | map | props | value | null
集合合并
Spring容器还支持合并集合。 应用程序开发人员可以定义父,,或元素,并具有子,,或元素。 从父集合继承和覆盖值。 也就是说,子集合的值是合并父集合和子集合的元素的结果,子集合的元素会覆盖父集合中指定的值。
关于合并的本节讨论了父子bean机制。 不熟悉父bean和子bean定义的读者可能希望先阅读相关部分,然后再继续。
下面的示例演示了集合合并:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">[email protected]prop>
<prop key="support">[email protected]prop>
props>
property>
bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<props merge="true">
<prop key="sales">[email protected]prop>
<prop key="support">[email protected]prop>
props>
property>
bean>
<beans>
注意子bean定义的adminEmails属性的元素上使用merge = true属性。 当子bean由容器解析并实例化后,生成的实例具有adminEmails Properties集合,其中包含将孩子的adminEmails集合与父对象的adminEmails集合合并的结果。 以下清单显示了结果:
[email protected]
[email protected]
[email protected]
子属性集合的值集继承了父的所有属性元素,子属性的支持值将覆盖父集合中的值。
此合并行为类似地适用于,和集合类型。 在元素的特定情况下,将维护与List集合类型关联的语义(即,值的有序集合的概念)。 父级的值先于子级列表的所有值。 对于“Map”,“集合”和“属性”集合类型,不存在任何排序。 因此,对于容器内部使用的关联Map,Set和Properties实现类型基础的集合类型,没有任何排序语义有效。
不能合并不同的集合类型(例如Map和List)。 如果 尝试这样做,则会引发适当的Exception。 必须在下面的继承的子定义中指定merge属性。 在父集合定义上指定merge属性是多余的,不会导致所需的合并。
强类型集合
随着Java 5中泛型类型的引入, 可以使用强类型集合。 也就是说,可以声明一个Collection类型,使其只能包含(例如)String元素。 如果 使用Spring将强类型的Collection依赖注入到Bean中,则可以利用Spring的类型转换支持,以便在将强类型的Collection实例的元素转换为适当的类型,然后再添加到其中。 采集。 以下Java类和bean定义显示了如何执行此操作:
public class SomeClass {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="something" class="x.y.SomeClass">
<property name="accounts">
<map>
<entry key="one" value="9.99"/>
<entry key="two" value="2.75"/>
<entry key="six" value="3.99"/>
map>
property>
bean>
beans>
当准备注入bean的accounts属性时,可以通过反射获得有关强类型Map
空字符串值和空字符串值
Spring将属性等的空参数视为空String。 以下基于XML的配置元数据片段将email属性设置为空的String值(“”)。
<bean class="ExampleBean">
<property name="email" value=""/>
bean>
exampleBean.setEmail("");
元素处理空值。 以下清单显示了一个示例:
<bean class="ExampleBean">
<property name="email">
<null/>
property>
bean>
==>
exampleBean.setEmail(null);
带p-namespace命名空间的XML快捷方式
使用p-namespace, 可以使用bean元素的属性(而不是嵌套的元素)来描述协作bean的属性值,或同时使用两者。
Spring支持带有名称空间的可扩展配置格式,这些名称空间基于XML Schema定义。 本章讨论的bean配置格式在XML Schema文档中定义。 但是,p-namespace未在XSD文件中定义,仅存在于Spring的核心中。
以下示例显示了两个XML代码段(第一个使用标准XML格式,第二个使用p-namespace),它们可以解析为相同的结果:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="[email protected]"/>
bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="[email protected]"/>
beans>
该示例显示了p-namespace中的一个属性,该属性在bean定义中称为email。 这告诉Spring包含一个属性声明。 如前所述,p-namespace没有架构定义,因此可以将属性名称设置为属性名称。
下一个示例包括另外两个bean定义,它们都引用了另一个bean:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
bean>
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
bean>
beans>
此示例不仅包括使用p-namespace的属性值,而且还使用特殊格式来声明属性引用。 第一个bean定义使用spouse
是属性名称,而-ref
部分表示这不是一个直接值,而是对另一个bean的引用。
注意
p-namespace不如标准XML格式灵活。 例如,声明属性引用的格式与以Ref结尾的属性发生冲突,而标准XML格式则没有。 我们建议 仔细选择方法,并将其传达给团队成员,以避免同时使用这三种方法生成XML文档。
具有c-namespace的XML快捷方式
与带有p-namespace的XML快捷方式类似,在Spring 3.1中引入的c-namespace允许使用内联属性来配置构造函数参数,而不是嵌套的builder-arg元素。
以下示例使用c:命名空间执行与基于构造函数的依赖注入相同的操作
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="[email protected]"/>
bean>
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="[email protected]"/>
beans>
c-namespace使用与p-namespace相同的约定(Bean引用为尾随-ref)以按名称设置构造函数参数。 同样,即使未在XSD模式中定义它(也存在于Spring内核中),也需要在XML文件中声明它。
对于极少数情况下无法使用构造函数自变量名称的情况(通常,如果字节码是在没有调试信息的情况下编译的),可以对参数索引使用后备,如下所示:
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="[email protected]"/>
注意
由于XML语法的原因,索引符号要求前导_
的存在,因为XML属性名称不能以数字开头(即使某些IDE允许)。 相应的索引符号也可用于元素,但并不常用,因为声明的普通顺序在那里就足够了。
实际上,构造函数解析机制在匹配参数方面非常有效,因此除非 确实需要,否则我们建议在整个配置过程中使用名称表示法。
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
bean>
Something bean具有fred属性,该属性具有bob属性,该属性具有sammy属性,并且最终的sammy属性被设置为123的值。为了使其正常工作,something的fred属性和bob属性 构造bean后,fred的of不能为null。 否则,将引发NullPointerException。
如果一个bean是另一个bean的依赖项,则通常意味着将一个bean设置为另一个bean的属性。 通常, 可以使用基于XML的配置元数据中的元素来完成此操作。 但是,有时bean之间的依赖性不太直接。 一个示例是何时需要触发类中的静态初始值设定项,例如用于数据库驱动程序注册。 依赖属性可以显式地强制初始化一个或多个使用该元素的bean之前的bean。 下面的示例使用depends-on属性来表示对单个bean的依赖关系:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
要表达对多个bean的依赖关系,请提供一个bean名称列表作为depends-on属性的值(逗号,空格和分号是有效的定界符):
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
注意
Depends-on属性既可以指定初始化时间依赖性,也可以仅在单例bean的情况下指定相应的销毁时间依赖性。 与给定bean定义依赖关系的从属bean首先被销毁,然后再销毁给定bean本身。 因此,依赖也可以控制关闭顺序
比如说是bean a 有一个值默认值 bean b 是一个队默认值修改的方法 bean c 必须要拿到修改后的值 这个时候就必须控制bean b 的创建在c,但是吧和c之前 之前有没有依赖关系 这个时候c 上面就可以添加这个属性值
默认情况下,作为初始化过程的一部分,ApplicationContext实现会急于创建和配置所有单例bean。 通常,这种预初始化是可取的,因为与数小时甚至数天后相比,会立即发现配置或周围环境中的错误。 如果不希望出现这种情况,则可以通过将bean定义标记为延迟初始化来防止对singleton bean的预先实例化。 延迟初始化的bean告诉IoC容器在首次请求时而不是在启动时创建一个bean实例。
在XML中,此行为由元素上的lazy-init属性控制
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
当前面的配置被ApplicationContext占用时,在ApplicationContext启动时不会急切地预先实例化懒惰的bean,而在not.lazy Bean中则急切地预先实例化。
但是,如果延迟初始化的bean是未延迟初始化的单例bean的依赖项,则ApplicationContext在启动时会创建延迟初始化的bean,因为它必须满足单例的依赖关系。 延迟初始化的bean被注入到其他未延迟初始化的单例bean中。
还可以通过使用元素上的default-lazy-init属性在容器级别控制延迟初始化,以下示例显示
<beans default-lazy-init="true">
beans>
Spring容器可以自动装配协作bean之间的关系。 可以通过检查ApplicationContext的内容,让Spring为 的bean自动解决协作者(其他bean)。 自动装配具有以下优点:
自动装配可以显著的减少指定属性或构造函数参数的需要 .(在这方面,其他机制 例如本章讨论的bean模板) 也是很有价值的
随着对象的发展,自动装配可以更新配置,比如说,如果你需要为你的class 添加一个依赖,这个依赖可以满足而不是需要你去修改配置,所以自动装配是在开发中非常有用的
当你是用XML 的法式配置元数据的时候,你可以指定autowire
属性值来设置自动装配的模式在 里面
自动装配功能具有四种模式,可以为每个bean指定自动装配,因此可以选择要自动装配的装配。 下表描述了四种自动装配模式:
模式 | 说明 |
---|---|
no |
(默认)无自动装配。 Bean引用必须由ref元素定义。 对于较大的部署,建议不要更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。 在某种程度上,它记录了系统的结构。 |
byName 根据名字自动注入 |
按属性名称自动装配。 Spring寻找与需要自动装配的属性同名的bean。 例如,如果一个bean定义被设置为按名称自动装配,并且包含一个master属性(即,它具有setMaster(…)方法),那么Spring将查找一个名为master的bean定义,并使用它来设置该属性。 |
byType 根据类型自动注入 |
如果容器中恰好存在一个该属性类型的bean,则使该属性自动装配。 如果存在多个,则将引发致命异常,这表明可能不对该bean使用byType自动装配。 如果没有匹配的bean,则什么都不会发生(未设置该属性)。 |
constructor 构造器自动注入 |
与byType类似,但适用于构造函数参数。 如果容器中不存在构造函数参数类型的一个bean,则将引发致命错误。 |
使用byType
或constructor
自动装配模式,你可以装配数组和类型的集合,在这种情况下,将提供容器中与期望类型匹配的所有自动装配候选,以满足相关性。如果预期键类型为String,则可以自动装配强类型Map实例。 自动关联的Map实例的值包含所有与预期类型匹配的bean实例,并且Map实例的键包含相应的bean名称。
自动装配的局限性和缺点
在整个项目中一致使用自动装配时,效果最好.如果通常不使用自动装配,则可能使开发人员仅使用自动装配来连接一个或两个bean定义而感到困惑。
考虑自动装配的局限性和缺点:
属性和构造器参数设置中的显式依赖关系始终会覆盖自动装配。 无法自动连接简单属性,例如基本类型,字符串和类(以及此类简单属性的数组)。 此限制是设计使然。
自动装配不如显式装配精确。 尽管如前所述,Spring还是谨慎避免在可能产生意外结果的模棱两可的情况下进行猜测。 Spring管理的对象之间的关系不再明确记录。
接线信息可能不适用于可能从Spring容器生成文档的工具。
容器内的多个bean定义可能与要自动装配的setter方法或构造函数参数指定的类型匹配。 对于数组,集合或Map实例,这不一定是问题。 但是,对于需要单个值的依赖项,不会任意解决此歧义。 如果没有唯一的bean定义可用,则会引发异常。
在后一种情况下,有几种选择:
放弃自动装配,转而使用明确的装配。
通过将bean的autowire-candidate属性设置为false,避免自动装配bean定义,如下一节所述。
通过将其元素的primary属性设置为true,将单个bean定义指定为主要候选对象。
如基于注解的容器配置中所述,通过基于注解的配置实现更细粒度的控件。
从自动装配中排除Bean
在每个bean的基础上, 可以从自动装配中排除一个bean,使用Spring的XML格式,将元素的autowire-candidate属性设置为false.容器使该特定的bean定义不适用于自动装配基础结构
注意
autowire-candidate属性设计为仅影响基于类型的自动装配。 它不会影响按名称显示的显式引用,即使指定的Bean未标记为自动装配候选,该名称也可得到解析。 因此,如果名称匹配,按名称自动装配仍然会注入Bean。你还可以限制自动装配候选项基于Bean名称模式匹配,顶级的标签 在default-autowire-candidates
属性内部接收一个或者多个模式,比如,要将自动装配候选状态限制为名称+Respository 结尾的任何bean,请提供Respository的名称.要提供多种模式,请在,逗号分隔符的列表里面定义他们.bean定义的autowrie-candidate s属性的显示值true/false 始终优先,对于此类bean,模式匹配规则不适用。
这些技术对于 不希望通过自动装配将其注入其他bean的bean非常有用。 这并不意味着排除的bean本身不能使用自动装配进行配置。 相反,bean本身不是自动装配其他bean的候选对象
一个解决方案是放弃某些控制反转。 可以通过实现ApplicationContextAware接口使bean A知道容器,然后,通过每次容器A进行一次getBean(“ B”)调用来请求一个(通常是新的)bean B实例。 以下示例显示了此方法:
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
前面的内容是不理想的,因为业务代码知道并耦合到Spring框架。 方法注入是Spring IoC容器的一项高级功能,可让 清晰地处理此用例. 可以在此博客文章中了解有关方法注入动机的更多信息。
查找方法注入
查找方法注入是容器覆盖容器管理的Bean上的方法并返回容器中另一个命名Bean的查找结果的能力。查找通常涉及原型bean,如上一节所述的情况。Spring框架通过使用从CGLIB库生成字节码来动态生成覆盖该方法的子类来实现此方法注入。
为了使此动态子类起作用,Spring Bean容器子类的类也不能是final,而要覆盖的方法也不能是final。
对具有抽象方法的类进行单元测试需要 自己对该类进行子类化,并提供该抽象方法的存根实现。
组件扫描也需要具体方法,这需要具体的类别。
另一个关键限制是,查找方法不适用于工厂方法,尤其不适用于配置类中的@Bean方法,因为在这种情况下,容器不负责创建实例,因此无法创建运行时生成的 飞行中的子类。
对于前面的代码片段中的CommandManager类,Spring容器动态地覆盖createCommand()方法的实现。 如重新编写的示例所示,CommandManager类没有任何Spring依赖项:
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
在包含要注入的方法的客户端类(在本例中为CommandManager)中,要注入的方法需要以下形式的签名:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果该方法是抽象的,则动态生成的子类将实现该方法。 否则,动态生成的子类将覆盖原始类中定义的具体方法。 考虑以下示例:
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
bean>
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
bean>
每次需要myCommand 这个bean实例的时候,表示为CommandManager 的bean就会去条用createCommand方法去生产这样的一个bean.如果确实需要myCommand bean对象,则必须小心将其部署为原型.如果是单例,则每次都返回myCommand bean的相同实例
另外,在基于注解的组件模型中, 可以通过@Lookup注解声明一个查找方法,如以下示例所示:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
或者,更惯用的是, 可以依赖于目标bean根据lookup方法的声明的返回类型来解析:
@Lookup
protected abstract MyCommand createCommand();
请注意,通常应使用具体的存根实现声明此类带注解的查找方法.为了使它们与Spring的组件扫描规则兼容,默认情况下,抽象类将被忽略。 此限制不适用于显式注册或显式导入的Bean类。
注意
访问范围不同的目标bean的另一种方法是ObjectFactory / Provider注入点。 请参阅作用域Bean作为依赖项。
可能还会发现ServiceLocatorFactoryBean(在org.springframework.beans.factory.config包中)很有用。
任意方法替换 (跳过 不太需要了解)
当你创建了一个bean的定义,你就是创建了一个’食谱’为该bean定义所定义的一个实际对象,bean的定义 菜单非常的状态,因为这个以为着,一个类,你可以通过这个食谱创建许多的实例对象;你不仅仅可以控制要插入到从特定bean定义创建的对象中的各种依赖关系和配置值,还可以控制从特定的bean的定义创建对象的范围;
这种方法功能强大且灵活,因为 可以选择通过配置创建的对象的范围,而不必在回到Java类级别上修改对象的范围。可以将Bean定义为部署在多个范围之一中。Spring框架支持六种作用域;仅当 使用web 环境的时候ApplicationContext,其中四个才可用。 还可以创建自定义范围。
Scope | Description |
---|---|
singleton | (默认)将每个Spring IoC容器的单个bean定义范围限定为单个对象实例。 |
prototype | 将单个bean定义的作用域限定为任意数量的对象实例。 |
request | 将单个bean定义的范围限定为单个HTTP请求的生命周期。 也就是说,每个HTTP请求都有一个在单个bean定义后面创建的bean实例。 仅在web的Spring ApplicationContext上下文中有效。 |
session | 将单个bean定义的作用域限定为HTTP会话的生命周期。仅在web的Spring ApplicationContext上下文中有效。 |
application | 将单个bean定义的作用域限定为ServletContext的生命周期。仅在web的Spring ApplicationContext上下文中有效。 |
websocket | 将单个bean定义的作用域限定为WebSocket的生命周期。仅在web的Spring ApplicationContext上下文中有效。 |
注意
从Spring 3.0开始,线程作用域可用,但默认情况下未注册。 有关更多信息,请参见SimpleThreadScope文档。 有关如何注册此自定义范围或任何其他自定义范围的说明,请参阅使用自定义范围。
仅管理一个singleton bean的一个共享实例,并且所有对具有ID或与该bean定义相匹配的ID的bean的请求都将导致该特定的bean实例由Spring容器返回,换句话说,当 定义一个bean定义并且其作用域为单例时,Spring IoC容器将为该bean定义所定义的对象创建一个实例。 该单个实例存储在此类单例bean的高速缓存中,并且对该命名bean的所有后续请求和引用都返回该高速缓存的对象。 下图显示了单例作用域的工作方式:就一个相同的对象被注入
Spring的“单例豆”概念与《四人帮(Gang of Four,GANG)模式》一书中定义的单例模式有所不同。GoF单例对对象的范围进行硬编码,以便每个ClassLoader仅创建一个特定类的一个实例.最好将Spring单例的范围描述为每个容器和每个bean.这意味着,如果 在单个Spring容器中为特定类定义一个bean,则Spring容器将创建该bean定义所定义的类的一个且只有一个实例。 单例作用域是Spring中的默认作用域。 要将bean定义为XML中的单例,可以定义bean,如以下示例所示:
<bean id="accountService" class="com.something.DefaultAccountService"/>
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
Bean部署的非单一原型范围会在每次请求特定bean时创建一个新bean实例。也就是说,该Bean被注入到另一个Bean中,或者 可以通过容器上的getBean()方法调用来请求它。 通常,应将原型作用域用于所有有状态Bean,将单例作用域用于无状态Bean。
对于每次对容器请求一个bean 都会创建一个被请求的bean
(数据访问对象(DAO)通常不配置为原型,因为典型的DAO不拥有任何对话状态。对于我们而言,重用单例图的核心更为容易。)
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
与其他作用域相比,Spring不能管理原型Bean的完整生命周期。 容器实例化,配置或组装原型对象,然后将其交给客户端,而无需对该原型实例的进一步记录。 因此,尽管在不考虑作用域的情况下在所有对象上都调用了初始化生命周期回调方法,但在原型的情况下,不会调用已配置的销毁生命周期回调。 客户端代码必须清除原型作用域内的对象并释放原型Bean拥有的昂贵资源。 要使Spring容器释放由原型作用域的bean占用的资源,请尝试使用自定义bean后处理器,该处理器包含对需要清理的bean的引用。
在某些方面,Spring容器在原型作用域Bean方面的角色是Java new运算符的替代。 超过该时间点的所有生命周期管理必须由客户端处理。 (有关Spring容器中bean生命周期的详细信息,请参阅生命周期回调。)
当你的单例对象持有原型bean的依赖关系,要意识到这个将在实例化该单例对象的时候被解析.因此,如果 将依赖范围的原型范围的bean注入到单例范围的bean中,则将实例化新的原型bean,然后将依赖项注入到该singleton Bean中。 原型实例是曾经提供给单例范围的bean的唯一实例。
但是,假设 希望单例作用域的bean在运行时重复获取原型作用域的bean的新实例。 不能将原型作用域的bean依赖项注入到 的单例bean中,因为当Spring容器实例化单例bean并解析并注入其依赖项时,该注入仅发生一次。 如果在运行时不止一次需要原型bean的新实例,请参见方法注入。
Request, Session, Application, and WebSocket这几个作用域是使用在web 应用里面的,如果你是用这些作用域和常规的IOC容器结合起来,比如XmlWebApplicationContext
,引发一个IllegalStateException,抛出一个未知的bean作用域。
初始化web 配置
支持在Request, Session, Application, WebSocket级别上对Bean进行范围界定(网络范围的Bean);在定义bean之前,需要一些小的初始配置(标准作用域不需要此初始设置:单例和原型)
如何完成此初始设置取决于 的特定Servlet环境。
如果你实际上在使用SpringMVC中访问由DispatcherServlet 处理的请求中的作用域bean ,就不需要另外的处理和设置 DispatcherServlet 已经公开或者叫处理了所有的bean的相关状态;
如果你是用servlet 2.5 的web容器,在Spring的DispatcherServlet之外处理请求(例如,当使用JSF或Struts时),你需要去注册org.springframework.web.context.request.RequestContextListener` `ServletRequestListener
在Servlet 3.0 .对于Servlet 3.0+,可以使用WebApplicationInitializer以编程方式完成此操作.或者,或者对于较旧的容器,将以下声明添加到Web应用程序的web.xml文件中:
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
listener-class>
listener>
...
web-app>
另外,如果 的监听器设置存在问题,请考虑使用Spring的RequestContextFilter,过滤器映射取决于周围的Web应用程序配置.因此 必须适当地更改它。 以下清单显示了Web应用程序的过滤器部分
<web-app>
...
<filter>
<filter-name>requestContextFilterfilter-name>
<filter-class>org.springframework.web.filter.RequestContextFilterfilter-class>
filter>
<filter-mapping>
<filter-name>requestContextFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
...
web-app>
请求作用域 Request scope
bean的s定义为
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
Spring容器通过为每个HTTP请求使用loginAction bean定义来创建LoginAction bean的新实例。也就是说,loginAction bean的作用域为HTTP请求级别. 可以根据需要更改所创建实例的内部状态,应为相同的有这个bean定义创建出来的实例看不见这些状态的变换;它们特定于单个请求。 当请求完成处理时,将限制作用于该请求的Bean。
当使用注解驱动的组件或Java配置时,@ RequestScope注解可用于将组件分配给请求范围。 以下示例显示了如何执行此操作:
@RequestScope
@Component
public class LoginAction {
// ...
}
回话作用域 Session Scope
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
Spring容器通过在单个HTTP会话的生存期内使用userPreferences bean定义来创建UserPreferences bean的新实例。换句话说,用户Preferences Bean的作用域实际上是HTTP Session级别.与请求范围的Bean一样, 可以根据需要更改所创建实例的内部状状态;知道其他HTTP Session实例也在使用从同一userPreferences bean定义创建的实例时,看不到状态的这些更改,因为它们特定于单个HTTP会话,也就是说一个会话里面的userPreferences 不可以访问另一个会话里面的userPreferences ;当HTTP会话最终被丢弃时,作用于该特定HTTP会话的bean也将被丢弃。
使用注解驱动的组件或Java配置时,可以使用@SessionScope注解将组件分配给会话范围。
@SessionScope
@Component
public class UserPreferences {
// ...
}
应用作用域 Application Scope
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
Spring容器通过对整个Web应用程序使用一次appPreferences bean定义来创建AppPreferences bean的新实例。 也就是说,appPreferences bean的作用域位于ServletContext级别,并存储为常规ServletContext属性。 这有点类似于Spring单例bean,但有两个重要的区别:它是每个ServletContext的单个实例,而不是每个Spring’ApplicationContext’的单个实例(在任何给定的Web应用程序中可能都有多个),并且实际上是公开的,因此 可见为ServletContext属性。
使用注解驱动的组件或Java配置时,可以使用@ApplicationScope注解将组件分配给应用程序范围。 以下示例显示了如何执行此操作
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
bean 的范围作为依赖
Spring IoC容器不仅管理对象(bean)的实例化,而且还管理协作者(或依赖项)的连接;如果 要将HTTP请求范围的Bean注入(例如)一个寿命更长的范围的另一个Bean,则可以选择注入AOP代理来代替该范围的Bean,也就是说, 需要注入一个代理对象,该对象公开与范围对象相同的公共接口,但也可以从相关范围(例如HTTP请求)中检索实际目标对象,并将方法调用委托给实际对象。
注意:
你还可以为单例bean使用
当针对范围原型的bean声明
同样,作用域代理不是以生命周期安全的方式从较短的作用域访问bean的唯一方法。 也可以将注入点(即构造函数或setter参数或自动装配的字段)声明为ObjectFactory ,从而允许getObject()调用在需要时按需检索当前实例。 实例或将其单独存储。
作为扩展变体, 可以声明ObjectProvider ,它提供了几个附加的访问变体,包括getIfAvailable和getIfUnique。
JSR-330的这种变体称为Provider,并与Provider 声明和每次检索尝试的相应get()调用一起使用。 有关JSR-330总体的更多详细信息,请参见此处。
以下示例中的配置只有一行,但是了解其背后的“原因”和“方式”很重要:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<aop:scoped-proxy/> ====>!!!!!!
bean>
<bean id="userService" class="com.something.SimpleUserService">
<property name="userPreferences" ref="userPreferences"/>
bean>
要创建这样的代理,请将子
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
bean>
在前面的示例中,单例bean(userManager)注入了对HTTP会话范围的bean(userPreferences)的引用,这里的重点是userManager bean是一个单例,每个容器仅实例化一次,并且它的依赖项(在这种情况下,只有一个,userPreferences bean)也仅注入一次.这意味着userManager bean仅在完全相同的userPreferences对象(即最初注入该对象的对象)上操作;
也就是是说userManager 只是在实例化第一次的时候注入过一次 后面该对象操作都都是第一次注入的对象 ,和后面基于回话级别的重新实例的对象就没有什么直接的关系了 需要配置代理才不会这样
将寿命较短的作用域bean注入寿命较长的bean时,这不是 想要的行为(比如 就是把Http session 级别的协作对注入到单例的bean对象)相反, 只需要一个userManager对象,并且在HTTP会话的生存期内, 需要一个特定于HTTP会话的userPreferences对象。因此,容器创建了一个对象,该对象公开了与UserPreferences类完全相同的公共接口(理想情况下,该对象是UserPreferences实例),可以从范围界定机制(HTTP请求,会话等)中获取实际的UserPreferences对象。 容器将此代理对象注入到userManager bean中,而后者不知道此UserPreferences引用是代理。 在此示例中,当UserManager实例在注入依赖项的UserPreferences对象上调用方法时,实际上是在代理上调用方法。 然后,代理从HTTP会话(在本例中)获取真实的UserPreferences对象,并将方法调用委托给检索到的真实的UserPreferences对象。
因此,在将请求范围和会话范围的bean注入到协作对象中时, 需要以下(正确和完整)配置,如以下示例所示:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<aop:scoped-proxy/>
bean>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
bean>
总结
如果只考虑容器对bean的实例化,scoped-proxy确实没什么意义,scoped-proxy的意义在关联bean之间的依赖时才能体现。
前一个例子没有使用aop:scoped-proxy/,那么在这里userManager的作用域是singleton,容器中仅初始化一次,其作为属性userPreferences也仅被注入一次。当session失效后,容器中userManager的实例仍然存在,其属性userPreferences也随着存在,换句话说,userManager以后使用的userPreferences永远都是同一个。
但后一个例子则不一样,userManager的属性userPreferences指向的是com.foo.UserPreferences实例的代理,当session过期后,userManager的属性userPreferences自然也不能再使用,也就是userManager的作为属性时的生命周期是按照自身声明的。
那么proxy,它代理的工作就是——暴露这个bean令其符合其自身作用域。
选择要创建的代理类型
默认情况下,当Spring容器为使用
注意
CGLIB代理仅拦截公共方法调用! 不要在这样的代理上调用非公共方法。 它们没有被委派给实际的作用域目标对象。
另外, 可以通过为
以下示例显示了基于接口的代理:
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
bean>
<bean id="userManager" class="com.stuff.UserManager">
<property name="userPreferences" ref="userPreferences"/>
bean>
有关选择基于类或基于接口的代理的更多详细信息,请参阅代理机制。
bean的作用域机制是可扩展的。 可以定义自己的范围,甚至重新定义现有范围,尽管后者被认为是不好的做法,并且不能覆盖内置的singleton和原型范围
创建一个自定义作用域
要将自定义作用域集成到Spring容器中, 需要实现org.springframework.beans.factory.config.Scope接口,这将在本节中进行介绍。 有关如何实现自己的作用域的想法,请参阅Spring框架本身提供的Scope实现和Scope javadoc,其中详细说明了需要实现的方法。
作用域接口有四种方法可以从作用域获取对象,将它们从作用域中删除,然后销毁它们。
例如,会话范围实现返回会话范围的Bean(如果不存在,则该方法将其绑定到会话上以供将来参考之后,将返回该Bean的新实例)。 以下方法从基础范围返回对象:
Object get(String name, ObjectFactory<?> objectFactory)
会话范围的实现,例如,从基础会话中删除了会话范围的bean。 应该返回该对象,但是如果找不到具有指定名称的对象,则可以返回null。 以下方法从基础范围中删除该对象:
Object remove(String name)
以下方法注册在销毁作用域或销毁作用域中的指定对象时应执行的回调:
void registerDestructionCallback(String name, Runnable destructionCallback)
有关销毁回调的更多信息,请参见javadoc或Spring范围实现。
以下方法获取基础范围的会话标识符:
String getConversationId()
使用自定义作用域
在编写和测试一个或多个自定义范围实现之后,需要使Spring容器知道 的新范围。 以下方法是在Spring容器中注册新范围的主要方法:
void registerScope(String scopeName, Scope scope);
此方法在ConfigurableBeanFactory接口上声明,该接口可通过Spring附带的大多数具体ApplicationContext实现上的BeanFactory属性获得。
registerScope(…)方法的第一个参数是与范围关联的唯一名称。 Spring容器本身中的此类名称示例包括单例和原型。 registerScope(…)方法的第二个参数是 希望注册和使用的自定义Scope实现的实际实例。
假设 编写了自定义的Scope实现,然后注册它,如下面的示例所示
注意
下一个示例使用SimpleThreadScope,它包含在Spring中,但默认情况下未注册。 对于 自己的自定义范围实现,说明将是相同的。
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
然后, 可以按照 的自定义范围的作用域规则创建bean定义,如下所示:
<bean id="..." class="..." scope="thread">
使用自定义范围实现,不仅限于以编程方式注册范围。 还可以使用CustomScopeConfigurer类以声明方式进行范围注册,如以下示例所示:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
entry>
map>
property>
bean>
<bean id="thing2" class="x.y.Thing2" scope="thread">
<property name="name" value="Rick"/>
<aop:scoped-proxy/>
bean>
<bean id="thing1" class="x.y.Thing1">
<property name="thing2" ref="thing2"/>
bean>
beans>
注意
当将
Spring框架提供了许多接口,可用于自定义Bean的性质。 本节将它们分组如下
ApplicationContextAware
and BeanNameAware
上下文bean初始化设置Aware
Interfaces 其他初始化设置为了和容器对bean生命周期的管理进行交互,你可以实现Spring initializing 和DisposableBean 接口,容器为前者调用afterPropertiesSet()并为后者调用destroy(),以使Bean在初始化和销毁Bean时执行某些操作。
注意
在现代Spring应用程序中,JSR-250 @PostConstruct和@PreDestroy注解通常被认为是接收生命周期回调的最佳实践。使用这些注解意味着 的bean没有耦合到特定于Spring的接口。 有关详细信息,请参见使用@PostConstruct和@PreDestroy。
如果不想使用JSR-250注解,但仍然想删除耦合,请考虑使用init-method和destroy-method Bean定义元数据。
在内部,Spring框架使用BeanPostProcessor实现来处理它可以找到的任何回调接口并调用适当的方法。 如果 需要自定义功能或其他生命周期行为,Spring默认不提供,则 可以自己实现BeanPostProcessor。 有关更多信息,请参见容器扩展点。
除了初始化和销毁回调,Spring 管理的对象还可以实现Lifecycle接口,以便这些对象可以在容器自身的生命周期的驱动下参与启动和关闭过程。
本节介绍了生命周期回调接口。
初始化回调
使用org.springframework.beans.factory.InitializingBean接口,容器在容器上设置了所有必需的属性后,就可以执行初始化工作。 InitializingBean接口指定一个方法:
void afterPropertiesSet() throws Exception;
我们建议不要使用InitializingBean接口,因为它不必要地将代码耦合到Spring。 另外,我们建议使用@PostConstruct注解或指定POJO初始化方法,对于基于XML的配置元数据,可以使用init-method属性指定具有无参数签名的方法的名称。通过Java配置,可以使用@Bean的initMethod属性。 请参阅接收生命周期回调。 考虑以下示例:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
前面的示例与下面的示例(包含两个清单)几乎具有完全相同的效果:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}
但是,前面两个示例中的第一个示例并未将代码耦合到Spring。
销毁回调
通过实现org.springframework.beans.factory.DisposableBean接口,当包含bean的容器被销毁时,它可以获取回调。 DisposableBean接口指定一个方法:
void destroy() throws Exception;
我们建议 不要使用DisposableBean回调接口,因为它不必要地将代码耦合到Spring。 另外,我们建议使用@PreDestroy注解或指定bean定义支持的通用方法。 使用基于XML的配置元数据时,可以在上使用destroy-method属性。 通过Java配置,可以使用@Bean的destroyMethod属性。 请参阅接收生命周期回调。 考虑以下定义
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
前面的定义与下面的定义几乎具有完全相同的效果:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
@Override
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但是,前面两个定义中的第一个没有将代码耦合到Spring。
注意
可以为元素的destroy-method属性分配一个特殊的(推断的)值,该值指示Spring自动检测特定bean类上的公共关闭或关闭方法。 (因此,任何实现java.lang.AutoCloseable或java.io.Closeable的类都将匹配。) 还可以在元素的default-destroy-method属性上设置此特殊(推断)值,以将此行为应用于 bean(请参阅默认初始化和销毁方法)。 请注意,这是Java配置的默认行为
默认初始化和销毁方法
当编写不使用Spring特定的InitializingBean和DisposableBean回调接口的初始化和销毁方法回调时,通常会使用诸如init(),initialize(),dispose()等名称编写方法。 理想情况下,此类生命周期回调方法的名称应在整个项目中标准化,以便所有开发人员都使用相同的方法名称并确保一致性。
可以将Spring容器配置为“寻找”命名的初始化,并销毁每个bean上的回调方法名称。 这意味着,作为应用程序开发人员, 可以编写应用程序类并使用称为init()的初始化回调,而不必为每个bean定义配置init-method =“ init”属性。 Spring IoC容器在创建bean时(并根据前面描述的标准生命周期回调协定)调用该方法。 此功能还对初始化和销毁方法回调强制执行一致的命名约定。
假设 的初始化回调方法命名为init(),而destroy回调方法命名为destroy()。 然后, 的class似于以下示例中的class:
public class DefaultBlogService implements BlogService {
private BlogDao blogDao;
public void setBlogDao(BlogDao blogDao) {
this.blogDao = blogDao;
}
// this is (unsurprisingly) the initialization callback method
public void init() {
if (this.blogDao == null) {
throw new IllegalStateException("The [blogDao] property must be set.");
}
}
}
然后, 可以在类似于以下内容的Bean中使用该类:
<beans default-init-method="init">
<bean id="blogService" class="com.something.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
bean>
beans>
顶级的beans 标签里面日安家default-init-mothod 属性,导致spring ioc 容器将在bean的方法识别上识别到bean默认的初始化初始方法,在创建bean和组装bean的时候,如果bean里面有default-init-mothod 标签定义的方法,则会在适当的时候去调用它, 类似的 你也可以在beans的标签上面配置所有该标签下面的bean对象的distrory 方法回调 标签是 default-destrory-method
如果beans顶级标签里面的bean里面已经有销毁和初始化方法 则以bean 标签里面的为准,会覆盖外面的beans 里面的初始化和销毁时候的方法.
Spring容器保证在为bean提供所有依赖项后立即回调配置的初始化方法,因此在原始的bean 引用调用了初始化回调,这意味着AOP拦截器等尚未作用于bean. 首先会额安全创建目标bean,然后在应用带有其拦截器链的AOP 代理
如果目标Bean和代理分别定义,则 的代码甚至可以绕过代理与原始目标Bean进行交互。 因此,将拦截器应用于init方法是不一致的,因为这样做会将目标Bean的生命周期耦合到其代理或拦截器,并且当 的代码直接与原始目标Bean进行交互时会留下奇怪的语义。
总结 就是初始化方法 一般拦截器拦截不到 也不会这样使用
组合生命周期机制
从Spring 2.5开始, 可以使用三个选项来控制Bean生命周期行为:
InitializingBean和DisposableBean回调接口
自定义init()和destroy()方法
@PostConstruct和@PreDestroy注解。 可以结合使用这些机制来控制给定的bean。
注意
如果为一个bean配置了多个生命周期机制,并且为每个机制配置了不同的方法名称,则将按照此注解后列出的顺序执行每个已配置的方法。 但是,如果为多个生命周期机制中的多个生命周期机制配置了相同的方法名称(例如,为初始化方法使用init()则该方法将执行一次,如上一节所述。
就是 xml 配置了 注解也加了 同样的方法 方法只会执行一次
为同一个bean配置的具有不同初始化方法的多种生命周期机制如下:
@PostConstruct
afterPropertiesSet()
由InitializingBean
回调接口定义init()
方法销毁方法的调用顺序相同:
用@PreDestroy
注解的方法
由DisposableBean回调接口定义的destroy()
定制配置的destroy(
方法
启动和关机回调
Lifecycle接口为具有自己的生命周期要求(例如启动和停止某些后台进程)的任何对象定义了基本方法:
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何Spring管理的对象都可以实现Lifecycle接口.然后,当ApplicationContext本身接收到启动和停止信号时(例如,对于运行时的停止/重新启动方案),它将把这些调用级联到在该上下文中定义的所有Lifecycle实现。 它通过委派给LifecycleProcessor
来做到这一点,如以下清单所示:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
请注意,LifecycleProcessor
本身是Lifecycle
接口的扩展。 它还添加了其他两种方法来对正在刷新和关闭的上下文做出反应。
任何spring管理的对象都可以实现这个接口。那么,当ApplicationContext自身启动和停止时,它将自动调用上下文内所有生命周期的实现。通过委托给LifecycleProcessor
来做这个工作。注意LifecycleProcessor
自身扩展了Lifecycle接口。它也增加了两个其他的方法来与上下文交互,使得可以刷新和关闭。`
注意
请注意,常规的org.springframework.context.Lifecycle接口是用于显式启动和停止通知的普通协议,并不意味着在上下文刷新时自动启动。 为了对特定bean的自动启动(包括启动阶段)进行细粒度的控制,请考虑改为实现org.springframework.context.SmartLifecycle。
另外,请注意,不能保证会在销毁之前发出停止通知。 在常规关闭时,在传播常规销毁回调之前,所有Lifecycle bean首先都会收到停止通知。 但是,在上下文生命周期内进行热刷新或刷新尝试失败时,仅调用destroy方法
启动和关闭调用的顺序可能很重要。 如果任何两个对象之间存在“依赖”关系,则依赖方在其依赖之后开始,而在依赖之前停止.但是,有时直接依赖项是未知的。 可能只知道某种类型的对象应该先于另一种类型的对象开始.在这些情况下,SmartLifecycle接口定义了另一个选项,即在其超级接口Phased上定义的getPhase()方法。 以下清单显示了Phased接口的定义:
public interface Phased {
int getPhase();
}
以下清单显示了SmartLifecycle
接口的定义:
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
启动时,相位最低的首先启动.停止时,遵循相反的顺序.因此实现SmartLiftCycle
并且getPhase()
方法如果返回值是Integer.MIN_VALUE 的话 ,此种对象就是首先启动并且是最后一个关闭的.于此相反的是 ,相位值是Interger.MAX_VALUE 的话,该对象就是最后启动(可能是依赖于其他的对象先启动,后面该对象在启动),并且是最先开始关闭.何未实现SmartLifecycle
的“正常” Lifecycle对象的默认阶段为0。因此,任何负相位值都表明对象应在这些标准组件之前开始(并在它们之后停止)。 对于任何正相位值,反之亦然。
SmartLifecycle
定义了stop 方法并接受方法回调.任何实现动必须在该实现的关闭过程完成后调用该回调的run()
方法,这将在必要的时候启用异步关闭.应为LifecycleProcessor 会等待其超时值,等待每个阶段内的对象组调用该回调函数.默认的每阶段超时是30秒.你可以通过在上下文中定义一个名为LifecycleProcessor 的bean 来覆盖默认的生命周期处理器实例,如果只是想修改,则定义一下的内容就可以了:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<property name="timeoutPerShutdownPhase" value="10000"/>
bean>
就像前面说的那样,LifecycleProcessor 接口还定义了用于刷新和关闭上下文的回调方法.后者驱动关闭过程,就好像显示的调用stop() ,但是在他是在上下文关闭的时候发生.另一方面,刷新回调启用SmartLifecycle bean的另外一个功能,刷新上下文时(在所有的对象都在被实例化和初始化后),该回调被调用,此时默认的生命周期处理器对象对检查每个SmartLifecycle 对象的isAutoStartup() 方法.如果返回的是true 则就在该点启动该对象,而不是在等待上下文或者它自己的start() 方法的显示调用(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。 相位值和任何“依赖”关系决定了启动顺序,如前所述。
在非Web应用程序中正常关闭Spring IoC容器
本节仅适用于非Web应用程序。 Spring的基于Web的ApplicationContext实现已经具有适当的代码,可以在相关的Web应用程序关闭时正常关闭Spring IoC容器。
如果 在非Web应用程序环境中(例如,在富客户端桌面环境中)使用Spring的IoC容器,请向JVM注册一个关闭钩子。 这样做可以确保正常关机,并在 的Singleton bean上调用相关的destroy方法,以便释放所有资源。 仍然必须正确配置和实现这些destroy回调。
要注册关闭挂钩,请调用在ConfigurableApplicationContext接口上声明的registerShutdownHook()方法,如以下示例所示:
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Boot {
public static void main(final String[] args) throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// add a shutdown hook for the above context...
ctx.registerShutdownHook();
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
}
当applicationContext
创建了一个实现了org.springframework.context.ApplicationContextAware
接口的实例的时候.实例提供了对该ApplicationContext的引用。以下清单显示了ApplicationContextAware接口的定义:
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,bean可以通过ApplicationContext接口或通过将引用转换为该接口的已知子类(例如ConfigurableApplicationContext,它公开了其他功能)来以编程方式操纵创建它们的ApplicationContext。 一种用途是通过编程方式检索其他bean。 有时,此功能很有用。 但是,通常应避免使用它,因为它将代码耦合到Spring,并且不遵循控制反转样式,在该样式中,将协作者作为属性提供给bean。 ApplicationContext的其他方法提供对文件资源的访问,发布应用程序事件以及访问MessageSource。 这些附加功能在ApplicationContext的其他功能中进行了描述。
自动装配是获得对ApplicationContext的引用的另一种选择。 传统的构造函数和byType自动装配模式(如“自动装配协作器”中所述)可以分别为构造函数参数或setter方法参数提供ApplicationContext类型的依赖项。 要获得更大的灵活性,包括能够自动连接字段和使用多个参数方法,请使用基于注解的自动装配功能。 如果这样做,则将ApplicationContext自动连接到需要使用ApplicationContext类型的字段,构造函数参数或方法参数中(如果有问题的字段,构造函数或方法带有@Autowired注解)。 有关更多信息,请参见使用@Autowired。
当ApplicationContext创建实现org.springframework.beans.factory.BeanNameAware接口的类时,该类将获得对在其关联对象定义中定义的名称的引用。 以下清单显示了BeanNameAware接口的定义:
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
在填充正常的bean属性之后,但在初始化回调(例如InitializingBean,afterPropertiesSet或自定义init-method)之前,调用该回调.
Name | 注入依赖 | 解释 |
---|---|---|
ApplicationContextAware |
声明ApplicationContext . |
ApplicationContextAware and BeanNameAware |
ApplicationEventPublisherAware |
事件发布关闭ApplicationContext . |
Additional Capabilities of the ApplicationContext |
BeanClassLoaderAware |
类加载器,用于加载Bean类。 | Instantiating Beans |
BeanFactoryAware |
声明BeanFactory . |
ApplicationContextAware and BeanNameAware |
BeanNameAware |
声明bean的名称。 | ApplicationContextAware and BeanNameAware |
BootstrapContextAware |
容器在其中运行资源适配器BootstrapContext。通常仅在支持JCA的ApplicationContext实例中可用。 | JCA CCI |
`定义的编织器,用于在加载时处理类定义。 | 定义的编织器,用于在加载时处理类定义。 | Load-time Weaving with AspectJ in the Spring Framework |
MessageSourceAware |
解决消息的已配置策略(支持参数化和国际化) | Additional Capabilities of the ApplicationContext |
NotificationPublisherAware |
Spring JMX通知发布者 | Notifications |
ResourceLoaderAware |
配置的加载程序,用于对资源的低级别访问。 | Resources |
ServletConfigAware |
容器在其中运行的当前ServletConfig。仅在可感知网络的Spring ApplicationContext中有效。 | Spring MVC |
ServletContextAware |
容器在其中运行的当前ServletContext。仅在可感知网络的Spring ApplicationContext中有效。 | Spring MVC |
再次注意,使用这些接口会将 的代码与Spring API绑定在一起,并且不遵循“控制反转”样式。 因此,我们建议将它们用于需要以编程方式访问容器的基础结构Bean。
Bean定义可以包含许多配置信息,包括构造函数参数,属性值和特定于容器的信息,例如初始化方法,静态工厂方法名称等。 子bean定义从父定义继承配置数据。 子定义可以覆盖某些值或根据需要添加其他值。 使用父bean和子bean定义可以节省很多输入。 实际上,这是一种模板形式。
如果以编程方式使用ApplicationContext接口,则子bean定义由ChildBeanDefinition类表示。 大多数用户不在此级别上与他们合作。 相反,它们在诸如ClassPathXmlApplicationContext之类的类中声明性地配置bean定义。 当使用基于XML的配置元数据时,可以通过使用parent属性(将父bean指定为该属性的值)来指示子bean定义。 以下示例显示了如何执行此操作
<bean id="inheritedTestBean" abstract="true"
class="org.springframework.beans.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
bean>
<bean id="inheritsWithDifferentClass"
class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBean" init-method="initialize">
<property name="name" value="override"/>
bean>
注意``parent属性。
如果未指定子bean定义,则使用父定义中的bean类,但也可以覆盖它。 在后一种情况下,子bean类必须与父类兼容(也就是说,它必须接受父类的属性值)。
子bean定义从父对象继承作用域 scope,构造函数参数值,属性值和方法替代,并可以选择添加新值。 指定的任何范围,初始化方法,destroy方法或静态工厂方法设置都会覆盖相应的父设置。
其余设置始终从子定义中获取:依赖项,自动装配模式,依赖项检查,单例和惰性初始化。
前面的示例使用abstract属性将父bean定义显式标记为abstract。 如果父定义未指定类,则需要将父bean定义显式标记为抽象,如以下示例所示:
<bean id="inheritedTestBeanWithoutClass" abstract="true">
<property name="name" value="parent"/>
<property name="age" value="1"/>
bean>
<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBeanWithoutClass" init-method="initialize">
<property name="name" value="override"/>
bean>
父bean不能单独实例化,因为它不完整,并且还被明确标记为抽象。 当定义是抽象的时,它只能用作纯模板bean定义,用作子定义的父定义。 通过将其称为另一个bean的ref属性或使用父bean ID进行显式getBean()调用来尝试单独使用这样的抽象父bean会返回错误。 同样,容器的内部preInstantiateSingletons()方法将忽略定义为抽象的bean定义。
注意;
默认情况下,ApplicationContext会预先实例化所有单例。 因此,重要的是(至少对于单例bean),如果有一个(父)bean定义仅打算用作模板,并且此定义指定了一个类,则必须确保将abstract属性设置为true ,否则应用程序上下文将实际上(试图)预先实例化抽象Bean。
通常,应用程序开发人员不需要为ApplicationContext实现类提供子类。 相反,可以通过插入特殊集成接口的实现来扩展Spring IoC容器。 接下来的几节描述了这些集成接口。
BeanPostProcessor接口定义了回调方法, 可以实施这些回调方法以提供自己的(或覆盖容器的默认值)实例化逻辑,等等。 如果 想在Spring容器完成实例化,配置和初始化bean之后实现一些自定义逻辑,则可以插入一个或多个自定义BeanPostProcessor实现
可以配置多个BeanPostProcessor实例,并且可以通过设置order属性来控制这些BeanPostProcessor实例的执行顺序。 仅当BeanPostProcessor实现Ordered接口时,才可以设置此属性。 如果 编写自己的BeanPostProcessor,则也应该考虑实现Ordered接口。 有关更多详细信息,请参见BeanPostProcessor和Ordered接口的javadoc。 另请参见有关以编程方式注册BeanPostProcessor实例的说明。
注意
BeanPostProcessor实例在bean(或对象)实例上运行。 即,Spring IoC容器实例化一个bean实例,然后BeanPostProcessor实例完成其工作。BeanPostProcessor实例是按容器划分作用域的。 仅在使用容器层次结构时,这才有意义。 如果在一个容器中定义BeanPostProcessor,则仅后处理该容器中的bean。 换句话说,一个容器中定义的Bean不会被另一个容器中定义的BeanPostProcessor进行后处理,即使这两个容器是同一层次结构的一部分也是如此。
要更改实际的bean定义(即定义bean的蓝图), 需要使用BeanFactoryPostProcessor,如使用BeanFactoryPostProcessor自定义配置元数据中所述。
总结就是: BeanFactoryPostProcessor 看他的意思就是bean工厂后置处理器,在工厂里面 可以获取到bean的许多元数据信息.也就是说可以是在bean被IOC 工厂创建之前就可以对bean的元数据做一些修改的操作.而beanPostProcessor 就是bean的后置处理器 也就是在bean对象在被初始化后对对象的bean进行操作,在这个接口里面有两个需要实现的方法 分别对应得执行时间是配置的init-mothed 前面和后面;
org.springframework.beans.factory.config.BeanPostProcessor接口恰好由两个回调方法组成。 当此类被注册为容器的后处理器时,对于容器创建的每个bean实例,后处理器都会在容器初始化方法(例如InitializingBean.afterPropertiesSet()或 任何声明的init方法),并在任何bean初始化回调之后被调用。 后处理器可以对bean实例执行任何操作,包括完全忽略回调。 Bean后处理器通常检查回调接口,或者可以用代理包装Bean。 一些Spring AOP基础结构类被实现为bean后处理器,以提供代理包装逻辑。AOP的一些原理就是类似于后置处理器;
ApplicationContext自动检测实现BeanPostProcessor接口的配置元数据中定义的所有bean。 ApplicationContext将这些bean注册为后处理器,以便以后在bean创建时可以调用它们。 Bean后处理器可以与其他Bean相同的方式部署在容器中。
注意,在配置类上使用@Bean工厂方法声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身,或者至少是org.springframework.beans.factory.config.BeanPostProcessor接口。 指示该bean的后处理器性质。 否则,ApplicationContext无法在完全创建之前按类型自动检测它。 由于BeanPostProcessor需要及早实例化才能应用于上下文中其他bean的初始化,因此这种早期类型检测至关重要。
以代码方式注册BeanPostProcessor实例
虽然建议的BeanPostProcessor注册方法是通过ApplicationContext自动检测(如前所述),但是 可以使用addBeanPostProcessor方法以编程方式将其注册到ConfigurableBeanFactory。 当 需要在注册之前评估条件逻辑,甚至需要跨层次结构的上下文复制Bean后处理器时,这将非常有用。 但是请注意,以编程方式添加的BeanPostProcessor实例不遵守Ordered接口。 在这里,注册的顺序决定了执行的顺序。 还要注意,以编程方式注册的BeanPostProcessor实例始终在通过自动检测注册的实例之前进行处理,而不考虑任何明确的顺序。
就是说添加这个后置处理器是有一个先后顺序的 先注册的是addBeanPostProcessor()注册 ,先注册先执行 也就是代码的方法这个实在前面的,这个完成了以后 然后就是容器的自动检测IOC 容器里面的其他的BeanPostProcessor bean .
BeanPostProcessor实例和AOP自动代理
实现BeanPostProcessor接口的类是特殊的,并且容器对它们的处理方式有所不同。 它们直接引用的所有BeanPostProcessor实例和Bean在启动时都会实例化,作为ApplicationContext特殊启动阶段的一部分。 接下来,以排序方式注册所有BeanPostProcessor实例,并将其应用于容器中的所有其他bean。 因为AOP自动代理是作为BeanPostProcessor本身实现的,所以BeanPostProcessor实例或它们直接引用的bean都不适合进行自动代理,因此,没有编织的方面。
对于任何这样的bean, 应该看到一条参考性的日志消息:Bean someBean不适合所有BeanPostProcessor接口进行处理(例如:不适合自动代理)。
如果 通过使用自动装配或@Resource(可能会退回到自动装配)将Bean连接到BeanPostProcessor中,则Spring在搜索类型匹配的依赖项候选对象时可能会访问意外的Bean,因此使它们不符合自动代理或其他种类的条件 Bean后处理。 例如,如果 有一个用@Resource注解的依赖项,其中字段或设置器名称不直接与bean的声明名称相对应,并且不使用name属性,那么Spring将访问其他bean以按类型匹配它们。
下面的示例演示如何在ApplicationContext中编写,注册和使用BeanPostProcessor实例
示例:Hello World,BeanPostProcessor风格
第一个示例说明了基本用法。 该示例显示了一个自定义BeanPostProcessor实现,该实现调用由容器创建的每个bean的toString()方法,并将结果字符串打印到系统控制台。
以下清单显示了自定义BeanPostProcessor实现类的定义:
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// simply return the instantiated bean as-is
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // we could potentially return any object reference here...
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
以下bean元素使用InstantiationTracingBeanPostProcessor
:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang
https://www.springframework.org/schema/lang/spring-lang.xsd">
<lang:groovy id="messenger"
script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
<lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
lang:groovy>
<bean class="scripting.InstantiationTracingBeanPostProcessor"/>
beans>
注意如何仅定义InstantiationTracingBeanPostProcessor。 它甚至没有名称,并且因为它是bean,所以可以像其他任何bean一样依赖注入。 (前面的配置还定义了一个由Groovy脚本支持的bean。Spring动态语言支持在标题为Dynamic Language Support的章节中有详细介绍。)
以下Java应用程序运行上述代码和配置
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
Messenger messenger = ctx.getBean("messenger", Messenger.class);
System.out.println(messenger);
}
}
控制台输出
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
示例:RequiredAnnotationBeanPostProcessor
将回调接口或注解与自定义BeanPostProcessor实现结合使用是扩展Spring IoC容器的常用方法。 Spring的RequiredAnnotationBeanPostProcessor是一个示例,它是Spring发行版附带的BeanPostProcessor实现,可以确保标有(任意)注解的bean上的JavaBean属性实际上(配置为)依赖注入了一个值。
BeanFactoryPostProcessor
来自定义beans我们要看的下一个扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor。 该接口的语义与BeanPostProcessor的语义相似,但有一个主要区别:BeanFactoryPostProcessor对Bean配置元数据进行操作。 也就是说,Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据,并有可能在容器实例化除BeanFactoryPostProcessor实例以外的任何bean之前更改它。
可以配置多个BeanFactoryPostProcessor实例,并且可以通过设置order属性来控制这些BeanFactoryPostProcessor实例的运行顺序。 但是,仅当BeanFactoryPostProcessor实现Ordered接口时才能设置此属性。 如果 编写自己的BeanFactoryPostProcessor,则也应该考虑实现Ordered接口。 有关更多详细信息,请参见BeanFactoryPostProcessor和Ordered接口的javadoc。
注意
如果要更改实际的bean实例(即从配置元数据创建的对象),则需要使用BeanPostProcessor(在前面的使用BeanPostProcessor自定义Bean中进行了介绍)。 从技术上讲,可以在BeanFactoryPostProcessor中使用Bean实例(例如,通过使用BeanFactory.getBean()),但这样做会导致过早的Bean实例化,从而违反了标准容器的生命周期。 这可能会导致负面影响,例如绕过bean后处理。
同样,BeanFactoryPostProcessor实例是按容器划分作用域的。 仅在使用容器层次结构时才有意义。 如果在一个容器中定义BeanFactoryPostProcessor,它将仅应用于该容器中的Bean定义。 一个容器中的Bean定义不会由另一个容器中的BeanFactoryPostProcessor实例进行后处理,即使两个容器都属于同一层次结构也是如此。
总结: BeanFactoryPostProcessor 实际上是操作bean的元数据,BeanPostProcessor 是操作bean的实例对象.但是呢在BeanFactoryPostProcessor 也可以通过BeanFactory.getBean() 拿到bean实例,但是这样会导致bean对象被过早的实例化 这样就会违反标准容器的生命周期 最好不要这样使用.
Bean工厂后处理器在ApplicationContext中声明时会自动执行,以便将更改应用于定义容器的配置元数据。 Spring包含许多预定义的bean工厂后处理器,例如PropertyOverrideConfigurer和PropertySourcesPlaceholderConfigurer。 还可以使用自定义BeanFactoryPostProcessor,例如注册自定义属性编辑器。ApplicationContext自动检测实现BeanFactoryPostProcessor接口的部署到其中的所有Bean。 它在适当的时候将这些bean用作bean工厂的后处理器。 可以像部署其他任何bean一样部署这些后处理器bean。
示例:类名替换PropertySourcesPlaceholderConfigurer
可以使用PropertySourcesPlaceholderConfigurer
通过使用标准Java属性格式将外部Bean定义的属性值外部化到单独的文件中。 这样做使部署应用程序的人员可以自定义特定于环境的属性,例如数据库URL和密码,而无需为修改容器的主要XML定义文件而复杂或冒风险。
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations" value="classpath:com/something/jdbc.properties"/>
bean>
<bean id="dataSource" destroy-method="close"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
bean>
该示例显示了从外部属性文件配置的属性。 在运行时,将PropertySourcesPlaceholderConfigurer应用于替换数据源某些属性的元数据。 将要替换的值指定为$ {property-name}格式的占位符,该格式遵循Ant和log4j和JSP EL样式。
实际值来自标准Java属性格式的另一个文件:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
因此,$ {jdbc.username}字符串在运行时将被替换为值“ sa”,并且其他与属性文件中的键匹配的占位符值也将被替换。 PropertySourcesPlaceholderConfigurer检查Bean定义的大多数属性和属性中的占位符。 此外, 可以自定义占位符前缀和后缀。
借助Spring 2.5中引入的上下文名称空间, 可以使用专用配置元素配置属性占位符。 可以在location属性中提供一个或多个位置作为逗号分隔的列表,如以下示例所示:
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
PropertySourcesPlaceholderConfigurer
不仅在 指定的属性文件中查找属性。 默认情况下,如果无法在指定的属性文件中找到属性,则会检查Spring Environment属性和常规Java System属性。
注意
可以使用PropertySourcesPlaceholderConfigurer
来替换类名称,这在 必须在运行时选择特定的实现类时有时很有用。 以下示例显示了如何执行此操作:
<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
<property name="locations">
<value>classpath:com/something/strategy.propertiesvalue>
property>
<property name="properties">
<value>custom.strategy.class=com.something.DefaultStrategyvalue>
property>
bean>
<bean id="serviceStrategy" class="${custom.strategy.class}"/>
如果无法在运行时将该类解析为有效的类,则将要创建的bean的解析将失败,这是在非懒惰加载bean的ApplicationContext的preInstantiateSingletons()阶段期间进行的。
示例:PropertyOverrideConfigurer
另一个bean工厂后处理程序PropertyOverrideConfigurer与PropertySourcesPlaceholderConfigurer相似,但是与后者不同,原始定义可以具有默认值,也可以完全没有bean属性的值。 如果覆盖的属性文件没有某个bean属性的条目,则使用默认的上下文定义。
注意,bean定义不知道会被覆盖,因此从XML定义文件中不能立即看出正在使用覆盖配置程序。 如果有多个PropertyOverrideConfigurer实例为同一个bean属性定义了不同的值,则由于覆盖机制,最后一个实例将获胜。
属性文件配置行采用以下格式:
beanName.property=value
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
该示例文件可与包含一个名为dataSource的bean的容器定义一起使用,该bean具有驱动程序和url属性。
只要路径的每个组成部分(最终属性被覆盖)之外的所有组成部分都已经为非空(可能是由构造函数初始化),则也支持复合属性名。 在下面的示例中,将tom Bean的fred属性的bob属性的sammy属性设置为标量值123:
tom.fred.bob.sammy=123
注意
指定的替代值始终是文字值。 它们不会转换为bean引用。 当XML bean定义中的原始值指定bean引用时,此约定也适用。
使用Spring 2.5中引入的上下文名称空间,可以使用专用配置元素配置属性覆盖,如以下示例所示
<context:property-override location="classpath:override.properties"/>
FactoryBean
自定义实例化逻辑可以为本身就是工厂的对象实现org.springframework.beans.factory.FactoryBean
接口。
FactoryBean接口是可插入Spring IoC容器的实例化逻辑的一点。如果 有复杂的初始化代码,更好的表达方式就是使用java代码 胃不适使用冗长的XML 配置的方式, 可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将自定义FactoryBean插入容器。
FactoryBean接口提供了三种方法:
Object getObject()
返回有工厂创建的对象,这个实例可以被共享,取决于这个工厂是否返回的是一个单例对象.boolean isSingleton()
返回true 就标识是一个FactoryBean 返回的对象是一个单例对象Class getObjectType()
返回由getObject()方法返回的对象类型;如果类型未知,则返回null。Spring框架中的许多地方都使用了FactoryBean概念和接口。 Spring附带了50多个FactoryBean接口实现
当 需要向容器要求一个实际的FactoryBean实例本身而不是由它产生的bean时,请在调用ApplicationContext的getBean()方法时在该bean的ID前面加上一个&符号。 因此,对于给定的myBean id为FactoryBean的FactoryBean,在容器上调用getBean(“ myBean”)将返回FactoryBean的乘积,而调用getBean(“&myBean”)
将返回FactoryBean实例本身。
注:释在配置Spring方面比XML更好吗?
基于注解的配置的引入提出了一个问题,即这种方法是否比XML“更好”。 简短的答案是“取决于情况”。 长话短说,每种方法都有其优缺点,通常,由开发人员决定哪种策略更适合他们。 由于定义方式的不同,注解在声明中提供了很多上下文,从而使配置更短,更简洁。 但是,XML擅长连接组件而不接触其源代码或重新编译它们。 一些开发人员更喜欢将布线放置在靠近源的位置,而另一些开发人员则认为带注解的类不再是POJO,而且,该配置变得分散且难以控制。
无论选择如何,Spring都可以容纳两种样式,甚至可以将它们混合在一起。 值得指出的是,通过其JavaConfig选项,Spring允许以非侵入方式使用注解,而无需接触目标组件的源代码,并且就工具而言,Spring Tools for Eclipse支持所有配置样式。
基于注解的配置提供了一种替代XML 配置的方式,该配置依赖于字节码元数据来连接组件,而不是使用XML 里面的尖括号声明.通过使用相关的类,方法或者字段上面的注解,开发人员无需使用XML来描述bean之间的关系,还是把配置引入了组件本身之中.如实例结合使用BeanPostProcessor和注解,RequiredAnnotationBeanPostProcessor是扩展Spring IoC容器的常用方法例如,Spring 2.0引入了使用@Required注解强制执行必需属性的可能性。 Spring 2.5使遵循相同的通用方法来驱动Spring的依赖注入成为可能。 本质上,@ Autowired注解提供的功能与自动装配协作器中描述的功能相同,但具有更细粒度的控制和更广泛的适用性。 Spring 2.5还添加了对JSR-250注解的支持,例如@PostConstruct和@PreDestroy。 Spring 3.0增加了对javax.inject包中包含的JSR-330(Java依赖注入)注解的支持,例如@Inject和@Named。 有关这些注解的详细信息,请参见相关章节。
注意
基于注解的注入优先级高于XML 方式的注入,因此XML 会覆盖基于注解注入的属性;
与往常一样, 可以将它们注册为单独的bean定义,但是也可以通过在基于XML的Spring配置中包含以下标记来隐式注册它们(请注意包括上下文名称空间):
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
beans>
(隐式注册的后处理器包括AutowiredAnnotationBeanPostProcessor
,CommonAnnotationBeanPostProcessor
,PersistenceAnnotationBeanPostProcessor
和前述的RequiredAnnotationBeanPostProcessor
。)
注意
@Required 适用于bean属性设置器方法,如以下示例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
此注解指示必须在配置时通过bean定义中的显式属性值或通过自动装配来填充受影响的bean属性。 如果尚未填充受影响的bean属性,则容器将引发异常。 这允许急切和显式的失败,避免以后再出现NullPointerException实例等。 我们仍然建议 将断言放入bean类本身中(例如,放入init方法中)。 这样做会强制执行那些必需的引用和值,即使 在容器外部使用该类也是如此。
注意
从Spring Framework 5.1开始,@ Required注解已正式弃用,转而使用构造函数注入进行必需的设置(或InitializingBean.afterPropertiesSet()的自定义实现以及bean属性setter方法)。
@Autowired
在本节中的示例中,可以使用JSR 330的@Inject注解代替Spring的@Autowired注解。 有关更多详细信息,请参见此处.可以将@Autowired注解应用于构造函数,如以下示例所示:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
注意
从Spring Framework 4.3开始,如果目标bean仅定义一个构造函数作为开始,则不再需要在此类构造函数上使用@Autowired注解。 但是,如果有几个构造函数可用,并且没有主要/默认构造函数,则至少一个构造函数必须使用@Autowired注解,以指示容器使用哪个构造函数。 有关详细信息,请参见关于构造函数解析的讨论。
总结:也就是说,只有一个比如你要把一个bean交给spring IOC 容器去管理,现在容器去实例化bean的时候,需要至少找到一个构造器,如果你指定了一个构造器,毫无疑问,Spring会反射区调用你的构造器去执行实例化操作,但是如果是两个或者两个以上的构造器的时候,Spring这个时候他不知道需要使用哪个构造器去实现对象的实例化操作,于是就是在构造器方法上面添加@AutoWired 注解 ,这个就会被Spring知道,然后就使用这个构造器方法去实例化对象;
还可以将@Autowired注解应用于传统的setter方法,如以下示例所
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
还可以将注解应用于具有任意名称和多个参数的方法,如以下示例所示:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
也可以将@Autowired应用于字段,甚至可以将其与构造函数混合使用,如以下示例所示:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
注意
确保目标组件(例如,MovieCatalog或CustomerPreferenceDao)由用于@Autowired的注入点的类型一致地声明。 否则,注入可能会由于运行时出现“找不到类型匹配”错误而失败。
对于通过类路径扫描找到的XML定义的bean或组件类,容器通常预先知道具体的类型。 但是,对于@Bean工厂方法, 需要确保声明的返回类型具有足够的表现力。 对于实现多个接口的组件或可能由其实现类型引用的组件,请考虑在工厂方法上声明最具体的返回类型(至少根据引用 的bean的注入点的要求具体声明)
*就是注解还按照类型来匹配的,当一个实现类实现了多个接口的话,注入的时候可能按类型来注入的对象是同一个,这就是上面提到的需要确保声明的返回类型具有足够的表现力
声明最具体的返回类型*
还可以通过将@Autowired注解添加到需要该类型数组的字段或方法中,指示Spring从ApplicationContext提供特定类型的所有bean,如以下示例所示:
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
如下例所示,这同样适用于类型化集合:
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
注意
如果 希望数组或列表中的项目以特定顺序排序,则目标bean可以实现org.springframework.core.Ordered接口或使用@Order或标准@Priority注解。 否则,它们的顺序将遵循容器中相应目标bean定义的注册顺序。
可以在目标类级别和@Bean方法上声明@Order注解,这可能适用于单个bean定义(如果使用同一bean类的多个定义)。 @Order值可能会影响注入点的优先级,但请注意它们不会影响单例启动顺序,这是由依赖关系和@DependsOn声明确定的正交关注点。
注意,标准javax.annotation.Priority注解在@Bean级别不可用,因为无法在方法上声明它。 它的语义可以通过@Order值与@Primary结合在每种类型的单个bean上进行建模。
只要预期的键类型为String,即使是类型化的Map实例也可以自动装配。 映射值包含所有预期类型的bean,并且键包含相应的bean名称,如以下示例所示:
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
默认情况下,当给定注入点没有匹配的候选bean可用时,自动装配将失败。 对于声明的数组,集合或映射,至少应有一个匹配元素。
默认行为是将带注解的方法和字段视为指示所需的依赖项。 可以按照以下示例中的说明更改此行为,从而使框架可以通过将其标记为不需要来跳过不满意的注入点(即,通过将@Autowired中的required属性设置为false)
就是说默认的情况下是@Autowired中的required属性默认情况下是true
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
如果不需要的方法(或在多个参数的情况下,其中一个依赖项)不可用,则根本不会调用该方法。 在这种情况下,完全不需要填充非必需字段,而将其默认值保留在适当的位置。
注入的构造函数和工厂方法参数是一种特殊情况,因为由于Spring的构造函数解析算法可能会处理多个构造函数,因此@Autowired中的required属性的含义有所不同。 缺省情况下,实际上有效地需要构造函数和工厂方法参数,但是在单构造函数场景中有一些特殊规则,例如,如果没有可用的匹配bean,则多元素注入点(数组,集合,映射)解析为空实例。 这允许一种通用的实现模式,其中所有依赖项都可以在唯一的多参数构造函数中声明-例如,声明没有@Autowired的单个public构造函数。
注意
任何给定bean的只有一个构造函数可以声明@Autowired,并将必需属性设置为true,用于指示容器实例化的构造器,因此,如果Autowired 是默认的或者是true ,则一个bean只能有一个带有@Autowired(require=true/默认的值true) 注解的构造器.如果是多个构造器使用了该注解 那么这些必须全部声明为**@Autowired(require=false)**为了被视为自动装配的候选者(类似于XML中的autowire = constructor),将选择通过匹配Spring容器中的bean可以满足的依赖关系数量最多的构造函数。 如果没有一个候选者满意,则将使用主/默认构造函数(如果存在)。 同样,如果一个类声明了多个构造函数,但都没有使用@Autowired进行注解,则将使用主/默认构造函数(如果存在)。 如果一个类仅声明一个单一的构造函数开始,即使没有注解,也将始终使用它。 请注意,带注解的构造函数不必是public的。
建议在setter方法上使用@Autowired的required属性,而不推荐使用已弃用的@Required注解。 将required属性设置为false表示该属性对于自动装配而言不是必需的,并且如果无法自动装配该属性,则将其忽略。 另一方面,@Required更为强大,因为它可以通过容器支持的任何方式强制设置属性,并且如果未定义任何值,则会引发相应的异常。
另外, 可以通过Java 8的java.util.Optional表示特定依赖项的非必需性质,如以下示例所示:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
从Spring Framework 5.0开始, 还可以使用@Nullable注解(在任何包中都可以使用任何形式,例如JSR-305中的javax.annotation.Nullable)或仅利用Kotlin内置的null安全支持:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
}
也可以将@Autowired用于众所周知的可解决依赖项的接口:BeanFactory,ApplicationContext,环境,ResourceLoader,ApplicationEventPublisher和MessageSource。 这些接口及其扩展接口(例如ConfigurableApplicationContext或ResourcePatternResolver)将自动解析,而无需进行特殊设置。 以下示例自动装配ApplicationContext对象:
就是上面的对象都是可以在代码里面直接Autowired 的
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
注意
@Autowired
,@Inject
@ Value
和@Resource
注解由Spring BeanPostProcessor实现处理。 这意味着 不能在自己的BeanPostProcessor或BeanFactoryPostProcessor类型(如果有)中应用这些注解。 必须使用XML或Spring @Bean方法显式“连接”这些类型。
就是你自己写BeanFactoryPostProcessor 的时候是不能够使用上面的这些注解的 因为这些注解的实现原理就是通过BeanFactoryPostProcessor 来的,比如说你在实例化BeanFactoryPostProcessor/ BeanPostProcessor
中添加Autowired 这个时候需要有BeanFactoryPostProcessor 是来完成自动注入 而这个是BeanFactoryPostProcessor 还没有完成实例化,就会引发错误
由于按类型自动注入可能会导致多个候选对象,因此通常有必要更好地控制选择过程。 一种实现此目标的方法是使用Spring的@Primary注解。 @Primary表示当多个bean可以自动连接到单值依赖项的候选对象时,应优先使用特定的bean。 如果候选对象中仅存在一个主bean,则它将成为自动装配的值。
考虑以下将firstMovieCatalog定义为主要MovieCatalog的配置
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
通过前面的配置,以下MovieRecommender与firstMovieCatalog自动注入
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
xml方式 bean定义
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog" primary="true">
bean>
<bean class="example.SimpleMovieCatalog">
bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
beans>
当可以确定一个主要候选者时,@ Primary是在几种情况下按类型使用自动装配的有效方法。 当 需要对选择过程进行更多控制时,可以使用Spring的@Qualifier注解。 可以将限定符值与特定的参数相关联,从而缩小类型匹配的范围,以便为每个参数选择特定的bean。 在最简单的情况下,这可以是简单的描述性值,如以下示例所示:
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
还可以在各个构造函数参数或方法参数上指定@Qualifier注解,如以下示例所示:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
以下示例显示了相应的bean定义。
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier value="main"/>
bean>
<bean class="example.SimpleMovieCatalog">
<qualifier value="action"/>
bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
beans>
作为后备匹配,bean的名字就是默认的限定符,因此, 可以使用id为main而不是嵌套的qualifier元素定义bean,达到相同的匹配结果*(就是定义bean的名字是main)*.但是,尽管 可以使用此约定通过名称来引用特定的bean,@Autowired 基本上是使用按照类型来匹配的注入.这意味着,即使带有Bean名称后备的限定符值,在类型匹配集中也只是始终具有缩小的选择bean的范围,还不是精确。他们没有在语义上表示对唯一bean id的引用.好的限定符值是main
的或EMEA
的或persistent
,表示特定组件的特征,这些特征独立于Bean ID.如果是匿名bean定义(例如前面的示例中的定义),则可以自动生成该属性.
限定符还适用于类型化的集合,如前面所述(例如,应用于Set )。 在这种情况下,根据声明的限定符,将所有匹配的bean作为集合注入。 这意味着限定词不必是唯一的。 相反,它们构成了过滤标准。 例如, 可以使用相同的限定符值“ action”定义多个MovieCatalog Bean,所有这些都注入到以@Qualifier(“ action”)注解的Set 中。
Spring里面bean名字是可以有一样的 但是bean的id是绝对不能是一样的
注意:
在类型匹配的候选对象中,让限定符值针对目标Bean名称进行选择.在注入点不需要@Qualifier注解.如果没有其他解析指标(例如限定词或主要标记),对于非唯一的依赖情况.Spring匹配注入点名称(即字段名称或参数名称),针对目标Bean名称,然后选择同名候选者(如果有)也就是说Autowired 是按照类型来匹配一般情况下是不需要的Qualifier的,因为按照类型匹配没有匹配上的话 就在按照Autowired 类型的对应得字段名称或者属性名称来匹配候选者
也就是说,如果你打算按名称表示注解的注入,则即使它能够在类型匹配的候选对象中按bean名称进行选择,也不要主要使用@Autowired。 而是使用JSR-250 @Resource注解,该注解的语义定义是通过其唯一名称标识特定的目标组件,而声明的类型与匹配过程无关。@Autowired具有非常不同的语义:在按类型选择候选bean之后,仅在那些类型选择的候选中考虑指定的String qualifier 值
对于本身定义为集合,映射或数组类型的bean,@ Resource是一个很好的解决方案,它通过唯一的名称引用特定的集合或数组bean。 也就是说,从4.3版本开始,只要元素类型信息保留在@Bean返回类型签名或集合继承层次结构中,就可以通过Spring的@Autowired类型匹配算法来匹配Map和数组类型。 在这种情况下, 可以使用限定符值在同类型的集合中进行选择,如上一段所述。
从4.3开始,@ Autowired还考虑了自我引用以进行注入(即,引用回当前注入的Bean)。 请注意,自我注入是一个回调。 对其他组件的常规依赖始终优先。 从这个意义上说,自我依赖不参与常规的候选人选择,因此尤其是绝不是主要的。 相反,它们总是以最低优先级结束。 实际上, 应该仅将自我引用用作最后的手段(例如,通过Bean的事务代理在同一实例上调用其他方法)。 在这种情况下,请考虑将受影响的方法分解为单独的委托Bean。 或者, 可以使用@Resource,它可以通过其唯一名称获取返回到当前bean的代理。
注意:
尝试将@Bean方法的结果注入相同的配置类也是一种有效的自引用方案.要么在实际需要的方法签名中延迟解析这些引用(与配置类中的自动装配字段相对),要么将受影响的@Bean方法声明为静态,将它们与包含的配置类实例及其生命周期脱钩。否则,仅在回调阶段考虑此类Bean,而将其他配置类上的匹配Bean选作主要候选对象(如果可用)。
@Autowired适用于字段,构造函数和多参数方法,从而允许在参数级别缩小限定符注解的范围。 相反,只有具有单个参数的字段和bean属性设置器方法才支持@Resource。 因此,如果注入目标是构造函数或多参数方法,则应坚持使用qualifiers 。
可以创建自己的自定义限定符注解。 为此,请定义一个注解并在定义中提供@Qualifier注解,如以下示例所示:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
然后, 可以在自动连接的字段和参数上提供自定义限定符,如以下示例所示
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
接下来,可以提供有关候选bean定义的信息。 可以将标记添加为标记的子元素,然后指定类型和值以匹配的自定义限定符注解。 该类型与注解的标准类名匹配。 另外,为方便起见,如果不存在名称冲突的风险,则可以使用简短的类名。 下面的示例演示了两种方法:
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier type="Genre" value="Action"/>
bean>
<bean class="example.SimpleMovieCatalog">
<qualifier type="example.Genre" value="Comedy"/>
bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
beans>
在“类路径扫描和托管组件”中, 可以看到基于注解的替代方法,以XML形式提供限定符元数据。 具体来说,请参阅Providing Qualifier Metadata with Annotations。
在某些情况下,使用没有值的注解就足够了。 当注解用于更一般的用途并且可以应用于几种不同类型的依赖项时,这将很有用。 例如, 可以提供一个脱机目录,当没有Internet连接可用时可以进行搜索。 首先,定义简单的注解,如以下示例所示:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
public class MovieRecommender {
@Autowired
@Offline
private MovieCatalog offlineCatalog;
// ...
}
现在,bean定义仅需要限定符类型,如以下示例所示
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/>
<!-- inject any dependencies required by this bean -->
</bean>
还可以定义自定义限定符注解,以接受除简单值属性之外或代替简单值属性的命名属性。 如果随后在要自动装配的字段或参数上指定了多个属性值,则bean定义必须与所有此类属性值匹配才能被视为自动装配候选。 例如,请考虑以下注解定义:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
在这种情况下,Format是一个枚举,定义如下
public enum Format {
VHS, DVD, BLURAY
}
要自动装配的字段将用自定义限定符进行注解,并包括两个属性的值:genre
和format
,如以下示例所示:
public class MovieRecommender {
@Autowired
@MovieQualifier(format=Format.VHS, genre="Action")
private MovieCatalog actionVhsCatalog;
@Autowired
@MovieQualifier(format=Format.VHS, genre="Comedy")
private MovieCatalog comedyVhsCatalog;
@Autowired
@MovieQualifier(format=Format.DVD, genre="Action")
private MovieCatalog actionDvdCatalog;
@Autowired
@MovieQualifier(format=Format.BLURAY, genre="Comedy")
private MovieCatalog comedyBluRayCatalog;
// ...
}
最后,bean定义应包含匹配的限定符值。 此示例还演示了可以使用bean元属性代替元素。 如果可用,元素及其属性优先,但是如果不存在这样的限定符,则自动装配机制将退回到标记内提供的值,如下面示例中的最后两个bean定义 :
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier type="MovieQualifier">
<attribute key="format" value="VHS"/>
<attribute key="genre" value="Action"/>
qualifier>
bean>
<bean class="example.SimpleMovieCatalog">
<qualifier type="MovieQualifier">
<attribute key="format" value="VHS"/>
<attribute key="genre" value="Comedy"/>
qualifier>
bean>
<bean class="example.SimpleMovieCatalog">
<meta key="format" value="DVD"/>
<meta key="genre" value="Action"/>
bean>
<bean class="example.SimpleMovieCatalog">
<meta key="format" value="BLURAY"/>
<meta key="genre" value="Comedy"/>
bean>
beans>
除了@Qualifier注解之外, 还可以将Java泛型类型用作资格的隐式形式。 例如,假设 具有以下配置:
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
假设前面的bean实现了通用接口(即Store 和Store ),则可以@Autowire Store接口,并且通用接口用作限定符,如以下示例所示:
@Autowired
private Store<String> s1; // qualifier, injects the stringStore bean
@Autowired
private Store<Integer> s2; // qualifier, injects the integerStore bean
当自动装配列表,Map实例和数组时,通用限定符也适用。 以下示例自动装配通用列表:
@Autowired
private List<Store<Integer>> s;
CustomAutowireConfigurer
CustomAutowireConfigurer是BeanFactoryPostProcessor,即使没有使用Spring的@Qualifier进行注解,也可以注册自己的自定义注解类型。 以下示例显示如何使用CustomAutowireConfigurer:
<bean id="customAutowireConfigurer"
class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>example.CustomQualifiervalue>
set>
property>
bean>
AutowireCandidateResolver通过以下方式确定自动装配候选者:
每个bean定义的autowire-candidate值
元素上可用的任何默认autowire-candidates模式
@Qualifier以及在CustomAutowireConfigurer中注册的所有自定义注解的存在
当多个bean符合自动装配候选条件时,“主要”的确定如下:如果候选中恰好有一个bean定义的主要属性设置为true,则将其选中。
Spring还通过对字段或bean属性设置器方法使用JSR-250 @Resource注解(javax.annotation.Resource)支持注入.这是Java EE中的常见模式:例如,在JSF管理的Bean和JAX-WS端点中。Spring也为Spring管理的对象支持此模式.@Resource具有名称属性。 默认情况下,Spring将该值解释为要注入的Bean 的name属性.换句话说,它遵循名称语义,如以下示例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
如果未明确指定名称,则默认名称是从字段名称或setter方法派生的。 如果是字段,则采用字段名称。 在使用setter方法的情况下,它采用bean属性名称。 以下示例将把名为movieFinder的bean注入其setter方法中:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
注解提供的名称由CommonAnnotationBeanPostProcessor感知的ApplicationContext解析为bean名称。如果 明确配置Spring的SimpleJndiBeanFactory,则可以通过JNDI解析名称。但是,我们建议 依赖默认行为,并使用Spring的JNDI查找功能来保留间接级别。
在@Resource用法的特例中,未指定任何显式名称,并且类似于@ Autowired,@ Resource查找主类型匹配而不是特定的命名bean,并解析众所周知的可解决依赖项:BeanFactory,ApplicationContext,ResourceLoader,ApplicationEventPublisher和 MessageSource接口。
就是这些接口BeanFactory,ApplicationContext,ResourceLoader,ApplicationEventPublisher和 MessageSource接口 直接使用@Resource 就可以直接在代码里面获取到这种对象
因此,在以下示例中,customerPreferenceDao字段首先查找名为“ customerPreferenceDao”的bean,然后回退到类型为CustomerPreferenceDao的主类型匹配项
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
*@Resource(这个注解属于J2EE的),默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。*如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
@Value通常用于注入外部属性:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
使用以下配置:
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
配置文件
catalog.name=MovieCatalog
当然如果是Springboot 就有默认的配置文件就不需要上面的那个配置类对象了
在这种情况下,catalog
参数和字段将等于MovieCatalog值。
Spring提供了一个默认的宽松内嵌值解析器。 它将尝试解析属性值,如果无法解析,则将属性名称(例如$ {catalog.name})作为值注入。 如果要严格控制不存在的值,则应声明一个PropertySourcesPlaceholderConfigurer bean,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
注意:
使用JavaConfig配置PropertySourcesPlaceholderConfigurer时,@ Bean方法必须是静态的。
如果无法解析任何$ {}占位符,则使用上述配置可确保Spring初始化失败。 也可以使用setPlaceholderPrefix,setPlaceholderSuffix或setValueSeparator之类的方法来自定义占位符。
注意:
Spring Boot默认配置一个PropertySourcesPlaceholderConfigurer bean,它将从application.properties和application.yml文件获取属性。
Spring提供的内置转换器支持允许自动处理简单的类型转换(例如,转换为Integer或int)。 多个逗号分隔的值可以自动转换为String数组,而无需付出额外的努力。
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
Spring BeanPostProcessor在后台使用ConversionService处理将@Value中的String值转换为目标类型的过程。 如果要为自己的自定义类型提供转换支持,则可以提供自己的ConversionService bean实例,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
当@Value包含SpEL表达式时,该值将在运行时动态计算,如以下示例所示:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
}
SpEL还可以使用更复杂的数据结构:
@Component
public class MovieRecommender {
private final Map<String, Integer> countOfMoviesPerCatalog;
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
CommonAnnotationBeanPostProcessor不仅可以识别@Resource注解,还可以识别JSR-250生命周期注解:javax.annotation.PostConstruct和javax.annotation.PreDestroy。 在Spring 2.5中引入的对这些注解的支持为初始化回调和销毁回调中描述的生命周期回调机制提供了一种替代方法。 假设CommonAnnotationBeanPostProcessor已在Spring ApplicationContext中注册,则在生命周期中与相应的Spring生命周期接口方法或显式声明的回调方法在同一点调用带有这些注解之一的方法。 在以下示例中,缓存在初始化时预先填充,并在销毁时清除
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
有关组合各种生命周期机制的效果的详细信息,请参见组合生命周期机制
注意:
与@Resource一样,@ PostConstruct和@PreDestroy注解类型是JDK 6到8的标准Java库的一部分。但是,整个javax.annotation包与JDK 9中的核心Java模块分离,并最终在JDK 11中删除了。 如果需要,现在需要通过Maven Central获取javax.annotation-api工件,只需像其他任何库一样将其添加到应用程序的类路径中即可。
@Repository注解是实现存储库的角色或构造型(也称为数据访问对象或DAO)的任何类的标记。 该标记的用途包括自动翻译异常,如“异常翻译”中所述。
Spring提供了进一步的构造型注解:@ Component,@ Service和@Controller。 @Component是任何Spring托管组件的通用构造型。 @ Repository,@ Service和@Controller是@Component的特化,用于更特定的用例(分别在持久层,服务层和表示层中)。 因此, 可以使用@Component来注解组件类,但是通过使用@ Repository,@ Service或@Controller来注解组件类, 的类更适合通过工具进行处理或与方面相关联。 例如,这些构造型注解成为切入点的理想目标。 @ Repository,@ Service和@Controller在Spring框架的将来版本中也可以带有其他语义。 因此,如果在服务层使用@Component或@Service之间进行选择,则@Service显然是更好的选择。 同样,如前所述,@ Repository已被支持作为持久层中自动异常转换的标记。
注意:
从Spring 3.0开始,Spring JavaConfig项目提供的许多功能是核心Spring Framework的一部分。 这使 可以使用Java而不是使用传统的XML文件来定义bean。 查看@ Configuration,@ Bean,@ Import和@DependsOn注解,以获取有关如何使用这些新功能的示例。
@Componect
和进一步的其他一般注解@Repository
是实现存储库角色或构造型的任何类的标记 一般是DAO层;
Spring提供一些常用的注解@Component
, @Service
, @Controller
. @Component
相当于是对通用注解的一些改造,赋予这些注解一些特有的意义.@ Repository,@ Service和@Controller是@Component的特化,用于更特定的用例(分别在持久层,服务层和表示层中)。 因此,可以使用@Component来注解组件类,但是通过使用@ Repository,@ Service或@Controller来注解组件类, 的类更适合通过工具进行处理或与方面相关联。 例如,这些构造型注解成为切入点的理想目标。 @ Repository,@ Service和@Controller在Spring框架的将来版本中也可以带有其他语义。 因此,如果在服务层使用@Component或@Service之间进行选择,则@Service显然是更好的选择。 同样,如前所述,@ Repository已被支持作为持久层中自动异常转换的标记。
Spring提供的许多注解都可以在 自己的代码中用作元注解。 元注解是可以应用于另一个注解的注解。 例如,前面提到的@Service注解使用@Component进行元注解,如以下示例所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
}
还可以组合元注解来创建“组合注解”。 例如,Spring MVC中的@RestController注解由@Controller和@ResponseBody组成。
此外,组合注解可以选择从元注解中重新声明属性,以允许自定义。 当 只希望公开元注解属性的子集时,此功能特别有用。 例如,Spring的@SessionScope注解将作用域名称硬编码为会话,但仍允许自定义proxyMode。 以下清单显示了SessionScope注解的定义
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
/**
* Alias for {@link Scope#proxyMode}.
* Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
*/
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
然后, 可以使用@SessionScope而不用声明如下的proxyMode:
@Service
@SessionScope
public class SessionScopedService {
// ...
}
还可以覆盖proxyMode的值,如以下示例所示:
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
Spring可以自动检测构造型类,并向ApplicationContext注册相应的BeanDefinition实例。 例如,以下两个类别资格进行这种自动检测:
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}
要自动检测这些类并注册相应的bean,需要将@ComponentScan添加到@Configuration类中,其中basePackages属性是这两个类的公共父包。 (或者, 可以指定一个逗号分隔,分号分隔或空格分隔的列表,其中包括每个类的父包。)
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
可以使用xml来替换
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example"/>
beans>
注意:
(1)< context:annotation-config />:仅能够在已经在已经注册过的bean上面起作用。对于没有在spring容器中注册的bean,它并不能执行任何操作。
(2)< context:component-scan base-package=“XX.XX”/> :除了具有上面的功能之外,还具有自动将带有@component,@service,@Repository等注解的对象注册到spring容器中的功能。
扫描类路径包需要在类路径中存在相应的目录条目。 使用Ant构建JAR时,请确保未激活JAR任务的仅文件开关。 另外,在某些环境中,基于安全策略可能不会公开类路径目录。例如,JDK 1.7.0_45及更高版本上的独立应用程序(这需要在清单中设置“受信任的库”。)请参见https://stackoverflow.com/ Questions / 19394570 / java-jre-7u45-breaks-classloader-getresources)。
在JDK 9的模块路径(Jigsaw)上,Spring的类路径扫描通常可以正常进行。 但是,请确保将组件类导出到模块信息描述符中。 如果 希望Spring调用 的类的非公共成员,请确保将它们“打开”(即,它们在模块信息描述符中使用了opens声明而不是export声明)
此外,当 使用component-scan元素时,将隐式包括AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor。 这意味着两个组件将被自动检测并连接在一起,而所有这些都不需要XML中提供任何bean配置元数据。
你可以禁用AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor的注册,方法是将注解-配置属性的值设为false
默认情况下,仅使用@ Component,@ Repository,@ Service,@ Controller,@ Configuration进行注解的类或本身使用@Component进行注解的自定义注解是唯一检测到的候选组件。 可以通过应用自定义过滤器来修改和扩展此行为。 将它们添加为@ComponentScan注解的includeFilters或excludeFilters属性(或作为XML配置中
过滤类型 | Example Expression | Description |
---|---|---|
annotation (default) | org.example.SomeAnnotation |
在目标组件中的类型级别上存在或元数据存在的注解。 |
assignable | org.example.SomeClass |
目标组件可分配给(扩展或实现)的类(或接口) |
aspectj | org.example..*Service+ |
目标组件要匹配的AspectJ类型表达式. |
regex | org\.example\.Default.* |
一个正则表达式,要与目标组件的类名匹配. |
custom | org.example.MyTypeFilter |
org.springframework.core.type.TypeFilter接口的自定义实现 |
以下示例显示了忽略所有@Repository注解并改为使用“stub ”存储库的配置:
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
...
}
总结 : 简单的来说就是通过以上的一系列规则去(不)加载这些规则(不)匹配的类
等效于下面的XML 配置文件
注意: 还可以通过在注解上设置useDefaultFilters = false或通过将use-default-filters =“ false”作为元素的属性来禁用默认过滤器。 这有效地禁用了自动检测通过@ Component,@ Repository,@ Service,@ Controller,@ RestController或@Configuration进行注解或元注解的类的功能。
总结默认是useDefaultFilters = true 当这个属性的值是false 的时候,一些常用的注解就是失效了@ Component,@ Repository,@ Service,@ Controller,@ RestController或@Configuration,这个属性一般配合context:include-filter子标签一起使用;
其意为:只扫描指定注解的类。 context:include-filte用于指定注解的类型
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
只是扫描controller 其他的不扫描
Spring组件还可以将bean定义元数据贡献给容器,可以使用与@Bean注解相同的@Bean注解来定义@Configuration带注解的类中的bean元数据。 以下示例显示了如何执行此操作
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
上一类是Spring组件,在其doWork()方法中具有特定于应用程序的代码。 但是,它也提供了一个具有工厂方法的bean定义,该工厂方法引用了方法publicInstance()。 @Bean标识工厂方法和其他bean定义属性,例如通过@Qualifier注解的限定符值。 可以指定的其他方法级别注解为@ Scope,@ Lazy和自定义限定符注解
注意
除了用于组件初始化的角色外, 还可以将@Lazy注解放置在标有@Autowired或@Inject的注入点上。 在这种情况下,它导致注入了惰性解析代理。
如前所述,支持自动装配的字段和方法,并自动装配@Bean方法。 以下示例显示了如何执行此操作:
@Component
public class FactoryMethodComponent {
private static int i;
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
// use of a custom qualifier and autowiring of method parameters
@Bean
protected TestBean protectedInstance(
@Qualifier("public") TestBean spouse,
@Value("#{privateInstance.age}") String country) {
TestBean tb = new TestBean("protectedInstance", 1);
tb.setSpouse(spouse);
tb.setCountry(country);
return tb;
}
@Bean
private TestBean privateInstance() {
return new TestBean("privateInstance", i++);
}
@Bean
@RequestScope
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}
}
该示例将String方法参数country自动映射到一个名为privateInstance的bean上age属性的值。Spring Expression Language元素通过符号#{}定义属性的值。对于@Value注解,表达式解析器已预先配置为在解析表达式文本时查找bean名称。
从Spring Framework 4.3开始,还可以声明类型为InjectionPoint的工厂方法参数(或更具体的子类:DependencyDescriptor),以访问触发当前bean创建的请求注入点。请注意,这仅适用于实际创建的Bean实例,不适用于注入现有实例.因此,此功能对原型范围的bean最有意义。 对于其他作用域,factory方法仅在给定作用域中看到触发创建新bean实例的注入点(例如,触发创建惰性单例bean的依赖项)。 在这种情况下,可以将提供的注入点元数据与语义一起使用。 以下示例显示如何使用InjectionPoint:
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
常规Spring组件中的@Bean方法的处理方式与Spring @Configuration类中的@Bean方法不同。 区别在于,使用CGLIB不能增强@Component类,以拦截方法和字段的调用。 CGLIB代理是一种调用@Configuration类中@Bean方法中的方法或字段的方法,用于创建Bean元数据引用以协作对象。 此类方法不是使用常规Java语义调用的,而是通过容器进行的,以提供通常的生命周期管理和Spring Bean的代理,即使通过@Bean方法的编程调用引用其他Bean时也是如此。 相反,在普通@Component类内的@Bean方法中调用方法或字段具有标准Java语义,而无需特殊的CGLIB处理或其他约束;
总结:现在是使用@Configuration注解,此时调用dataSource() 方法返回的是被Spring管理的单例Bean。
如果换做是@Component 注解,那么调用了dataSource() 方法返回的对象是执行这个方法返回的对象实例,而不是被spring管理的对象。
@Configruation
public class example{
@Bean
public DataSource datasource(){
return new DataSource();
}
@Bean
public DataSourceManger getdatasource(){
return new DataSourceManger(dataSource());
}
}
dataSource() 方法会被容器所代理 放回之前被容器代理的bean (@bean方法是非静态方法 )
@Component
public class example{
@Bean
public DataSource datasource(){
return new DataSource();
}
@Bean
public DataSourceManger getdatasource(){
return new DataSourceManger(dataSource());
}
}
dataSource()就是常规语义 调用一个方法,放回一个重新创建的对象
注意:
可以将@Bean方法声明为静态方法,从而允许在不将其包含配置类创建为实例的情况下调用它们。 在定义后处理器Bean(例如BeanFactoryPostProcessor或BeanPostProcessor类型)时,这特别有意义,因为此类Bean在容器生命周期的早期进行了初始化,并且应避免在那时触发配置的其他部分。
由于技术限制,对静态@Bean方法的调用永远不会被容器拦截,即使在@Configuration类中也是如此(如本节前面所述),由于技术限制:CGLIB子类只能覆盖非静态方法。 结果,直接调用另一个@Bean方法具有标准的Java语义,从而导致直接从工厂方法本身直接返回一个独立的实例
@Bean方法的Java语言可见性不会对Spring容器中的最终bean定义产生直接影响。 可以在非@Configuration类中自由声明自己的工厂方法,也可以在任何地方声明静态方法。 但是,@ Configuration类中的常规@Bean方法必须是可重写的—即,不得将它们声明为private或final。
就是说@bean方法的修饰符是什么不管,但是在@ Configuration里面的就必须是可以重写的,声明了private和final 就是不能被重写了
@Bean方法也可以在给定组件或配置类的基类中发现,也可以在Java 8中在组件或配置类实现的接口中声明的默认方法中发现。这为组合复杂配置安排提供了很大的灵活性,甚至可以通过Spring 4.2中的Java 8默认方法实现多重继承。
最后,一个类可以为同一个bean保留多个@Bean方法,这取决于在运行时可用的依赖关系,从而可以使用多个工厂方法。 这与在其他配置方案中选择“最贪婪”的构造函数或工厂方法的算法相同:在构造时选择具有最大可满足依赖关系数量的变量,类似于容器在多个@Autowired构造函数之间进行选择的方式。
就比如说是一个bean它有好多子类只是@bean上面声明的是父类对象,可以在@bean方法上面在家其他的注解,效果就是上面讲的(取决于在运行时可用的依赖关系) 根据这个关系来具体实例化哪一个bean
在扫描过程中自动检测到组件时,其bean名称由该扫描程序已知的BeanNameGenerator策略生成。 默认情况下,任何包含名称值的Spring构造型注解(@ Component,@ Repository,@ Service和@Controller)都会将该名称提供给相应的bean定义。
如果这样的注解不包含名称值,或者不包含任何其他检测到的组件(例如,由自定义过滤器发现的组件),则缺省bean名称生成器将返回未大写的非限定类名称。 例如,如果检测到以下组件类,则名称将为myMovieLister和movieFinderImpl:
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
如果不想依赖默认的Bean命名策略,则可以提供自定义Bean命名策略。 首先,实现BeanNameGenerator
接口,并确保包括默认的无参数构造函数。 然后,在配置扫描程序时提供完全限定的类名,如以下示例注解和Bean定义所示。
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
beans>
作为一般规则,每当其他组件可能对其进行显式引用时,请考虑使用注解指定名称。 另一方面,只要容器负责装配/依赖注入,自动生成的名称就足够了。
通常,与Spring管理的组件一样,自动检测到的组件的默认且最常见的作用域是单例。 但是,有时 需要使用@Scope注解指定的其他范围。 可以在注解中提供范围的名称,如以下示例所示:
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
注意:
@Scope注解仅在具体的bean类(对于带注解的组件)或工厂方法(对于@Bean方法)上进行内省。 与XML bean定义相反,没有bean定义继承的概念,并且在类级别的继承层次结构与元数据目的无关。
这个解释在源码里面直接就有了
就是Scope 是和继承没有关系的
有关特定于Web的范围的详细信息,例如Spring上下文中的“请求”或“会话”,请参阅Request, Session, Application, and WebSocket Scopes.。 与这些范围的预构建注解一样, 也可以使用Spring的元注解方法来编写自己的作用域注解:例如,使用@Scope(“ prototype”)元注解的自定义注解,也可以声明一个自定义注解 范围代理模式。
注意:
要提供用于scope 解析的自定义策略,而不是依赖于基于注解的方法,可以实现ScopeMetadataResolver
接口。 确保包括默认的无参数构造函数。 然后,可以在配置扫描程序时提供完全限定的类名,如以下注解和Bean定义示例所示
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
beans>
使用某些非单作用域时,可能有必要为作用域对象生成代理。 在作用域Bean中将推理描述为依赖项。 为此,在component-scan元素上可以使用scoped-proxy属性。 三个可能的值是:no,interfaces和targetClass。 例如,以下配置生成标准的JDK动态代理:
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
beans>
@Qualifier注解将在使用限定符的基于注解的自动装配中进行讨论。 该部分中的示例演示了@Qualifier注解和自定义限定符注解的使用,以在解析自动装配候选时提供细粒度的控制。 由于这些示例基于XML bean定义,因此通过使用XML中bean元素的限定符或meta子元素,在候选bean定义上提供了限定符元数据。 当依靠类路径扫描来自动检测组件时,可以在候选类上为限定符元数据提供类型级别的注解。 下面的三个示例演示了此技术:
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}
与大多数基于注解的替代方法一样,请记住,注解元数据绑定到类定义本身,而XML的使用允许相同类型的多个bean提供其限定符元数据的变体,因为该元数据是按实例而不是按类。
不是很懂???
简单的理解一些,就是在类的定义上面添加注解,spring扫描到这些类的定义,也就是类的元数据上面后面一句不是很理解…
尽管类路径扫描非常快,但可以通过在编译时创建静态候选列表来提高大型应用程序的启动性能。 在这种模式下,作为组件扫描目标的所有模块都必须使用此机制。
注意
现有的@ComponentScan或 要生成索引,请向每个包含组件的模块添加附加依赖关系,这些组件是组件扫描指令的目标。 以下示例显示了如何使用Maven进行操作 该过程将生成一个包含在jar文件中的META-INF / spring.components文件。 注意: 在IDE中使用此模式时,spring-context-indexer必须注册为注解处理器,以确保在更新候选组件时索引是最新的 从Spring 3.0开始,Spring提供对JSR-330标准注解(依赖注入)的支持。 这些注解的扫描方式与Spring注解的扫描方式相同。 要使用它们, 需要在类路径中有相关的jar。 可以使用@ javax.inject.Inject代替@Autowired,如下所示: 与@Autowired一样, 可以在字段级别,方法级别和构造函数参数级别使用@Inject。 此外,可以将注入点声明为Provider,从而允许按需访问范围较小的bean,或者通过Provider.get()调用来懒加载其他bean。 以下示例提供了先前示例的变体: Provider 相当于就是封装了一层(有点类似于Optional) 如果要为应注入的依赖项使用限定名称,则应使用@Named注解,如以下示例所示: 与@Autowired一样,@ Inject也可以与java.util.Optional或@Nullable一起使用。 由于@Inject没有必填属性,因此这在此处更为适用。 以下一对示例显示了如何使用@Inject和@Nullable: 可以使用@ javax.inject.Named或javax.annotation.ManagedBean代替@Component,如以下示例所示 在没有指定组件名称的情况下使用@Component是非常常见的。 可以类似的方式使用@Named,如以下示例所示: 当使用@Named或@ManagedBean时,可以使用与使用Spring注解完全相同的方式来使用组件扫描,如以下示例所示: 注意 与@Component相反,JSR-330 @Named和JSR-250 ManagedBean注解是不可组合的.应该使用Spring的原型模型来构建自定义组件注解。 本节介绍如何在Java代码中使用注解来配置Spring容器。 它包括以下主题 Spring的新Java配置支持中的主要构件是@Configuration注解的类和@Bean注解的方法。 @Bean注解用于指示方法实例化,配置和初始化要由Spring IoC容器管理的新对象。 对于那些熟悉Spring的 XML配置的人来说,@ Bean注解与元素具有相同的作用。 可以将@Bean注解方法与任何Spring @Component一起使用。 但是,它们最常与@Configuration bean一起使用。 用@Configuration注解类表示该类的主要目的是作为Bean定义的来源。 此外,@Configuration类允许通过调用同一类中的其他@Bean方法来定义Bean间的依赖关系。 最简单的@Configuration类的内容如下: 等效于下面的xml 注意: 完整的@Configuration与“精简” @Bean模式? 如果在未使用@Configuration注解的类中声明@Bean方法,则将它们称为以“精简”模式进行处理.在@Component或什至在简单的旧类中声明的Bean方法被认为是“精简版”,其中包含类具有不同的主要用途,而@Bean方法是其中的一种奖励.例如,服务组件可以通过每个适用组件类上的其他@Bean方法将管理视图公开给容器。 在这种情况下,@ Bean方法是一种通用的工厂方法机制。 与完整的@Configuration不同,lite @Bean方法无法声明Bean之间的依赖关系。 取而代之的是,它们在其包含组件的内部状态上进行操作,并且还可以根据其可能声明的参数进行操作。 因此,此类@Bean方法不应调用其他@Bean方法。 实际上,每个此类方法仅是用于特定bean引用的工厂方法,而没有任何特殊的运行时语义。 这里的积极副作用是,不必在运行时应用CGLIB子类,因此在类设计方面没有任何限制(即,包含类可能是最终类,依此类推)==>即在@Component中@Bean是lite模式 在常见情况下,@ Bean方法将在@Configuration类中声明,以确保始终使用“完全”模式,因此跨方法引用将重定向到容器的生命周期管理。 这样可以防止通过常规Java调用意外地调用同一@Bean方法,从而有助于减少在“精简”模式下运行时难以追查的细微错误。 以下各节将详细讨论@Bean和@Configuration注解。 但是,首先,我们介绍了使用基于Java的配置来创建spring容器的各种方法。 以下各节介绍了Spring 3.0中引入的Spring的AnnotationConfigApplicationContext。 这种通用的ApplicationContext实现不仅能够接受@Configuration类作为输入,而且还可以接受普通的@Component类和带有JSR-330元数据注解的类。 当提供@Configuration类作为输入时,@ Configuration类本身将注册为Bean定义,并且该类中所有已声明的@Bean方法也将注册为Bean定义。 提供@Component和JSR-330类时,它们将注册为bean定义,并且假定在必要时在这些类中使用了诸如@Autowired或@Inject之类的DI元数据 简单构造器 与实例化 如前所述,AnnotationConfigApplicationContext不限于仅与@Configuration类一起使用。 可以将任何@Component或JSR-330带注解的类作为输入提供给构造函数,如以下示例所示: 前面的示例假定MyServiceImpl,Dependency1和Dependency2使用Spring依赖项注入注解,例如@Autowired。 **通过使用编程方式构建容器 可以使用无参构造函数实例化AnnotationConfigApplicationContext,然后使用register()方法对其进行配置。 以编程方式构建AnnotationConfigApplicationContext时,此方法特别有用。 以下示例显示了如何执行此操作 使用启用组件扫描 要启用组件扫描, 可以按如下方式注解@Configuration类 经验丰富的Spring使用者可能熟悉Spring的context:名称空间中的XML声明,如以下示例所示: 在前面的示例中,对com.acme包进行了扫描以查找任何@Component注释的类,并将这些类注册为容器内的Spring bean定义。 AnnotationConfigApplicationContext公开了scan(String …)方法以允许相同的组件扫描功能,如以下示例所示 注意: 请记住,@ Configuration类使用@Component进行元注释,因此它们是组件扫描的候选对象。 在前面的示例中,假定AppConfig在com.acme包(或下面的任何包)中声明,则在调用scan()时将其拾取。 根据refresh(),其所有@Bean方法都将被处理并注册为容器内的Bean定义。 支持Web应用程序 @Bean是方法级注释,是XML 元素。 注释支持提供的某些属性,例如:初始化方法销毁方法自动装配名称。 可以在@Configuration注释或@Component注释的类中使用@Bean注释。 声明一个bean 要声明一个bean,可以用@Bean注释来注释一个方法。 可以使用此方法在类型指定为该方法的返回值的ApplicationContext中注册Bean定义。默认情况下,bean名称与方法名称相同。 以下示例显示了@Bean方法声明: 前面的配置与下面的Spring XML完全等效 这两个声明都使一个名为transferService的bean在ApplicationContext中可用,并绑定到类型为TransferServiceImpl的对象实例,如以下文本图像所示 还可以使用接口(或基类)返回类型声明@Bean方法,如以下示例所示 但是,这将高级类型预测的可见性限制为指定的接口类型(TransferService)。 然后,使用仅一次容器已知的完整类型(TransferServiceImpl),就可以实例化受影响的单例bean。 非懒惰单例bean根据其声明顺序实例化,因此可能会看到不同的类型匹配结果,具体取决于另一个组件何时尝试按非声明类型进行匹配(例如@Autowired TransferServiceImpl,仅当transferService bean具有 被实例化)。 也就是是说声明一个父类的对象,指向子类的引用; 注意: 如果通过声明的服务接口一致地引用类型,则@Bean返回类型可以安全地加入该设计决策。 但是,对于实现多个接口的组件或由其实现类型潜在引用的组件,声明可能的最具体的返回类型(至少与引用的bean的注入点所要求的具体类型一样)更为安全。 也就说可能声明的类的接口指向的引用对象 ,也就是子类可能是实现了多个接口,然后呢最好是这个实现类以声明的接口中的方法为主,这样更好一点 Bean依赖 @Bean注释的方法可以具有任意数量的参数,这些参数描述构建该bean所需的依赖关系。 例如,如果我们的TransferService需要一个AccountRepository,则可以使用方法参数来实现该依赖关系,如以下示例所示 解析机制与基于构造函数的依赖注入几乎相同。 有关更多详细信息,请参见相关部分。 接收生命周期回调 任何使用@Bean注释定义的类都支持常规的生命周期回调,并且可以使用JSR-250中的@PostConstruct和@PreDestroy注释。 有关更多详细信息,请参见JSR-250注释。 还完全支持常规的Spring生命周期回调。 如果bean实现InitializingBean,DisposableBean或Lifecycle,则容器将调用它们各自的方法。 还完全支持标准的* Aware接口集(例如BeanFactoryAware,BeanNameAware,MessageSourceAware,ApplicationContextAware等)。 @Bean注释支持指定任意的初始化和销毁回调方法,非常类似于bean元素上Spring XML的init-method和destroy-method属性,如以下示例所示: 注意: 缺省情况下,使用Java配置定义的具有公共关闭或关闭方法的bean会自动通过销毁回调进行登记。 如果有一个公共关闭或关闭方法,并且不希望在容器关闭时调用它,则可以在bean定义中添加@Bean(destroyMethod =“”)以禁用默认(推断)模式。 默认情况下,可能要对通过JNDI获取的资源执行此操作,因为其生命周期是在应用程序外部进行管理的。 特别是,请确保始终对数据源执行此操作,因为这在Java EE应用程序服务器上是有问题的 以下示例显示如何防止对数据源的自动销毁回调: 另外,对于@Bean方法,通常使用程序化JNDI查找,方法是使用Spring的JndiTemplate或JndiLocatorDelegate帮助器,或者直接使用JNDI InitialContext而不是JndiObjectFactoryBean变体(这将迫使将返回类型声明为FactoryBean类型,而不是实际的类型。 目标类型,因此很难在打算引用此处提供的资源的其他@Bean方法中用于交叉引用调用。 总结:比如说是外部的服务器提供数据源服务 ,内部程序通过JNDI 来对数据库的连接,现在的问题就是对于datasource这个方法 如果对象是有Spring来管理,[因为系统对数据库的连接一般是有spring来管理,用来把数据库连接分发到其他的需要数据库连接的地方] 现在就是如果服务开启的时候 从服务器获取jndi 和数据源,但是到了容器关闭,也就是服务重启或者是重新发布肯定Spring的容器是要关闭的这个时候dataSource 是不需要关闭的,所以需要指定销毁方法为空 这样就可以了 对于上面例子中的BeanOne,在构造过程中直接调用init()方法同样有效,如下面的例子所示: 注意 当直接使用Java工作时,可以对对象执行任何操作,而不必总是依赖于容器生命周期。 指定Bean范围 Spring包含@Scope注解,以便可以指定bean的范围 使用@Scope注解 可以指定使用@Bean注解定义的bean应该具有特定范围。 可以使用Bean Scopes部分中指定的任何标准范围。 默认范围是单例,但是可以使用@Scope注释覆盖它,如以下示例所示: @Scope和scoped-proxy Spring提供了一种通过作用域代理处理作用域依赖性的便捷方法.使用XML配置时创建此类代理的最简单方法是 如果使用Java从XML参考文档(请参阅作用域代理)将作用域代理示例移植到我们的@Bean,则它类似于以下内容: 自定义Bean命名 默认情况下,配置类使用@Bean方法的名称作为结果bean的名称。 但是,可以使用name属性覆盖此功能,如以下示例所示: Bean别名 如在命名Bean中讨论的那样,有时希望为单个Bean提供多个名称,称为Bean别名。 为此,@ Bean注解的name属性接受一个String数组。 以下示例显示了如何为bean设置多个别名: Bean 描述 有时,提供有关bean的更详细的文本描述会很有帮助。 当暴露出bean(可能通过JMX)以进行监视时,这特别有用。 要向@Bean添加描述,可以使用@Description注释,如以下示例所示: @Configuration是类级别的注释,指示对象是Bean定义的源头。 @Configuration类通过公共@Bean注释方法声明bean。 对@Configuration类的@Bean方法的调用也可以用于定义Bean之间的依赖关系。 有关一般性介绍,请参见基本概念:@Bean和@Configuration。 注入bean间的依赖关系 当bean相互依赖时,表示依赖关系就像让一个bean方法调用另一个依赖一样简单,如以下示例所示: 在前面的示例中,beanOne通过构造函数注入接收对beanTwo的引用。 注意: 仅当在@Configuration类中声明@Bean方法时,此声明bean间依赖关系的方法才有效。 不能使用简单的@Component类声明Bean间的依赖关系; 查找方法注入 如前所述,查找方法注入是一项高级功能,应该很少使用。 在单例作用域的bean对原型作用域的bean有依赖性的情况下,这很有用。 将Java用于这种类型的配置为实现这种模式提供了自然的方法。 以下示例显示如何使用查找方法注入: 通过使用Java配置,可以创建CommandManager的子类,在该子类中,抽象的createCommand()方法将被覆盖,以使其查找新的(原型)命令对象。 以下示例显示了如何执行此操作: 有关基于Java的配置在内部如何工作的更多信息 考虑以下示例,该示例显示了一个@Bean注释方法被调用两次 在clientService1()和clientService2()中分别调用了一次clientDao()。由于此方法创建了ClientDaoImpl的一个新实例并返回它,因此通常需要两个实例(每个服务一个)。这肯定是有问题的:在Spring中,实例化的bean在默认情况下有一个单例范围。这就是神奇之处:所有**@Configuration类在启动时都由CGLIB生成子类**。在子类中,子方法在调用父方法并创建一个新实例之前,首先检查容器是否有缓存的(作用域的)bean。 注意: 根据bean的范围,行为可能有所不同。 我们在这里谈论单例。 从Spring 3.2开始,不再需要将CGLIB添加到的类路径中,因为CGLIB类已经在org.springframework.cglib下重新打包并直接包含在spring-core JAR中。 由于CGLIB在启动时会动态添加功能,因此存在一些限制。 特别是,配置类不能是最终的。 但是,从4.3版本开始,配置类中允许使用任何构造函数,包括使用@Autowired或单个非默认构造函数声明进行默认注入。 如果希望避免任何CGLIB施加的限制,请考虑在非@Configuration类(例如,在普通的@Component类上)声明@Bean方法。 然后,不会拦截@Bean方法之间的跨方法调用,因此必须专门依赖于那里的构造函数或方法级别的依赖项注入。 Spring的基于Java的配置功能使可以撰写注释,从而降低配置的复杂性。 使用@Import注解 与在Spring XML文件中使用元素来帮助模块化配置一样,@Import注解允许从另一个配置类加载@Bean定义,如以下示例所示: 现在,无需在实例化上下文时同时指定ConfigA.class和ConfigB.class,只需显式提供ConfigB,如以下示例所示: 总结 容器开始启动加载的是ConfigB 配置类 这个配置类里面有导入了配置类ConfigA 导致了就是两个配置类被一起加载 这种方法简化了容器的实例化,因为只需要处理一个类,而不需要在构造过程中记住大量的@Configuration类。 总结:就是写一个@Configuration 然后把其他的类(里面具有@bean的配置类)使用@Import 导入进来; 注意: 从Spring Framework 4.2开始,@ Import还支持对常规组件类的引用,类似于 在导入的@Bean定义上注入依赖项 前面的示例有效,但过于简单。 在大多数实际情况下,Bean在配置类之间相互依赖。 使用XML时,这不是问题,因为不涉及任何编译器,所以可以声明ref =“ someBean”并信任Spring在容器初始化期间进行处理.使用@Configuration类时,Java编译器会在配置模型上施加约束,因为对其他bean的引用必须是有效的Java语法。 幸运的是,解决这个问题很简单。 正如我们已经讨论的,@ Bean方法可以具有任意数量的参数来描述Bean的依赖关系。 考虑以下具有多个@Configuration类的更实际的场景,每个类取决于在其他类中声明的bean: 还有另一种方法可以达到相同的结果。 请记住,@ Configuration类最终仅是容器中的另一个bean:这意味着它们可以利用@Autowired和@Value注入以及与任何其他bean相同的其他功能。 注意: 确保以这种方式注入的依赖项只是最简单的一种。 @Configuration类是在上下文初始化期间非常早地处理的,并且强制以这种方式注入依赖项可能导致意外的早期初始化。 如上例所示,尽可能使用基于参数的注入。 另外,通过@Bean使用BeanPostProcessor和BeanFactoryPostProcessor定义时要特别小心。 通常应将这些声明为静态@Bean方法,而不触发其包含的配置类的实例化。 否则,@Autowired和@Value可能不适用于配置类本身,因为可以将其创建为比AutowiredAnnotationBeanPostProcessor早的bean实例。 以下示例说明如何将一个bean自动连接到另一个bean: 现在,ServiceConfig与具体的DefaultRepositoryConfig松散耦合,并且内置的IDE工具仍然有用:可以轻松地获得RepositoryConfig实现的类型层次结构。 通过这种方式,导航@Configuration类及其依赖项与基于接口的代码的通常过程没有什么不同。 如果要影响某些bean的启动创建顺序,请考虑将其中一些声明为@Lazy(用于在首次访问时创建而不是在启动时创建)或声明为@DependsOn某些其他bean(确保在创建其他特定bean之前) 当前的bean,而不是后者的直接依赖项所暗示的)。 有条件地包含@Configuration类或@Bean方法 根据某些系统状态,有条件地启用或禁用完整的@Configuration类甚至单个@Bean方法通常很有用。 一个常见的示例是仅在Spring环境中启用了特定配置文件后,才使用@Profile注解来激活Bean(有关详细信息,请参见Bean定义配置文件)。 @Profile注解实际上是通过使用更灵活的称为@Conditional的注解来实现的。 @Conditional注解指示在注册@Bean之前应参考的特定org.springframework.context.annotation.Condition实现。 Condition接口的实现提供了一个matches(…)方法,该方法返回true或false。 例如,以下清单显示了用于@Profile的实际Condition实现: 结合Java和XML配置 Spring的@Configuration类支持并非旨在100%完全替代Spring XML。 某些工具(例如Spring XML名称空间)仍然是配置容器的理想方法。 在使用XML方便或有必要的情况下,您可以选择:使用 以XML为中心的@Configuration类的使用 最好从XML引导Spring容器并以即席方式包含@Configuration类。 例如,在使用Spring XML的大型现有代码库中,根据需要创建@Configuration类并从现有XML文件中将它们包含在内会变得更加容易。 在本节的后面,我们将介绍在这种“以XML为中心”的情况下使用@Configuration类的选项。 将@Configuration类声明为纯Spring 元素 请记住,@Configuration类最终是容器中的bean定义。 在本系列示例中,我们创建一个名为AppConfig的@Configuration类,并将其作为定义包含在system-test-config.xml中。 因为 注意: 在system-test-config.xml文件中,AppConfig 没有声明id元素。 尽管这样做是可以接受的,但由于没有其他bean引用过它,因此这是不必要的,并且不太可能通过名称从容器中显式获取。 同样,DataSource bean只能按类型自动装配,因此也不严格要求显式bean id。 就是一个AppConfig 没人引用就不用声明id ,datasource 是Autowired 按类型注入DriverManagerDataSource 也不需要声明id 使用 因为@Configuration用@Component进行元注释,所以@Configuration注释的类自动成为组件扫描的候选对象。 使用与先前示例中描述的场景相同的场景,我们可以重新定义system-test-config.xml以利用组件扫描的优势。 请注意,在这种情况下,我们无需显式声明 @Configuration以类为中心的XML与@ImportResource的结合使用 在@Configuration类是配置容器的主要机制的应用程序中,仍然有必要至少使用一些XML。 在这些情况下,您可以使用@ImportResource并仅定义所需的XML。 这样做实现了一种“以Java为中心”的方法来配置容器,并将XML保持在最低限度。 以下示例(包括配置类,定义Bean的XML文件,属性文件和主类)显示了如何使用@ImportResource注解来实现按需使用XML的以Java为中心的配置 Environment接口是集成在容器中的抽象,它对应用程序环境的两个关键方面进行建模:概要文件和属性。 配置文件是仅在给定配置文件处于活动状态时才向容器注册的Bean定义的命名逻辑组 profiles 在bean上面标注需要什么样的环境才会注册bean 容器根据当前的环境去查看是否实例化bean 可以将Bean分配给profiles ,无论是以XML定义还是注解定义,和profiles 相关的``Environment ``Properties bean的定义配置文件提供的一种机制是允许在核心容器里面注册不同的bean根据不同的状态;单词 在开发中针对内存中的数据源进行工作,而不是在进行QA或生产时从JNDI查找相同的数据源。 仅在将应用程序部署到性能环境中时注册监视基础结构。 为客户A和客户B部署注册bean的自定义实现 在需要数据源的实际应用中考虑第一个用例。 在测试环境中,配置可能类似于以下内容 现在,假设该应用程序的数据源已在生产应用程序服务器的JNDI目录中注册,请考虑如何将该应用程序部署到QA或生产环境中。 现在,我们的dataSource bean看起来像下面的清单: 问题是如何根据当前环境在使用这两种变体之间进行切换。 随着时间的流逝,Spring用户已经设计出多种方法来完成此任务,通常依赖于系统环境变量和包含$ {placeholder}令牌的XML 语句的组合,这些语句根据值解析为正确的配置文件路径 环境变量。 Bean定义 如果我们概括前面特定于环境的Bean定义示例中所示的用例,那么最终需要在某些上下文中而不是在其他上下文中注册某些Bean定义。 您可能会说您要在情况A中注册一个特定的bean定义配置文件,在情况B中注册一个不同的配置文件。我们首先更新配置以反映这种需求。 就是根据不同的环境注册不同的bean 注意: 如前所述,在@Bean方法中,通常选择使用程序化JNDI查找,方法是使用Spring的JndiTemplate / JndiLocatorDelegate帮助器或前面显示的直接JNDI InitialContext用法,而不是JndiObjectFactoryBean变体,这将迫使您将返回类型声明为 FactoryBean类型。 概要文件字符串可以包含简单的概要文件名称(例如,生产)或概要文件表达式。 配置文件表达式允许表达更复杂的配置文件逻辑(例如,生产和美国东部)。 概要文件表达式中支持以下运算符: 注意: 不能混用&和| 不使用括号的运算符。 例如,production & us-east | eu-central 不是有效的表达式。 它必须表示为production & (us-east | eu-central)。 您可以将@Profile用作元注释,以创建自定义的组合注释。 以下示例定义了一个自定义@Production注解,您可以将其用作@Profile(“ production”)的替代品: 注意: 如果@Configuration类用@Profile标记,则除非该类中的一个或多个指定的配置文件处于活动状态,否则所有与该类关联的@Bean方法和@Import注解都会被绕过;@Profile({“ p1”,“ p2”}),除非激活了配置文件’p1’或’p2’,否则不会注册或处理该类。 如果给定的配置文件以NOT运算符(!)为前缀,则只有在该配置文件处于非活动状态时,才会注册带注释的元素。 例如,给定@Profile({“ p1”,“!p2”}),如果配置文件’p1’处于活动状态或如果配置文件’p2’未处于活动状态,则会进行注册。 也可以在方法级别将@Profile声明为仅包含配置类的一个特定Bean(例如,特定Bean的替代变体),如以下示例所示: 注意 使用@Bean方法上的@Profile时,可能会出现特殊情况:如果重载的@Bean方法具有相同的Java方法名称(类似于构造函数重载),则必须在所有重载方法上一致声明@Profile条件;如果条件不一致,则仅重载方法中第一个声明的条件很重要。 因此,@ Profile不能用于选择具有特定参数签名的重载方法。 在创建时,同一bean的所有工厂方法之间的解析都遵循Spring的构造函数解析算法。** 如果要定义具有不同概要文件条件的备用Bean,请使用@Bean name属性使用不同的Java方法名称来指向相同的Bean名称,如前面的示例所示。 如果参数签名都相同(例如,所有变体都具有no-arg工厂方法),则这是首先在有效Java类中表示这种排列的唯一方法(因为只能有一个 特定名称和参数签名的方法)。 总结 就是对于相同的bean 和构造参数 尽量使用不同的方法名来区分 XML Bean定义配置文件 XML对应项是元素的profile属性。 我们前面的示例配置可以用两个XML文件重写,如下所示: 也可以避免在同一文件中拆分和嵌套元素,如以下示例所示: spring-bean.xsd已被限制为仅允许这些元素作为文件中的最后一个元素。 这应该有助于提供灵活性,而不会引起XML文件混乱。 注意: XML对应项不支持前面描述的配置文件表达式。 但是,可以使用 在前面的示例中,如果生产和用户配置文件都处于活动状态,则将显示dataSource bean。 激活当前状态 现在我们已经更新了配置,我们仍然需要指示Spring哪个配置文件处于活动状态。 如果立即启动示例应用程序,则会看到抛出NoSuchBeanDefinitionException的消息,因为容器找不到名为dataSource的Spring bean。 可以通过多种方式来激活配置文件,但最直接的方法是针对可通过ApplicationContext获得的Environment API以编程方式进行配置。 以下示例显示了如何执行此操作: 此外,您还可以通过spring.profiles.active属性声明性地激活配置文件,该属性可以通过系统环境变量,JVM系统属性,web.xml中的servlet上下文参数或什至作为JNDI中的条目来指定(请参阅PropertySource Abstraction)。 )。 在集成测试中,可以使用spring-test模块中的@ActiveProfiles注解来声明活动配置文件(请参阅环境配置文件的上下文配置) 请注意,配置文件不是“非此即彼”的命题。 您可以一次激活多个配置文件。 通过编程,您可以为setActiveProfiles()方法提供多个配置文件名称,该方法接受String … varargs。 以下示例激活多个配置文件: 声明性地,spring.profiles.active可以接受以逗号分隔的配置文件名称列表,如以下示例所示: 默认环境 默认配置文件表示默认情况下启用的配置文件。 考虑以下示例 如果没有配置文件处于活动状态,那么将创建dataSource。 您可以看到这是为一个或多个bean提供默认定义的一种方法。 如果启用了任何配置文件,则默认配置文件将不适用。 您可以通过在Environment上使用setDefaultProfiles()或通过使用spring.profiles.default属性声明性地更改默认配置文件的名称。 Spring的环境抽象提供了可配置属性源层次结构上的搜索操作。 考虑以下清单: 在前面的代码片段中,我们看到了一种高级方式来询问Spring是否为当前环境定义了my-property属性。 为了回答这个问题,环境对象在一组PropertySource对象上执行搜索。 PropertySource是对任何键值对源的简单抽象,并且Spring的StandardEnvironment配置有两个PropertySource对象-一个代表JVM系统属性的集合(System.getProperties())和一个代表系统环境变量的集合( System.getenv())。 注意: 这些默认属性源存在于StandardEnvironment中,可在独立应用程序中使用。 StandardServletEnvironment填充了其他默认属性源,包括servlet配置和servlet上下文参数。 它可以选择启用JndiPropertySource。 有关详细信息,请参见javadoc 具体来说,当您使用StandardEnvironment时,如果在运行时存在my-property系统属性或my-property环境变量,则对env.containsProperty(“ my-property”)的调用将返回true。 注意: 执行的搜索是分层的。 默认情况下,系统属性优先于环境变量。因此,如果在调用env.getProperty(“ my-property”)时在两个地方都同时设置了my-property属性,则系统属性值“ wins”并返回。 请注意,属性值不会合并,而是会被前面的条目完全覆盖。 对于常见的StandardServletEnvironment,完整层次结构如下,最高优先级条目位于顶部: ServletConfig参数(如果适用,例如在DispatcherServlet上下文中) ServletContext参数(web.xml上下文参数条目) JNDI环境变量(java:comp / env /条目) JVM系统属性(-D命令行参数) JVM系统环境(操作系统环境变量) 最重要的是,整个机制是可配置的。 也许您有一个要集成到此搜索中的自定义属性源。 为此,实现并实例化您自己的PropertySource并将其添加到当前环境的PropertySources集中。 以下示例显示了如何执行此操作: 在前面的代码中,在搜索中添加了具有最高优先级的MyPropertySource。 如果它包含my-property属性,则将检测并返回该属性,以支持任何其他PropertySource中的my-property属性。 MutablePropertySources API公开了许多方法,这些方法允许对属性源集进行精确操作 @PropertySource注释为将PropertySource添加到Spring的环境中提供了一种方便的声明性机制。 给定一个名为app.properties的文件,其中包含键-值对testbean.name = myTestBean,下面的@Configuration类使用@PropertySource,其方式是对testBean.getName()的调用返回myTestBean: @PropertySource资源位置中存在的任何$ {…}占位符都是根据已经针对该环境注册的一组属性源来解析的,如以下示例所示: 假设my.placeholder存在于已注册的属性源之一(例如,系统属性或环境变量)中,则占位符将解析为相应的值。 如果不是,则将default / path用作默认值。 如果未指定默认值并且无法解析属性,则抛出IllegalArgumentException。 注意: 根据Java 8约定,@PropertySource注解是可重复的。 但是,所有此类@PropertySource注解都需要在同一级别上声明,可以直接在配置类上声明,也可以在同一自定义注解中声明为元注解。 不建议将直接注释和元注释混合使用,因为直接注释会有效地覆盖元注释。 从历史上看,元素中占位符的值只能根据JVM系统属性或环境变量来解析。 这已不再是这种情况。 由于环境抽象是在整个容器中集成的,因此很容易通过它路由占位符的解析。 这意味着您可以按照自己喜欢的任何方式配置解析过程。 您可以更改搜索系统属性和环境变量的优先级,也可以完全删除它们。 您还可以根据需要将自己的属性源添加到混合中。 具体而言,以下语句无论在何处定义客户属性都有效,只要该属性在环境中可用即可: Spring使用LoadTimeWeaver在将类加载到Java虚拟机(JVM)中时对其进行动态转换; 要启用加载时编织,可以将@EnableLoadTimeWeaving添加到您的@Configuration类之一,如以下示例所示: 另外,对于XML配置,可以使用context:load-time-weaver元素: 为ApplicationContext配置后,该ApplicationContext中的任何bean都可以实现LoadTimeWeaverAware,从而接收对加载时weaver实例的引用。 与Spring的JPA支持结合使用时,该功能特别有用,因为在JPA类转换中可能需要进行加载时编织。 有关更多详细信息,请查阅LocalContainerEntityManagerFactoryBean javadoc。 有关AspectJ加载时编织的更多信息,请参见Spring框架中的AspectJ加载时编织 如本章介绍中所讨论的,org.springframework.beans.factory包提供了用于管理和操纵bean的基本功能,包括以编程方式。 org.springframework.context包添加了ApplicationContext接口,该接口扩展了BeanFactory接口,此外还扩展了其他接口以提供更多面向应用程序框架的样式的附加功能。 许多人以完全声明性的方式使用ApplicationContext,甚至没有以编程方式创建它,而是依靠诸如ContextLoader之类的支持类来自动实例化ApplicationContext作为Java EE Web应用程序正常启动过程的一部分。 为了以更加面向框架的方式增强BeanFactory的功能,上下文包还提供以下功能: 通过MessageSource界面访问i18n样式的消息。 通过ResourceLoader界面访问资源,例如URL和文件。 通过使用ApplicationEventPublisher接口,将事件发布到实现ApplicationListener接口的bean。 加载多个(分层)上下文,使每个上下文都通过HierarchicalBeanFactory接口集中在一个特定层上,例如应用程序的Web层。 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的父级。 如果是这样,它将使用该bean作为MessageSource。 如果ApplicationContext找不到任何消息源,则将实例化一个空的DelegatingMessageSource,以便能够接受对上述方法的调用。 Spring提供了两个MessageSource实现,即ResourceBundleMessageSource和StaticMessageSource。 两者都实现HierarchicalMessageSource以便进行嵌套消息传递。 StaticMessageSource很少使用,但是提供了将消息添加到源中的编程方式。 下面的示例显示ResourceBundleMessageSource: 该示例假设您在类路径中定义了三个资源包,分别称为格式,异常和窗口。 解析消息的任何请求都通过JDK标准的通过ResourceBundle对象解析消息的方式来处理。 就本示例而言,假定上述两个资源束文件的内容如下: 下一个示例显示了执行MessageSource功能的程序。 请记住,所有ApplicationContext实现也是MessageSource实现,因此可以转换为MessageSource接口。可以强转 以上程序的结果输出如下 text 总而言之,MessageSource是在名为beans.xml的文件中定义的,该文件位于类路径的根目录下。 messageSource bean定义通过其basenames属性引用了许多资源包。 列表中传递给basenames属性的三个文件在类路径的根目录下以文件形式存在,分别称为format.properties,exceptions.properties和windows.properties。 下一个示例显示了传递给消息查找的参数。 这些参数将转换为String对象,并插入到查找消息中的占位符中。 关于国际化(“ i18n”),Spring的各种MessageSource实现遵循与标准JDK ResourceBundle相同的语言环境解析和后备规则。 简而言之,并继续前面定义的示例messageSource,如果要针对英国(en-GB)语言环境解析消息,则可以分别创建名为format_en_GB.properties,exceptions_en_GB.properties和windows_en_GB.properties的文件。 您还可以使用MessageSourceAware接口获取对已定义的任何MessageSource的引用。 创建和配置bean时,在ApplicationContext中实现MessageSourceAware接口的所有bean都会与应用程序上下文的MessageSource一起注入。 注意: 作为ResourceBundleMessageSource的替代,Spring提供了ReloadableResourceBundleMessageSource类。 此变体支持相同的包文件格式,但比基于标准JDK的ResourceBundleMessageSource实现更灵活。 特别是,它允许从任何Spring资源位置(不仅从类路径)读取文件,并且支持捆绑属性文件的热重载(同时在它们之间有效地进行缓存)。 有关详细信息,请参见ReloadableResourceBundleMessageSource javadoc。 通过ApplicationEvent类和ApplicationListener接口提供ApplicationContext中的事件处理。 如果将实现ApplicationListener接口的bean部署到上下文中,则每次将ApplicationEvent发布到ApplicationContext时,都会通知该bean。 本质上,这是标准的Observer 观察者设计模式。 注意: 从Spring 4.2开始,事件基础结构得到了显着改进,并提供了基于注释的模型以及发布任意事件(即不一定从ApplicationEvent扩展的对象)的功能。 发布此类对象后,我们会为您包装一个事件。 您还可以创建和发布自己的自定义事件。 以下示例显示了一个简单的类,该类扩展了Spring的ApplicationEvent基类: 若要发布自定义ApplicationEvent,**请在ApplicationEventPublisher上调用publishEvent()**方法。 通常,这是通过创建一个实现ApplicationEventPublisherAware的类并将其注册为Spring Bean来完成的。 以下示例显示了此类: 在配置时,Spring容器检测到EmailService实现了ApplicationEventPublisherAware并自动调用setApplicationEventPublisher()。 实际上,传入的参数是Spring容器本身。 您正在通过其ApplicationEventPublisher接口与应用程序上下文进行交互。 要接收自定义ApplicationEvent,可以创建一个实现ApplicationListener的类并将其注册为Spring Bean。 以下示例显示了此类: 请注意,ApplicationListener通常使用您的自定义事件的类型(上一示例中的BlackListEvent)进行参数化。 这意味着onApplicationEvent()方法可以保持类型安全,从而避免了任何向下转换的需求。 您可以根据需要注册任意数量的事件侦听器,但是请注意,默认情况下,事件侦听器会同步接收事件。 这意味着publishEvent()方法将阻塞,直到所有侦听器都已完成对事件的处理为止。 这种同步和单线程方法的一个优点是,当侦听器接收到事件时,如果有可用的事务上下文,它将在发布者的事务上下文内部进行操作。 如果有必要采用其他发布事件的策略,请参阅Spring的ApplicationEventMulticaster接口的javadoc和配置选项的SimpleApplicationEventMulticaster实现。 以下示例显示了用于注册和配置每个bean的bean定义。 总而言之,当调用emailService bean的sendEmail()方法时,如果有任何电子邮件应列入黑名单,则将发布BlackListEvent类型的自定义事件。 blackListNotifier bean被注册为ApplicationListener并接收BlackListEvent,这时它可以通知适当的参与者。 注意: Spring的事件机制旨在在同一应用程序上下文内在Spring bean之间进行简单的通信。 但是,对于更复杂的企业集成需求,单独维护的Spring Integration项目为基于著名的Spring编程模型构建轻量级,面向模式,事件驱动的架构提供了完整的支持。 基于注释的事件侦听器 从Spring 4.2开始,您可以使用@EventListener注解在托管Bean的任何公共方法上注册事件侦听器。 BlackListNotifier可以重写如下: 方法签名再次声明其侦听的事件类型,但是这次使用灵活的名称,并且没有实现特定的侦听器接口。 只要实际事件类型在其实现层次结构中解析通用参数,也可以通过通用类型来缩小事件类型。 如果您的方法应该侦听多个事件,或者如果您想完全不使用任何参数来定义它,则事件类型也可以在注释本身上指定。 以下示例显示了如何执行此操作: 也可以通过使用定义SpEL表达式的注释的condition属性来添加其他运行时过滤,该注释应匹配以针对特定事件实际调用该方法。 以下示例显示了仅当事件的content属性等于my-event时,才可以重写我们的通知程序以进行调用: 每个SpEL表达式都会根据专用上下文进行评估。 下表列出了可用于上下文的项目,以便您可以将它们用于条件事件处理: 请注意,即使您的方法签名实际上引用了已发布的任意对象,#root.event也使您可以访问基础事件。 如果由于处理另一个事件而需要发布一个事件,则可以更改方法签名以返回应发布的事件,如以下示例所示: 此新方法为上述方法处理的每个BlackListEvent发布一个新的ListUpdateEvent。 如果您需要发布多个事件,则可以返回事件的Collection。 异步侦听器 如果希望特定的侦听器异步处理事件,则可以重用常规的@Async支持。 以下示例显示了如何执行此操作: 使用异步事件时,请注意以下限制: 如果异步事件侦听器引发Exception,则不会将其传播到调用方。 有关更多详细信息,请参见AsyncUncaughtExceptionHandler 异步事件侦听器方法无法通过返回值来发布后续事件。 如果您需要发布另一个事件作为处理的结果,请注入ApplicationEventPublisher以手动发布事件。 监听器执行顺序 如果需要先调用一个侦听器,则可以将@Order注解添加到方法声明中,如以下示例所示: 泛型事件 您还可以使用泛型来进一步定义事件的结构。 考虑使用EntityCreatedEvent ,其中T是已创建的实际实体的类型。 例如,您可以创建以下侦听器定义以仅接收Person的EntityCreatedEvent: 由于类型擦除,只有在触发的事件解析了事件侦听器所依据的通用参数(即类似PersonCreatedEvent的类扩展EntityCreatedEvent {…})时,此方法才起作用 在某些情况下,如果所有事件都遵循相同的结构,这可能会变得很乏味(就像前面示例中的事件一样)。 在这种情况下,您可以实现ResolvableTypeProvider来指导框架超出运行时环境提供的范围。 以下事件显示了如何执行此操作 为了获得最佳用法和对应用程序上下文 应用程序上下文是ResourceLoader,可用于加载Resource对象.Resource本质上是JDK java.net.URL类的功能更丰富的版本.实际上,Resource的实现在适当的地方包装了java.net.URL的一个实例。资源可以以透明的方式从几乎任何位置获取低级资源,包括从类路径,文件系统位置,可使用标准URL描述的任何位置以及一些其他变体。如果资源位置字符串是没有任何特殊前缀的简单路径,则这些资源的来源是特定的,并且适合于实际的应用程序上下文类型。 您可以配置部署到应用程序上下文中的Bean,以实现特殊的回调接口ResourceLoaderAware,以便在初始化时自动回调,并将应用程序上下文本身作为ResourceLoader传入,您还可以公开Resource类型的属性,以用于访问静态资源。 它们像其他任何属性一样注入其中。 您可以将那些Resource属性指定为简单的String路径,并在部署bean时依靠从这些文本字符串到实际Resource对象的自动转换。 提供给ApplicationContext构造函数的一个或多个位置路径实际上是资源字符串,并且根据特定的上下文实现以简单的形式对其进行适当处理。 例如,ClassPathXmlApplicationContext将简单的位置路径视为类路径位置。 您也可以使用带有特殊前缀的位置路径(资源字符串)来强制从类路径或URL中加载定义,而不管实际的上下文类型如何。 您可以使用例如ContextLoader声明性地创建ApplicationContext实例。 当然,您还可以使用ApplicationContext实现之一以编程方式创建ApplicationContext实例。 您可以使用ContextLoaderListener注册ApplicationContext,如以下示例所示: 监听器检查contextConfigLocation参数。 如果参数不存在,那么侦听器将使用/WEB-INF/applicationContext.xml作为默认值。 当参数确实存在时,侦听器将使用预定义的定界符(逗号,分号和空格)来分隔String,并将这些值用作搜索应用程序上下文的位置。 还支持退票费风格的路径模式。 示例包括/WEB-INF/*Context.xml(适用于所有名称以Context.xml结尾且位于WEB-INF目录中的文件)和/ WEB-INF / ** / * Context.xml(适用于所有此类文件) 文件在WEB-INF的任何子目录中)。 可以将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绑定的JDBC DataSource实例以及JMS ConnectionFactory实例,并且还可以在平台的JMX服务器上注册-整个过程都通过Spring的标准事务管理以及JNDI和JMX支持工具进行。 应用程序组件还可以通过Spring的TaskExecutor抽象与应用程序服务器的JCA WorkManager进行交互。 完全不理解 有关RAR部署中涉及的配置详细信息,请参见SpringContextResourceAdapter类的javadoc。 将所有应用程序类打包到RAR文件(这是具有不同文件扩展名的标准JAR文件)中。 将所有必需的库JAR添加到RAR归档文件的根目录中。 添加一个META-INF / ra.xml部署描述符(如SpringContextResourceAdapter的javadoc中所示)和相应的Spring XML bean定义文件(通常为META-INF / applicationContext.xml)。 将生成的RAR文件拖放到应用程序服务器的部署目录中。 BeanFactory API为Spring的IoC功能提供了基础。 它的特定合同主要用于与Spring的其他部分以及相关的第三方框架集成,并且它的 BeanFactory和相关接口(例如BeanFactoryAware,InitializingBean,DisposableBean)是其他框架组件的重要集成点。 通过不需要任何注释,甚至不需要反射,它们可以在容器及其组件之间进行非常有效的交互。 应用程序级Bean可以使用相同的回调接口,但通常更喜欢通过注释或通过编程配置进行声明式依赖注入。 请注意,核心BeanFactory API级别及其DefaultListableBeanFactory实现不对配置格式或要使用的任何组件注解进行假设。 所有这些风格都是通过扩展(例如XmlBeanDefinitionReader和AutowiredAnnotationBeanPostProcessor)引入的,并以核心元数据表示形式对共享BeanDefinition对象进行操作。 这就是使Spring的容器如此灵活和可扩展的本质 本节说明BeanFactory和ApplicationContext容器级别之间的区别以及对引导的影响。 除非有充分的理由不使用ApplicationContext,否则应该使用ApplicationContext,将generiapplicationcontext及其子类AnnotationConfigApplicationContext作为自定义引导的常见实现。 这些是所有常见用途的Spring核心容器的主要入口点.加载配置文件,触发类路径扫描,以编程方式注册Bean定义和带注释的类,以及(从5.0版本开始)注册功能性Bean定义。 ApplicationContext继承了接口BeanFactory 因为ApplicationContext包含BeanFactory的所有功能,所以通常建议在普通BeanFactory上使用,除非需要完全控制Bean处理的方案。在ApplicationContext(例如GenericApplicationContext实现)中,按照约定(即,按Bean名称或Bean类型(尤其是后处理器))检测到几种Bean,而普通的DefaultListableBeanFactory不知道任何特殊的Bean。 对于许多扩展的容器功能,例如注释处理和AOP代理,BeanPostProcessor扩展点是必不可少的。 如果仅使用普通的DefaultListableBeanFactory,则默认情况下不会检测到此类后处理器并将其激活。 这种情况可能会造成混淆,因为您的bean配置实际上并没有错。 而是在这种情况下,需要通过其他设置完全引导容器。 下表列出了BeanFactory和ApplicationContext接口和实现所提供的功能。 要向DefaultListableBeanFactory显式注册Bean后处理器,需要以编程方式调用addBeanPostProcessor,如以下示例所示: 要将BeanFactoryPostProcessor应用于普通的DefaultListableBeanFactory,您需要调用其postProcessBeanFactory方法,如以下示例所示: 在这两种情况下,显式的注册步骤都不方便,这就是为什么在Spring支持的应用程序中,各种ApplicationContext变量比普通的DefaultListableBeanFactory更为可取的原因,尤其是在典型企业设置中依赖BeanFactoryPostProcessor和BeanPostProcessor实例来扩展容器功能时。 本章介绍了Spring如何处理资源以及如何在Spring中使用资源。 它包括以下主题: 不幸的是,Java的标准java.net.URL类和用于各种URL前缀的标准处理程序不足以满足所有对低级资源的访问。 例如,没有标准化的URL实现可用于访问需要从类路径或相对于ServletContext获得的资源。虽然可以注册用于特殊URL前缀的新处理程序(类似于用于诸如http:的现有前缀的处理程序),但这通常相当复杂,并且URL接口仍然缺少某些理想的功能,例如用于检查是否存在的方法 指向的资源。 Spring的Resource接口旨在成为一种功能更强大的接口,用于抽象化对低级资源的访问。 以下清单显示了Resource接口定义: Resource接口中一些最重要的方法是: getInputStream():找到并打开资源,返回一个InputStream以便从资源中读取。 预期每次调用都返回一个新的InputStream。调用者有责任关闭流。 exist():返回一个布尔值,指示此资源是否实际以物理形式存在 是否在存在文件中 isOpen():返回一个布尔值,指示此资源是否表示具有打开流的句柄。 如果为true,则不能多次读取InputStream,必须只读取一次,然后将其关闭以避免资源泄漏。 对于所有常规资源实现,返回false,但InputStreamResource除外。 getDescription():返回对此资源的描述,用于在处理资源时用于错误输出。 这通常是标准文件名或资源的实际URL。 其他方法可让您获取代表资源的实际URL或File对象(如果基础实现兼容并且支持该功能)当需要资源时,Spring本身广泛使用Resource抽象作为许多方法签名中的参数类型。 一些Spring API中的其他方法(例如,各种ApplicationContext实现的构造函数)采用String形式,该字符串以未经修饰或简单的形式用于创建适合该上下文实现的Resource,或者通过String路径上的特殊前缀,让调用者指定 必须创建并使用特定的资源实现。 尽管Spring和Spring经常使用Resource接口,但实际上,在您自己的代码中单独用作通用实用工具类来访问资源也非常有用,即使您的代码不了解或不关心其他任何东西Spring的一部分。 虽然这将您的代码耦合到Spring,但实际上仅将其耦合到这套实用程序类,它们充当URL的更强大替代,并且可以被视为等同于您将用于此目的的任何其他库。 Spring包含以下资源实现: UrlResource包装了java.net.URL,可用于访问通常可以通过URL访问的任何对象,例如文件,HTTP目标,FTP目标等。 所有URL都具有标准化的String表示形式,因此使用适当的标准化前缀来指示另一种URL类型。 其中包括file:用于访问文件系统路径,http:用于通过HTTP协议访问资源,ftp:用于通过FTP访问资源等。 UrlResource是由Java代码通过显式使用UrlResource构造函数创建的,但通常在调用带有String参数表示路径的API方法时隐式创建。 对于后一种情况,JavaBeans PropertyEditor最终决定要创建哪种类型的资源。 如果路径字符串包含众所周知的前缀(例如,classpath :),则它将为该前缀创建适当的专用资源。 但是,如果它不能识别前缀,则假定该字符串是标准URL字符串并创建一个UrlResource。 此类表示应从类路径获取的资源。 它使用线程上下文类加载器,给定的类加载器或给定的类来加载资源。 如果类路径资源驻留在文件系统中,而不是驻留在jar中并且尚未(通过servlet引擎或任何环境将其扩展到)文件系统的类路径资源驻留在文件系统中,则此Resource实现以java.io.File支持解析。 为了解决这个问题,各种Resource实现始终支持将解析作为java.net.URL。 Java代码通过显式使用ClassPathResource构造函数来创建ClassPathResource,但通常在调用带有String参数表示路径的API方法时隐式创建ClassPathResource。 对于后一种情况,JavaBeans PropertyEditor可以识别字符串路径上的特殊前缀classpath: 并在这种情况下创建ClassPathResource。 2.3.3 这是java.io.File和java.nio.file.Path句柄的Resource实现。 它支持解析为文件和URL。 这是ServletContext资源的Resource实现,它解释相关Web应用程序根目录中的相对路径。 它始终支持流访问和URL访问,但仅在扩展Web应用程序档案且资源实际位于文件系统上时才允许java.io.File访问。 它是在文件系统上扩展还是直接扩展,或者直接从JAR或其他类似数据库(可以想到的)中访问,实际上取决于Servlet容器。 InputStreamResource是给定InputStream的Resource实现。 仅当没有特定的资源实现适用时才应使用它。 特别是,在可能的情况下,最好选择ByteArrayResource或任何基于文件的Resource实现。 与其他Resource实现相反,这是一个已经打开的资源的描述符。 因此,它从isOpen()返回true。 如果需要将资源描述符保留在某个地方,或者需要多次读取流,请不要使用它。 这是给定字节数组的Resource实现。 它为给定的字节数组创建一个ByteArrayInputStream。 这对于从任何给定的字节数组加载内容很有用,而不必求助于一次性InputStreamResource。 ResourceLoader接口旨在由可以返回(即加载)Resource实例的对象实现。 以下清单显示了ResourceLoader接口定义: 所有application contexts 均实现ResourceLoader接口。 因此,所有应用程序上下文都可用于获取资源实例。 当您在特定的应用程序上下文中调用getResource(),并且指定的位置路径没有特定的前缀时,您将获得适合该特定应用程序上下文的Resource类型。 例如,假设针对ClassPathXmlApplicationContext实例执行了以下代码片段: 针对ClassPathXmlApplicationContext,该代码返回ClassPathResource。 如果对FileSystemXmlApplicationContext实例执行了相同的方法,则它将返回FileSystemResource。 对于WebApplicationContext,它将返回ServletContextResource。 类似地,它将为每个上下文返回适当的对象。 总结 就是每个不同的ApplicationContext的实现类 内部肯定是具体的实现了ResourceLoader 接口的getResource() 方法,根据不同的实现 返回接口返回类型Resource的不同实现 结果,您可以以适合特定应用程序上下文的方式加载资源。 另一方面,您也可以通过指定特殊的classpath:前缀来强制使用ClassPathResource,而与应用程序上下文类型无关,如下例所示: 同样,您可以通过指定任何标准java.net.URL前缀来强制使用UrlResource。 以下两个示例使用文件和http前缀: 下表总结了将String对象转换为Resource对象的策略: ResourceLoaderAware接口是一个特殊的回调接口,用于标识期望随ResourceLoader参考一起提供的组件。 以下清单显示了ResourceLoaderAware接口的定义: 当一个类实现ResourceLoaderAware并部署到应用程序上下文中(作为Spring托管的bean)时,该类被应用程序上下文识别为ResourceLoaderAware。 然后,应用程序上下文调用setResourceLoader(ResourceLoader),将自身作为参数提供(请记住,Spring中的所有应用程序上下文均实现ResourceLoader接口)。 由于ApplicationContext是ResourceLoader,因此Bean也可以实现ApplicationContextAware接口,并直接使用提供的应用程序上下文来加载资源。 但是,通常,如果需要的话,最好使用专用的ResourceLoader接口。 该代码将仅耦合到资源加载接口(可以视为实用程序接口),而不耦合到整个Spring ApplicationContext接口。 在应用程序组件中,您还可以依靠自动装配ResourceLoader来实现ResourceLoaderAware接口。 “传统”构造函数和byType自动装配模式(如“自动装配协作器”中所述)能够分别为构造函数参数或setter方法参数提供ResourceLoader。 为了获得更大的灵活性(包括自动装配字段和多个参数方法的能力),请考虑使用基于注释的自动装配功能。 在这种情况下,只要有问题的字段,构造函数或方法带有@Autowired注解,ResourceLoader就会自动连接到需要ResourceLoader类型的字段,构造函数参数或方法参数中。 有关更多信息,请参见使用@Autowired。 如果Bean本身将通过某种动态过程来确定和提供资源路径,那么对于Bean来说,使用ResourceLoader接口加载资源可能是有意义的。 例如,考虑加载某种模板,其中所需的特定资源取决于用户的角色。 如果资源是静态的,则有必要完全消除对ResourceLoader接口的使用,让Bean公开所需的Resource属性,并期望将其注入其中。 然后注入这些属性的微不足道,所有应用程序上下文都注册并使用了特殊的JavaBeans PropertyEditor,它可以将String路径转换为Resource对象。 因此,如果myBean具有资源类型的模板属性,则可以为该资源配置一个简单的字符串,如以下示例所示: 请注意,资源路径没有前缀。 因此,由于应用程序上下文本身将用作ResourceLoader,因此根据上下文的确切类型,通过ClassPathResource,FileSystemResource或ServletContextResource加载资源本身。 如果需要强制使用特定的资源类型,则可以使用前缀。 以下两个示例显示了如何强制ClassPathResource和UrlResource(后者用于访问文件系统文件): 本节介绍如何使用资源创建应用程序上下文,包括使用XML的快捷方式,如何使用通配符以及其他详细信息。 应用程序上下文构造函数(针对特定的应用程序上下文类型)通常采用字符串或字符串数组作为资源的位置路径,例如构成上下文定义的XML文件。 当这样的位置路径没有前缀时,从该路径构建并用于加载Bean定义的特定Resource类型取决于特定应用程序上下文,并且适用于该特定应用程序上下文。 例如,考虑以下示例,该示例创建一个ClassPathXmlApplicationContext: 由于使用了ClassPathResource,因此从类路径中加载了Bean定义。 但是,请考虑以下示例,该示例创建一个FileSystemXmlApplicationContext: 现在,bean定义是从文件系统位置(在这种情况下,是相对于当前工作目录)加载的 请注意,在位置路径上使用特殊的类路径前缀或标准URL前缀会覆盖为加载定义而创建的默认资源类型。 考虑以下示例: 使用FileSystemXmlApplicationContext从类路径加载bean定义。 但是,它仍然是FileSystemXmlApplicationContext。 如果随后将其用作ResourceLoader,则任何未前缀的路径仍将视为文件系统路径。 构造ClassPathXmlApplicationContext实例-快捷方式 ClassPathXmlApplicationContext公开了许多构造函数以启用方便的实例化。 基本思想是,您只能提供一个字符串数组,该字符串数组仅包含XML文件本身的文件名(不包含前导路径信息),并且还提供一个Class。 然后,ClassPathXmlApplicationContext从提供的类中派生路径信息。 以下示例显示如何实例化由在名为service.xml和daos.xml(位于类路径中)的文件中定义的bean组成的ClassPathXmlApplicationContext实例: 有关各种构造函数的详细信息,请参见ClassPathXmlApplicationContext javadoc。 应用程序上下文构造函数值中的资源路径可以是简单路径(如先前所示),每个路径都具有到目标资源的一对一映射,或者可以包含特殊的“ classpath *:”前缀或内部Ant。 样式的正则表达式(通过使用Spring的PathMatcher实用程序进行匹配)。 后者都是有效的通配符 这种机制的一种用途是当您需要进行组件样式的应用程序组装时。 所有组件都可以将上下文定义片段“发布”到一个众所周知的位置路径,并且当使用前缀为classpath *:的相同路径创建最终应用程序上下文时,将自动拾取所有组件片段。 请注意,此通配符特定于在应用程序上下文构造函数中使用资源路径(或当您直接使用PathMatcher实用工具类层次结构时),并且在构造时已解决。 它与资源类型本身无关。 您不能使用classpath *:前缀来构造实际的Resource,因为资源一次仅指向一个资源。 Ant-style 格式 路径位置可以包含Ant样式的模式,如以下示例所示: 当路径位置包含Ant样式的模式时,解析程序将遵循更复杂的过程来尝试解析通配符。 它为到达最后一个非通配符段的路径生成资源,并从中获取URL。 如果此URL不是jar:URL或特定于容器的变体(例如WebLogic中的zip:,WebSphere中的wsjar等),则从中获取java.io.File并将其用于遍历 文件系统。 对于jar URL,解析器可以从中获取java.net.JarURLConnection,也可以手动解析jar URL,然后遍历jar文件的内容以解析通配符。 对可移植性的影响 如果指定的路径已经是一个文件URL(由于基本ResourceLoader是一个文件系统,所以它是隐式的,或者是显式的),则保证通配符可以完全可移植的方式工作。 如果指定的路径是类路径位置,则解析器必须通过调用Classloader.getResource()获得最后的非通配符路径段URL。 由于这只是路径的一个节点(而不是末尾的文件),因此实际上(在ClassLoader javadoc中)未定义确切返回的是哪种URL。 实际上,它始终是代表目录的java.io.File(类路径资源在其中解析到文件系统位置)或某种jar URL(类路径资源在jar上解析)。 尽管如此,此操作仍存在可移植性问题。 如果为最后一个非通配符段获取了jar URL,则解析程序必须能够从中获取java.net.JarURLConnection或手动解析jar URL,以便能够遍历jar的内容并解析通配符 。 这在大多数环境中确实有效,但在其他环境中则无效,因此我们强烈建议您在依赖特定环境之前,对来自jars的资源的通配符解析进行彻底测试。 类路径classpath:前缀* 在构造基于XML的应用程序上下文时,位置字符串可以使用特殊的classpath *:前缀,如以下示例所示: 这个特殊的前缀指定必须获取与给定名称匹配的所有类路径资源(内部,这实际上是通过调用ClassLoader.getResources(…)发生的),然后合并形成最终的应用程序上下文定义。 注意: 通配符类路径依赖于基础类加载器的getResources()方法。 由于当今大多数应用程序服务器都提供自己的类加载器实现,因此行为可能有所不同,尤其是在处理jar文件时。 检查classpath *是否有效的一个简单测试是使用classloader从classpath的jar中加载文件: 您还可以在其余位置路径中将classpath *:前缀与PathMatcher模式结合使用(例如,classpath *:META-INF / *-beans.xml)。 在这种情况下,解析策略非常简单:在最后一个非通配符路径段上使用ClassLoader.getResources()调用,以获取类加载器层次结构中的所有匹配资源,然后从每个资源获取相同的PathMatcher解析 前面描述的策略用于通配符子路径。 有关通配符的其他说明 请注意,当classpath *:与Ant样式的模式结合使用时,除非模式文件实际驻留在文件系统中,否则在模式启动之前,它只能与至少一个根目录可靠地一起工作。 这意味着诸如classpath :。xml之类的模式可能不会从jar文件的根目录检索文件,而只会从扩展目录的根目录检索文件。 Spring检索类路径条目的能力源自JDK的ClassLoader.getResources()方法,该方法仅返回文件系统中的空字符串位置(指示可能要搜索的根目录)。 Spring也会评估jar文件中的URLClassLoader运行时配置和java.class.path清单,但这不能保证会导致可移植行为 注意: 扫描类路径包需要在类路径中存在相应的目录条目。 使用Ant构建JAR时,请勿激活JAR任务的仅文件开关。 此外,在某些环境中,基于安全策略,可能不会公开类路径目录-例如,在JDK 1.7.0_45及更高版本上的独立应用程序(要求在清单中设置“受信任的库”。请参阅https:// /stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。 在JDK 9的模块路径(Jigsaw)上,Spring的类路径扫描通常可以正常进行。 强烈建议在此处将资源放入专用目录,以避免在搜索jar文件根目录级别时出现上述可移植性问题。 具有类路径的Ant-style模式:如果要搜索的根包在多个类路径位置可用,则不能保证资源找到匹配的资源。 考虑以下资源位置示例: 现在考虑某人可能用来尝试找到该文件的Ant样式的路径: 这样的资源可能只在一个位置,但是当使用诸如上一示例的路径尝试对其进行解析时,解析器将处理getResource(“ com / mycompany”);返回的(第一个)URL。 如果此基本包节点存在于多个类加载器位置,则实际的最终资源可能不存在。 因此,在这种情况下,*您应该首选使用具有相同Ant样式模式的classpath :,该模式将搜索包含根包的所有类路径位置。 就是推荐使用类路径在通配符 未附加到FileSystemApplicationContext的FileSystemResource(即,当FileSystemApplicationContext不是实际的ResourceLoader时)将按您期望的方式处理绝对路径和相对路径。 相对路径是相对于当前工作目录的,而绝对路径是相对于文件系统的根的。 但是,出于向后兼容性(历史)的原因,当FileSystemApplicationContext是ResourceLoader时,此情况会更改。 FileSystemApplicationContext强制所有附加的FileSystemResource实例将所有位置路径都视为相对位置,无论它们是否以前斜杠开头。 实际上,这意味着以下示例是等效的 以下示例也是等效的(尽管使它们有所不同是有意义的,因为一种情况是相对的,另一种情况是绝对的): 在实践中,如果需要真正的绝对文件系统路径,则应避免将绝对路径与FileSystemResource或FileSystemXmlApplicationContext一起使用,并通过使用file:URL前缀来强制使用UrlResource。 以下示例显示了如何执行此操作:<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-context-indexerartifactId>
<version>5.2.6.RELEASEversion>
<optional>trueoptional>
dependency>
dependencies>
1.11 使用JSR 330标准注解
<dependency>
<groupId>javax.injectgroupId>
<artifactId>javax.injectartifactId>
<version>1version>
dependency>
1.11.1 @Inject和@Named的依赖注入
import javax.inject.Inject;
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.findMovies(...);
// ...
}
}
import javax.inject.Inject;
import javax.inject.Provider;
public class SimpleMovieLister {
private Provider<MovieFinder> movieFinder;
@Inject
public void setMovieFinder(Provider<MovieFinder> movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.get().findMovies(...);
// ...
}
}
import javax.inject.Inject;
import javax.inject.Named;
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
public class SimpleMovieLister {
@Inject
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
// ...
}
}
1.11.2 @Named和@ManagedBean:@Component注解的标准等效项
import javax.inject.Inject;
import javax.inject.Named;
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
Spring
javax.inject.*
javax.inject restrictions / comments
@Autowired
@Inject
@Inject
没有 ‘required’ 属性. 可以使用 Java 8’s Optional
代替.
@Component
@Named / @ManagedBean
JSR-330 没有提供可以组合的模型, 只能用一个方式去命名组件
@Scope(“singleton”)
@Singleton
JSR-330的默认范围类似于Spring的
prototype
。 但是,为了使其与Spring的常规默认设置保持一致,默认情况下,在Spring容器中声明的JSR-330 bean是singleton
的。 为了使用单例以外的其他范围,应该使用Spring的@Scope注解。 javax.inject还提供了@Scope注解。 不过,此仅用于创建自己的注解
@Qualifier
@Qualifier / @Named
`javax.inject.Qualifier只是用于构建自定义限定符的元注解。 可以通过javax.inject.Named关联具体的字符串限定符(例如Spring的@Qualifier带有值)
@Value
-
没有等效
@Required
-
没有等效
@Lazy
-
没有等效
ObjectFactory
Provider
javax.inject.Provider
是Spring的ObjectFactory的直接替代方法,只是具有较短的get()方法名称。 它也可以与Spring的@Autowired结合使用,也可以与无注解的构造函数和setter方法结合使用。1.12 基于java代码的容器配置
AnnotationConfigApplicationContext
]1.12.1。 基本概念:@Bean和@Configuration
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
beans>
1.12.2 使用实例化Spring容器通过
AnnotationConfigApplicationContext
ClassPathXmlApplicationContext
时将Spring XML文件用作输入的方式几乎相同,实例化AnnotationConfigApplicationContext
时可以将@Configuration类用作输入。 如下面的示例所示,这允许完全不使用XML来使用Spring容器:public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
register(Class>…)**
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
scan(String…)
@Configuration
@ComponentScan(basePackages = "com.acme") ///此注释启用组件扫描。
public class AppConfig {
...
}
<beans>
<context:component-scan base-package="com.acme"/>
beans>
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
AnnotationConfigWebApplicationContext
WebApplicationContext
的变体/实现之一AnnotationConfigApplicationApplicationContext
的可用于AnnotationConfigWebApplicationContext。在配置Spring ContextLoaderListener Servlet侦听器,Spring MVC DispatcherServlet等时,可以使用此实现。 以下web.xml代码片段配置了典型的Spring MVC Web应用程序(请注意contextClass context-param和init-param的用法):<web-app>
<context-param>
<param-name>contextClassparam-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
param-value>
context-param>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>com.acme.AppConfigparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<servlet>
<servlet-name>dispatcherservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextClassparam-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
param-value>
init-param>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>com.acme.web.MvcConfigparam-value>
init-param>
servlet>
<servlet-mapping>
<servlet-name>dispatcherservlet-name>
<url-pattern>/app/*url-pattern>
servlet-mapping>
web-app>
1.12.3 使用@Bean注释
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
beans>
transferService -> com.acme.TransferServiceImpl
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
public class BeanOne {
public void init() {
// initialization logic
}
}
public class BeanTwo {
public void cleanup() {
// destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public BeanOne beanOne() {
return new BeanOne();
}
@Bean(destroyMethod = "cleanup")
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup("MyDS");
}
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
return new UserPreferences();
}
@Bean
public Service userService() {
UserService service = new SimpleUserService();
// a reference to the proxied userPreferences bean
service.setUserPreferences(userPreferences());
return service;
}
@Configuration
public class AppConfig {
@Bean(name = "myThing")
public Thing thing() {
return new Thing();
}
}
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
1.12.4 使用@Configuration注解
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
public abstract class CommandManager {
public Object process(Object commandState) {
// 获取适当的Command接口的新实例
Command command = createCommand();
// 在(希望是全新的)Command实例上设置状态
command.setState(commandState);
return command.execute();
}
// 好的...但是该方法的实现在哪里?
protected abstract Command createCommand();
}
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
AsyncCommand command = new AsyncCommand();
// 根据需要在此处注入依赖项
return command;
}
@Bean
public CommandManager commandManager() {
//使用createCommand()返回CommandManager的新匿名实现
// 重写后返回一个新的prototype命令对象
return new CommandManager() {
protected Command createCommand() {
return asyncCommand();
}
}
}
@Configuration
public class AppConfig {
@Bean
public ClientService clientService1() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientService clientService2() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientDao clientDao() {
return new ClientDaoImpl();
}
}
1.12.5 组成基于Java的配置
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration //这个就是导入其他的配置类
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
// 现在,beanA和beanB都将可用
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
AnnotationConfigApplicationContext.register
方法.如果要通过使用一些配置类作为入口点来显式定义所有组件,从而避免组件扫描,则此功能特别有用。@Configuration
public class ServiceConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
=======================================================================
@Configuration
public class RepositoryConfig {
@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}
}
=======================================================================
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
return new DataSource
}
}
=======================================================================
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
=======================================================================
@Configuration
public class RepositoryConfig {
private final DataSource dataSource;
public RepositoryConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
}
=======================================================================
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
}
ClassPathXmlApplicationContext
以XML为中心”的方式实例化容器或使用AnnotationConfigApplicationContext
和@ImportResource注释以“以Java为中心”的方式实例化它,以根据需要导入XML。@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService() {
return new TransferService(accountRepository());
}
}
<beans>
<context:annotation-config/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="com.acme.AppConfig"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
bean>
beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
<beans>
<context:component-scan base-package="com.acme"/> //会去扫描包 然后获取到Configruation
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
bean>
beans>
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}
properties-config.xml
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
1.13 环境抽象
[profiles] and [properties]
对象会决定哪个是当前的激活状态,那些事被默认激活的;
在几乎所有应用程序中都起着重要作用,并且可能源自多种来源:属性文件,JVM系统属性,系统环境变量,JNDI,servlet上下文参数,临时属性对象,Map对象等;环境对象与属性相关的作用是为用户提供方便的服务界面,用于配置属性源并从中解析属性;
1.13.1 Bean定义配置文件
environment
会意为不同的事物对应不同的用户;并且此功能可以帮助解决许多用例,包括
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
profiles
是一个核心容器功能,可提供此问题的解决方案。@Configuration
@Profile("development")
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");
}
}
!
逻辑非&
逻辑与|
逻辑或@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development") //standaloneDataSource方法仅在开发配置文件中可用。
public DataSource standaloneDataSource() {
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("dataSource")
@Profile("production") //jndiDataSource方法仅在生产配置文件中可用。
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
<beans profile="development"
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="...">
<beans profile="development">
<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>
!
配置文件。 也可以通过嵌套配置文件来应用逻辑&
,如以下示例所示:<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="...">
<beans profile="production">
<beans profile="us-east">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
beans>
beans>
beans>
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
-Dspring.profiles.active="profile1,profile2"
@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();
}
}
1.13.2 资源文件抽象化
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
1.13.3 使用@PropertySource
@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;
}
}
@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;
}
}
1.13.4 声明中的占位符解析
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
beans>
1.14 注册一个LoadTimeWeaver
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
<beans>
<context:load-time-weaver/>
beans>
1.15 ApplicationContext的其他功能
1.15.1 使用MessageSource进行国际化
<beans>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>formatvalue>
<value>exceptionsvalue>
<value>windowsvalue>
list>
property>
bean>
beans>
# in format.properties =
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
System.out.println(message);
}
Alligators rock!
<beans>
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="exceptions"/>
bean>
<bean id="example" class="com.something.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", Locale.ENGLISH);
System.out.println(message);
}
}
The userDao argument is required.
# 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.
1.15.2 标准和自定义事件
Event
解释说明
ContextRefreshedEvent
在初始化或刷新ApplicationContext时发布(例如,通过使用ConfigurableApplicationContext接口上的refresh()方法)。 在这里,“已初始化”是指所有Bean均已加载,检测到并激活了后处理器Bean,已预先实例化单例并且可以使用ApplicationContext对象。 只要尚未关闭上下文,只要选定的ApplicationContext实际上支持这种“热”刷新,就可以多次触发刷新。 例如,XmlWebApplicationContext支持热刷新,但GenericApplicationContext不支持。.
ContextStartedEvent
在ConfigurableApplicationContext接口上使用start()方法启动ApplicationContext时发布。 在这里,“已启动”表示所有Lifecycle bean都收到一个明确的启动信号。 通常,此信号用于在显式停止后重新启动Bean,但也可以用于启动尚未配置为自动启动的组件(例如,尚未在初始化时启动的组件)。
ContextStoppedEvent
通过使用ConfigurableApplicationContext接口上的stop()方法停止ApplicationContext时发布。 在这里,“已停止”表示所有Lifecycle bean都收到一个明确的停止信号。 停止的上下文可以通过start()调用重新启动。
ContextClosedEvent
通过使用ConfigurableApplicationContext接口上的close()方法或通过JVM关闭钩子关闭ApplicationContext时发布。 在这里,“封闭”意味着所有单例bean将被销毁。 关闭上下文后,它将达到使用寿命,无法刷新或重新启动。
RequestHandledEvent
一个特定于Web的事件,告诉所有Bean HTTP请求已得到服务。 请求完成后,将发布此事件。 此事件仅适用于使用Spring的DispatcherServlet的Web应用程序。
ServletRequestHandledEvent
RequestHandledEvent的子类,用于添加特定于Servlet的上下文信息
public class BlackListEvent extends ApplicationEvent {
private final String address;
private final String content;
public BlackListEvent(Object source, String address, String content) {
super(source);
this.address = address;
this.content = content;
}
// accessor and other methods...
}
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 content) {
if (blackList.contains(address)) {
publisher.publishEvent(new BlackListEvent(this, address, content));
return;
}
// send email...
}
}
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...
}
}
<bean id="emailService" class="example.EmailService">
<property name="blackList">
<list>
<value>[email protected]value>
<value>[email protected]value>
<value>[email protected]value>
list>
property>
bean>
<bean id="blackListNotifier" class="example.BlackListNotifier">
<property name="notificationAddress" value="[email protected]"/>
bean>
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() {
// ...
}
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlackListEvent(BlackListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
Name
Location
Description
Example
Event
root object
The actual
ApplicationEvent
.#root.event
or event
Arguments array
root object
用于调用方法的参数(作为对象数组)。
#root.args
or args
; args[0]
to access the first argument, etc.
Argument name
evaluation context
任何方法参数的名称。 如果由于某种原因这些名称不可用(例如,由于在编译的字节码中没有调试信息),则也可以使用#a <#arg>语法(其中<#arg>代表 参数索引(从0开始)。
#blEvent
or #a0
(you can also use #p0
or #p<#arg>
parameter notation as an alias)@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
// ...
}
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()));
}
}
1.15.3 方便地访问低级资源
application contexts
的理解,您应该熟悉Spring的Resource抽象,如参考资料所述。1.15.4 Web应用程序的便捷ApplicationContext实例化
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xmlparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
1.15.5 将Spring ApplicationContext部署为Java EE RAR文件
1.16 Bean工厂
DefaultListableBeanFactory
实现是更高级别的GenericApplicationContext
容器中的关键委托1.16.1 BeanFactory或ApplicationContext?
Feature
BeanFactory
ApplicationContext
bean 实例化和装配
Yes
Yes
生命周期管理
No
Yes
自动BeanPostProcessor注册
No
Yes
自动
BeanFactoryPostProcessor
注册No
Yes
方便的MessageSource访问(用于内部化)
No
Yes
内置的ApplicationEvent发布机制
No
Yes
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions
// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());
// now start using the factory
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));
// bring in some property values from a Properties file
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
// now actually do the replacement
cfg.postProcessBeanFactory(factory);
2.Resources 资源
ResourceLoader
资源加载器ResourceLoaderAware
interface 资源感知接口2.1 介绍
2.2 资源接口
public interface Resource extends InputStreamSource {
boolean exists();
boolean isOpen();
URL getURL() throws IOException;
File getFile() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
2.3 内置资源实现
UrlResource
ClassPathResource
FileSystemResource
ServletContextResource
InputStreamResource
ByteArrayResource
2.3.1.
UrlResource
2.3.2
ClassPathResource
FileSystemResource
2.3.4
ServletContextResource
2.3.5
InputStreamResource
2.3.6
ByteArrayResource
2.4 ResourceLoader
public interface ResourceLoader {
Resource getResource(String location);
}
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");
Prefix
Example
Explanation
classpath:
classpath:com/myapp/config.xml
从类路径加载。
file:
file:///data/config.xml
从文件系统作为URL加载。 另请参见FileSystemResource警告。
http:
https://myserver/logo.png
URL加载
(none)
/data/config.xml
取决于底层的ApplicationContext。
2.5 ResourceLoaderAware接口
public interface ResourceLoaderAware {
void setResourceLoader(ResourceLoader resourceLoader);
}
2.6 资源依赖关系
<bean id="myBean" class="...">
<property name="template" value="some/resource/path/myTemplate.txt"/>
bean>
<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>
2.7 应用程序上下文和资源路径
2.7.1 构造应用程序上下文
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
ApplicationContext ctx =
new FileSystemXmlApplicationContext("conf/appContext.xml");
ApplicationContext ctx =
new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
com/
foo/
services.xml
daos.xml
MessengerService.class
ApplicationContext ctx = new ClassPathXmlApplicationContext(
new String[] {"services.xml", "daos.xml"}, MessengerService.class);
2.7.2 应用程序上下文构造函数资源路径中的通配符
/WEB-INF/*-context.xml
com/mycompany/**/applicationContext.xml
file:C:/some/path/*-context.xml
classpath:com/mycompany/**/applicationContext.xml
ApplicationContext ctx =
new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
getClass().getClassLoader().getResources("
。 尝试对具有相同名称但位于两个不同位置的文件进行此测试。 如果返回了不合适的结果,请检查应用程序服务器文档中可能会影响类加载器行为的设置。com/mycompany/package1/service-context.xml
classpath:com/mycompany/**/service-context.xml
2.7.3 FileSystemResource警告
ApplicationContext ctx =
new FileSystemXmlApplicationContext("conf/context.xml");
ApplicationContext ctx =
new FileSystemXmlApplicationContext("/conf/context.xml");
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("some/resource/path/myTemplate.txt");
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("/some/resource/path/myTemplate.txt");
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
ApplicationContext ctx =
new FileSystemXmlApplicationContext("file:///conf/context.xml");