通过了解初始化的时间,bean可以检查是否满足其所需的所有依赖项。
尽管Spring可以帮助我们检查依赖项,但它几乎是一种全有或全无的方法,并且不会提供任何机会来将其他逻辑应用于依赖项的解析过程中。
假设一个bean有四个被声明为setter的依赖项,其中两个是必需的,还有一个bean在没有提供依赖项的情况下提供合适的默认值。
通过使用初始化回调,bean可以检查它所需的依赖项,触发异常或根据需要提供默认值。
一个bean不能在其构造函数中执行这些检查,因为Spring无法为所需的依赖项提供值。
Spring的初始化回调函数在Spring完成提供依赖项之后调用,并执行你所要求的依赖项检查。
不限于使用初始化回调来检查依赖项;可以在回调中做任何想要做的事情,但是它对于我们描述的目的来说非常有用。
接收初始化回调的一种方法是在bean上指定一个方法作为初始化方法,并告诉Spring将此方法用作初始化方法。
指定回调方法只是在bean的标记的init-method属性中指定名称的一种情况。
/**
* MyBean 类
*/
@Bean(initMethod = "init")
public class MyBean {
/**
* 初始化方法
*/
public void init(){
System.out.println("MyBean created!");
}
}
初始化方法的唯一限制是不能接收任何参数。可以定义任何返回类型,虽然返回值会被Spring忽略,甚至可以使用静态方法,但也不能接收任何参数。
当使用静态初始化方法时,这种机制带来的好处就被否定了,因为无法访问任何bean的状态来加以验证。
如果bean正在使用静态状态作为节约内存的机制,同时正在使用静态初始化方法来验证状态,那么应考虑将静态状态移至实例状态并使用非静态初始化方法。
虽然也可以使用Spring的单例管理功能来实现相同的效果。
Spring中定义的InitializingBean接口允许在bean代码中定义希望bean接收的Spring已经完成配置的通知。
使用初始化方法的方式相同,通过实现InitializingBean接口可以创造机会来检查bean配置以确保它是有效的,并提供任何默认值。
InitializingBean接口定义了一个方法,即afterPropertiesSet(),它的作用与前一节介绍的init)方法的作用相同。
以下代码片段重新实现了前面的示例,但此时使用InitializingBean接口替换了初始化方法:
public class MyBean implements InitializingBean {
/**
* 在属性设置之后执行的操作
*
* @throws Exception 如果在设置属性时发生异常
*/
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("MyBean created!");
}
}
/**
* 我的Bean类
*/
public class MyBean {
/**
* 在PostConstruct注解的方法,在对象创建完成后自动执行
*
* @throws Exception 异常
*/
@PostConstruct
public void afterPropertiesSet() throws Exception {
System.out.println("MyBean created!");
}
}
该程序与使用init-method方法相同;只是在init()方法前应用了@PostConstruct注解。
所有初始化机制都可以在同一个bean实例上使用。
在这种情况下,Spring首先调用使用了@PostConstruct注解的方法,然后调用afterPropertiesSet(),最后调用配置文件中指定的初始化方法。
该顺序是由一个技术原因决定的,可以注意到在bean创建过程中主要完成以下步骤:
如果你有一个在特定方法中执行某些初始化操作的bean,同时在使用Spring时需要添加更多的初始化代码,那么理解不同类型bean的初始化顺序是非常有用的。
当使用封装了DefaultListableBeanFactory接口的ApplicationContext实现,可以通过调用ConfigurableBeanFactory.destroySingletons()向BeanFactory发出信号,告知销毁所有单例实例。
在应用程序关闭时执行此操作,并允许清理bean可能保持打开的任何资源,从而使应用程序可以正常关闭。此外,在该回调中还可以将存储在内存中的任何数据刷新到持久存储库中,并允许bean关闭可能已启动的长时间运行的任何进程。
为了让bean接收到destroySingletons()被调用的通知,存在三种选择,这些选择类似于用来接收初始化回调的机制。销毁回调通常与初始化回调一起使用。在许多情况下,在初始化回调中创建并配置资源,然后在销毁回调中释放资源。
如果想要指定一个在bean被销毁时调用的方法,只需要在bean的标记的destroy-method属性中指定该方法的名称即可。
Spring在销毁bean的单例实例之前会调用该方法(对于那些有原型作用域的bean,Spring不会调用此方法)。
/**
* 自定义Bean类,用于在应用程序停止时被销毁
*/
@Bean(destroyMethod = "destroy")
public class MyBean {
/**
* 销毁方法,用于在应用程序停止时执行清理操作
*/
public void destroy(){
System.out.println("MyBean destroy!");
}
}
这里通过使用destroymethod属性指定destroy()方法作为销毁回调。
与初始化回调一样,Spring也提供了一个接口(即DisposableBean),你的bean可以实现该接口,作为接收销毁回调的机制。DisposableBean接口定义了一个方法destroy(),该方法在bean被销毁之前被调用。
public class MyBean implements DisposableBean {
/**
* 销毁Bean方法,执行与销毁相关的操作。
*/
@Override
public void destroy(){
System.out.println("MyBean destroy!");
}
}
义在bean销毁之前所调用方法的第三种方式是使用JSR-250生命周期注解@PreDestroy,它与@PostConstruct注解相反。
以下代码片段是DestructiveBean的一个版本,它在同一个类中同时使用@PostConstruct和@PreDestroy来执行程序的初始化和销毁操作。
public class MyBean implements DisposableBean {
/**
* 在Bean被销毁前执行的回调方法。
*/
@PreDestroy
public void destroy(){
System.out.println("MyBean destroy!");
}
}
创建bean的情况一样,可以在同一bean实例上使用所有机制来进行bean销毁。这种情况下,Spring首先调用用@PreDestroy注解的方法,然后调用DisposableBean.destroy),最后调用XML定义中配置的destroy)方法。
在Spring中销毁回调函数的唯一缺点是它们不会自动触发;需要记住在应用程序关闭之前调用AbstractApplicationContext.destroy()。
当应用程序作为servlet运行时,可以简单地在servlet的destroy()方法中调用destroy()。
但是,在独立的应用程序中,事情并不那么简单,尤其是在应用程序中存在多个退出点时。
幸运的是,有一个可行的解决方案。Java允许创建一个关闭钩子(shutdown hook),它是在应用程序关闭之前执行的一个线程。
这是调用AbstractApplicationContext所有具体的ApplicationContext实现都扩展了AbstractApplicationContext的destroy()方法的一种理想方式。
利用此机制的最简单方法是使用AbstractApplicationContext的registerShutdownHook()方法。该方法自动指示Spring注册底层JVM运行时的关闭钩子。bean的声明和配置和之前一样;唯一改变的是main()方法:添加对ctx.registerShutdownHook的调用,同时删除对ctx.destroy()或close()的调用。
相对于依赖查找,作为实现控制反转机制的依赖注入的最大亮点之一是,bean不需要知道正在管理它们的容器是如何实现的。
对于使用构造函数注入或setter注入的bean而言,Spring容器与Google Guice或PicoContainer所提供的容器是相同的。
但是,在某些情况下,可能需要一个使用依赖注入来获取其依赖项的bean,以便出于某种其他原因而与容器进行交互。
比如一个用来自动配置关闭钩子的bean,它需要访问ApplicationContext。
在其他情况下,bean可能想知道它的名称是什么(即在当前ApplicationContext中分配的bean名称),以便可以根据名称进行一些额外的处理。
也就是说,此功能真正用于Spring内部使用。为bean名称提供某种业务含义通常是一个糟糕的主意,并且可能导致配置问题,因为必须人为地操纵bean名称以支持其业务含义。
但是,我们发现能够让bean在运行时找到它的名称,对于日志记录来说是非常有用的。
假设有许多在不同配置下运行的相同类型的bean。
此时,在日志消息中可以包含bean名称,以便当出现错误时帮助区分哪些bean发生了错误,而哪些bean正常工作。
想要获取自己名称的bean可以实现BeanNameAware接口,它有一个方法setBeanName(String)。
在完成bean的配置之后且在调用任何生命周期回调(初始化回调或销毁回调)之前,Spring会调用setBeanName()方法。
大多数情况下,setBeanName()的实现仅仅是一行代码,它将容器传入的值存储在字段中供以后使用。
public class MyBean implements BeanNameAware {
// 每一个实例的名称
private String name;
@Override
public void setBeanName(String name) {
// 将给定的名称赋值给实例的名称
this.name = name;
}
}
通过使用ApplicationContextAware接口,bean可以获得对配置它们的ApplicationContext实例的引用。
创建此接口的主要原因,是为了允许bean在应用程序中访问Spring的ApplicationContext,例如,使用getBean()以编程方式获取其他Spring bean。
但是,应该避免这种做法,并使用依赖注入为bean提供协作者。如果在可以使用依赖注入时使用了基于查找的getBean()方法来获得依赖项,那么将会为bean添加不必要的复杂性。
ApplicationContext并不仅仅用于查找bean,它可以执行许多其他任务。
正如你在前面看到的,其中一项任务是销毁所有单例,但在销毁之前会依次通知每个单例。
在前面已经介绍了如何创建一个关闭钩子来确保在应用程序关闭之前指示ApplicationContext销毁所有单例。
可以使用ApplicationContextAware接口创建一个bean,该bean可以在ApplicationContext中配置,并自动创建和配置关闭钩子bean。
/**
* MyBean 类是一个实现了 ApplicationContextAware 接口的 Java 类。
*/
public class MyBean implements ApplicationContextAware {
/**
* setApplicationContext 方法用于设置 ApplicationContext。
*
* @param applicationContext 应用程序上下文
* @throws BeansException 如果设置应用程序上下文时发生错误,则抛出 BeansException 异常
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
}
}
在使用Spring时会面临一个问题:如何创建并注入不能简单地使用new运算符创建的依赖项。
为了解决这个问题,Spring提供了FactoryBean接口,该接口充当不能使用标准Spring语义创建和管理的对象的适配器。通常,使用FactoryBean创建不能通过使用new运算符创建的bean,例如通过静态工厂方法访问的bean,尽管情况并非总是如此。
简而言之,FactoryBean是一个bean,可以作为其他bean的工厂。
FactoryBean像任何普通bean一样在ApplicationContext中配置,但是当Spring使用FactoryBean接口来满足依赖或查找请求时,它并不返回FactoryBean。相反,它调用FactoryBean.getObject()方法并返回调用的结果。
FactoryBean在Spring中的使用效果很好;最明显的用途是创建事务代理,以及从JNDI上下文中自动获取资源。
但是,FactoryBean不仅仅用于构建Spring的内部组件,当构建自己的应用程序时,你会发现它们也非常有用,因为它们允许通过使用IoC来管理更多的资源。
一般来说,所开发的项目需要进行某种密码处理;通常包括生成存储在数据库中的消息摘要或者用户密码的哈希值。在Java中,MessageDigest类提供了创建任意数据摘要的功能。
MessageDigest本身是抽象的,通过调用MessageDigest.getlnstance()并传入想要使用的摘要算法的名称来获得具体的实现。
例如,如果想使用MD5算法创建一个摘要,可以使用下面的代码来创建MessageDigest实例:
MessageDigest md5 =MessageDigest.getInstance(“MD5”);
如果想用Spring来管理MessageDigest对象的创建,那么在没有FactoryBean的情况下可以采用的最好方法是使用bean上的属性algorithmName,然后使用初始化回调来调用MesageDigestgetlnstance();而如果使用FactoryBean,则可以将相关逻辑封装在一个bean中。
然后需要MessageDigest实例的任何bean都可以简单地声明属性messageDigest,并使用FactoryBean来获取实例。下面的代码片段中的FactoryBean实现了上述功能:
/**
* 消息摘要工厂类
*/
public class MessageDigestFactoryBean implements FactoryBean<MessageDigest>,InitializingBean {
private MessageDigest messageDigest;
@Override
public MessageDigest getObject() throws Exception {
return messageDigest;
}
@Override
public Class<MessageDigest> getObjectType() {
return MessageDigest.class;
}
@Override
public void afterPropertiesSet() throws Exception {
messageDigest=MessageDigest.getInstance("md5");
}
}
pring调用getObject()方法来检索由FactoryBean创建的对象。该对象将被传递给使用FactoryBean作为协作者的其他bean。
在上述代码片段中,可以看到MesageDigestFactoryBean传递了一个在InitializingBean.afterPropertiesSet()回调中创建的用于存储MessageDigest实例的副本。
getObjectType()方法允许告诉Spring FactoryBean所返回对象的类型。
如果事先不知道返回类型(例如,FactoryBean根据配置创建不同类型的对象,具体的类型只有在FactoryBean初始化后才能确定),那么对象类型可以为null,但如果指定了类型,那么 Spring可以使用该类型实现自动装配。
在这个示例中,将MessageDigest作为类型返回(此时返回的是一个类,但也可以尝试返回一个接口并让FactoryBean实例化具体的实现类,不过只有在必要的情况下才会这么做),因为我们不知道将返回什么样的具体类型(但这并不重要,因为所有的bean都会使用MessageDigest来定义它们的依赖项)。
通过使用isSingleton()属性可以告知Spring FactoryBean是否正在管理一个单例实例。
请记住,通过设置FactoryBean的标记的singleton属性,可以告诉Spring FactoryBean的单例状态,而不是所返回的对象。接下来看看在应用程序中如何使用FactoryBean。
@Bean
public MessageDigestFactoryBean shaDigest(){
MessageDigestFactoryBean factoryone = new MessageDigestFactoryBean();
return factoryone;
}
假设Spring可以自动满足由FactoryBean生成的对象对FactoryBean的引用,那么你可能会问是否可以直接访问FactoryBean。答案是肯定的。访问FactoryBean很简单,在调用getBean()时用“&”符号作为bean名称的前缀即可。
如果还不完全熟悉JavaBean概念,可以看看PropertyEditor,它是一个接口,它将属性值从其本机类型表示形式转换为字符串。
最初,该接口的设计目的是允许将属性值作为字符串值输入到编辑器中,并将它们转换为正确的类型。但是,由于PropertyEditor本身就是轻量级的类,因此在许多设置中都可以找到它们,包括Spring。
因为基于Spring的应用程序中,很大一部分属性值都在BeanFactory配置文件中开始生命周期,所以它们基本上都是Strings。但是,用这些值设置的属性可能并不是字符串类型的。因此,为了避免人为创建String类型的属性,Spring允许定义PropertyEditor以实现基于字符串的属性值到正确的类型的转换。
下图显示了属于spring-beans包的PropertyEditor的完整列表;可以使用任何智能Java编辑器查看此列表。
它们都扩展了java.beans.PropertyEditorSupport,并且可用来将字符串文字隐式转换为要注入bean中的属性值;因此,这里使用BeanFactory预先注册了它们。
虽然内置的PropertyEditor涵盖了属性类型转换的一些标准情况,但有时可能需要创建自己的PropertyEditor来支持应用程序中所使用的一个类或一组类。
Spring完全支持注册自定义PropertyEditor;唯一的缺点是java.beans.PropertyEditor接口有很多方法,其中很多方法与当前的任务(即当前转换属性类型的任务)无关。
值得庆幸的是,JDK5或更新的版本提供了其PropertyEditor可以扩展的PropertyEditorSupport类,该类只有一个方法setAsText)。接下来用一个简单的示例演示如何实现一个自定义属性编辑器。
假设有一个FullName类,它只有两个属性firstName和lastName,定义如下所示:
为了简化应用程序配置,开发一个自定义编辑器,将带有空格分隔符的字符串分别转换为FullName类的名字和姓氏。以下代码片段描述了自定义属性编辑器的实现:
该编辑器很简单。它扩展了JDK的PropertyEditorSupport类并实现了setAsText)方法。
在该方法中,简单地将String分隔成一个以空格作为分隔符的字符串数组。
之后,实例化FullName类的一个实例,将空格字符之前的字符串作为第一个名称并将空格字符之后的字符串作为姓氏传递。
最后,通过调用带结果的setValue()方法返回转换后的值。
如果想要在应用程序中使用NamePropertyEditor,需要在Spring的ApplicationContext中注册该编辑器。
以下配置示例显示了CustomEditorConfigurer和NamePropertyEditor的ApplicationContext配置。
BeanFactory中不存在的ApplicationContext的另一个功能是通过使用ApplicationContext作为代理发布和接收事件的能力。
使用应用程序事件,事件是派生自ApplicationEvent的类,而ApplicationEvent类又派生自java.util.EventObject。
任何bean都可以通过实现ApplicationListener接口来监听事件;当配置时,ApplicationContext会自动注册实现此接口的任何bean作为监听器。
事件是通过使用ApplicationEventPublisher.publishEvent()方法发布的,所以发布类必须可以访问ApplicationContext(它扩展了ApplicationEventPublisher接口)
想要设置活动配置文件,需要访问Environment接口。
Environment接口是一个抽象层,用于封装正在运行的Spring应用程序的环境,除配置文件外,Environment接口封装的其他关键信息都是属性。
属性用来存储应用程序的底层环境配置,例如应用程序文件夹的位置、数据库连接信息等。
Spring中的Environment和PropertySource抽象功能帮助开发人员访问来自运行平台的各种配置信息。
在抽象环境中,所有系统属性、环境变量和应用程序属性都由Environment接口提供,Spring启动ApplicationContext时将填充该接口。
而对于PropertySource抽象,Spring将按照以下默认顺序访问属性:
在现实生活中,很少需要直接与Environment接口进行交互,但会以${}的形式使用一个属性占位符,例如${application.home}),并将解析后的值注入Spring bean中。接下来看一下该过程是如何完成的。假设有一个类用来存储从属性文件加载的所有应用程序属性。以下所示的是AppProperty类: