IoC(Inversion of Control)也被称之为 DI(dependency injection),名称侧重点略有不同。
所谓控制翻转即对象通过构造函数参数、工厂方法参数或者属性字段设置来定义依赖,然后容器在创建 bean 的时候注入依赖。这个过程和对象自己管理依赖是完全相反的。
org.springframework.beans和org.springframework.context是 Spring 框架 IoC 容器的基础包。
(相关资料图)
BeanFactory接口提供了丰富的配置机制来管理各种类型的对象。
ApplicationContext是BeanFactory的子接口,在其原有基础上添加了如下特性:
和 Spring AOP 集成更容易。
消息处理(国际化应用)
事件分发
特有的应用层 contexts,例如 web 应用中的WebApplicationContext。
BeanFactory 提供了配置框架和基本的功能,ApplicationContext在此基础上添加了更多企业应用特性,是BeanFactory的超集。
Spring 有很多对象,其中 IoC 容器管理的实例化的对象称之为 bean。
二、容器总览org.springframework.context.ApplicationContext接口相当于 Spring IoC 容器,负责实例化,配置及组装 bean 对象。
配置元数据可以是 XML、Java 注解或者 Java 代码。
如下图,概括的展示了 Spring 的作用过程:
配置元数据用于描述 bean 及其依赖的关系,IoC 容器基于此来实例化,配置及组装 bean 对象。配置元数据和IoC 容器是相互独立,彼此不耦合的,它可以有多种形式,包括基于 XML配置、基于注解配置及基于Java配置等。
三、Bean 总览容器内部 bean 通过BeanDefinition 定义。
BeanDefinition 包含如下元数据:全限定类名(包含包名)、Bean 行为特性(作用域、生命周期回调等)、依赖描述及其它设置。
这些元数据可以通过如下一系列属性来描述:类名、名称、作用域、构造函数参数、属性、Autowire、懒加载、初始化方法、销毁方法。
四、Bean 作用域a)singleton默认,每个 IoC 容器一个 Bean 实例。应用于无状态 Bean 场景。
附:对于 singleton 类型 bean 依赖 prototype 类型 bean 的场景,因为容器实例化对象时只会处理一次依赖,所以 singleton 实例依赖的 prototype 对象只是其一。
b)prototype每次需要 Bean 对象时即创建新的实例。应用于有状态 Bean 场景。
附:对于 prototype 类型 bean,Spring 并没有管理其完整地生命周期,容器只负责实例化、配置及组装依赖。配置的销毁,生命周期回调并不会被调用。
可以通过自定义bean post-processor来处理。
c)requestSpring web 应用,对应每次 HTTP 请求生命周期,不同请求之间是隔离的。
d)sessionSpring web 应用,对应每次 HTTP Session 生命周期,不同 Session 之间是隔离的。
e)applicationSpring web 应用,对应ServletContext 生命周期,不同 ServletContext 之间是隔离的。
f)websocketSpring web 应用,对应WebSocket Session 生命周期。
五、自定义 Bean 特性1、生命周期回调可以通过实现SpringInitializingBean和DisposableBean接口来和容器 bean 生命周期管理过程进行交互。
bean 初始化:InitializingBean() ->afterPropertiesSet() 调用。bean 销毁:DisposableBean() -> destroy() 调用。同注解应用:@PostConstruct、@PreDestroy
我们也可以通过实现 BeanPostProcessor 来处理任何回调接口。
除了初始化和销毁回调,Spring 管理的对象也可以通过实现Lifecycle接口来参与启动及关闭过程回调。
a)Initialization Callbacksorg.springframework.beans.factory.InitializingBean接口可以让 bean 对象在容器设置完所有必要的属性后执行初始化操作。他只有一个方法:
void afterPropertiesSet() throws Exception;
通常我们不建议使用此接口,因为它会使得我们的代码和 Spring 代码产生耦合。推荐使用@PostConstruct注解或者声明一个 POLO 初始化方法。
初始化方法声明如下:
// 或者@Bean(initMethod = "")
如果存在多种声明机制,则执行顺序为:@PostConstruct -> InitializingBean.afterPropertiesSet() -> 自定义初始化方法
b)Destruction Callbacksorg.springframework.beans.factory.DisposableBean接口可以使得 bean 在容器销毁时收到一个回调,它也只有一个方法:
void destroy() throws Exception;
同样不建议使用,原因如InitializingBean,推荐使用@PreDestroy 注解或者定义销毁方法。如下:
或者@Bean(destroyMethod = "")
如果存在多种声明机制,则执行顺序为:@PreDestroy -> DisposableBean.destroy() -> 自定义销毁方法
c)Startup and Shutdown CallbacksLifecycle接口定义如下:
public interface Lifecycle { void start(); void stop(); boolean isRunning();}
ApplicationContext 容器在接收到启动或者关闭信号之后,会顺序的把这一消息传递给所有容器内 Spring 管理的实现了Lifecycle接口的对象。
这一过程会委托代理给 LifecycleProcessor进行处理,其定义如下:
public interface LifecycleProcessor extends Lifecycle { void onRefresh(); void onClose();}
LifecycleProcessor是对Lifecycle接口的拓展,引入了处理 context 刷新及关闭的两个方法。
对于存在依赖关系的不同对象,启动及关闭的相应调用顺序就要遵循一定的规则。
如果是直接依赖:依赖方要先于被依赖方启动,并后于被依赖方关闭。
对于非直接依赖关系,如只知道一类类型的对象需要依赖另一类类型的对象,以上的接口将无法满足使用。因此这里需要引入另外一个接口SmartLifecycle,它的定义如下:
public interface Phased { int getPhase();}public interface SmartLifecycle extends Lifecycle, Phased { boolean isAutoStartup(); void stop(Runnable callback);}
对于实现了 SmartLifecycle 接口的对象,启动时,phase 小的先启动,关闭时,phase 大的先关闭。
对于未实现 SmartLifecycle 接口的对象,我们可以认定它的 phase 为 0,如此,phase 小于 0 的对象则将先于其启动,后于其关闭。
SmartLifecycle 接口 stop() 方法会接收一个回调,所有实现此接口的对象都需要在其关闭过程执行完毕后调用一次 run() 方法。
LifecycleProcessor 接口的默认实现 DefaultLifecycleProcessor, 会等待所有对象执行完回调(可以通过 timeoutPerShutdownPhase 设置超时),藉由此机制,我们可以在需要的时候的时候实现应用异步关闭逻辑。
d)非 web 应用 IoC 容器的优雅关闭注册 JVM shutdown hook:ConfigurableApplicationContext.registerShutdownHook(),实例如下:
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... }}2、ApplicationContextAware和BeanNameAwarea)ApplicationContextAware
对象可以通过实现org.springframework.context.ApplicationContextAware接口来获取 ApplicationContext 资源。接口定义如下:
public interface ApplicationContextAware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException;}
藉由此,ApplicationContext 创建的 bean 也可以反过来对它执行特定的操作。例如,对其它 bean 的访问。
这种机制虽然在某些场景会很有用,但是它引发了代码的耦合,破坏了 IoC 机制,因此并不推荐。
另外一种获取ApplicationContext资源的方法是通过自动装配的方式引入ApplicationContext 对象依赖,如构造函数或者 setter。 推荐使用@Autowired 注解,更加灵活方便。
b)BeanNameAware接口定义如下:
public interface BeanNameAware { void setBeanName(String name) throws BeansException;}
其方法调用时机为:bean 属性组装完毕,初始化方法(InitializingBean.afterPropertiesSet() 或自定义初始化方法)调用之前。
3、其它 Aware 资源接口ApplicationEventPublisherAware、BeanClassLoaderAware、BeanFactoryAware、LoadTimeWeaverAware、MessageSourceAware、NotificationPublisherAware、ResourceLoaderAware、ServletConfigAware、ServletContextAware。
六、容器扩展1、BeanPostProcessor 自定义 bean 特性BeanPostProcessor接口通过提供回调方法来实现用户自定义逻辑。可以根据需要配置多个不同的实现,调用顺序通过Ordered设置。
org.springframework.beans.factory.config.BeanPostProcessor接口包含两个回调方法:
package org.springframework.beans.factory.config;import org.springframework.beans.BeansException;import org.springframework.lang.Nullable;public interface BeanPostProcessor { @Nullable default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Nullable default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; }}
分别对应初始化方法(InitializingBean.afterPropertiesSet() 或自定义初始化方法)前后需要执行的逻辑。
ApplicationContext能够自动检测实现了 BeanPostProcessor 接口的 bean,并把它们注册为前置处理器,以便之后在其它 bean 创建的时候调用。
需要注意的是:如果是使用 @Bean 注解工厂方法。那么返回的结果需要是实现了BeanPostProcessor 接口的类对象类型。否则ApplicationContext在创建它前无法自动检测其类型。
附:编程方式注册BeanPostProcessor实例(不推荐):
可以通过addBeanPostProcessor方法向ConfigurableBeanFactory注册。通过此方式注册实例 Ordered接口作用将失效,会按照注册的顺序执行,并且优先于所有自动检测注册的前置处理器。
应用实例:AutowiredAnnotationBeanPostProcessor
2、BeanFactoryPostProcessor 自定义 bean 定义BeanFactoryPostProcessor用于操作 bean 配置元数据。
可以配置多个BeanFactoryPostProcessor实例并通过实现 Ordered 接口设置调用顺序。
应用:PropertySourcesPlaceholderConfigurer、PropertyOverrideConfigurer
七、基于注解的容器配置基于注解的注入先于 XML 配置。同样的注入会产生覆盖。1、@Autowired 自动装配用在哪里注入装配哪里。byType。
a)构造器:public class MovieRecommender { private final CustomerPreferenceDao customerPreferenceDao; @Autowired public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) { this.customerPreferenceDao = customerPreferenceDao; } // ...}
对象只有多个构造器的时候,用以标明哪个构造器供容器使用。
b)setter 方法:public class SimpleMovieLister { private MovieFinder movieFinder; @Autowired public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ...}c)任意方法,任意参数:
public class MovieRecommender { private MovieCatalog movieCatalog; private CustomerPreferenceDao customerPreferenceDao; @Autowired public void prepare(MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) { this.movieCatalog = movieCatalog; this.customerPreferenceDao = customerPreferenceDao; } // ...}d)属性字段:
public class MovieRecommender { private final CustomerPreferenceDao customerPreferenceDao; @Autowired private MovieCatalog movieCatalog; @Autowired public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) { this.customerPreferenceDao = customerPreferenceDao; } // ...}e)获取容器内特定类型对象集合
默认需要至少有一个特定类型对象,否则会发生装配失败。
数据和列表
public class MovieRecommender { @Autowired private MovieCatalog[] movieCatalogs; // ...}或public class MovieRecommender { private SetmovieCatalogs; @Autowired public void setMovieCatalogs(Set movieCatalogs) { this.movieCatalogs = movieCatalogs; } // ...}
如果需要列表里的元素排序,则可以对收集的对象应用 Ordered 接口,或者添加@Order 注解。
Map 类型收集:key 为 bean 名称,value 为 bean 对象。
public class MovieRecommender { private Mapf)默认装配行为movieCatalogs; @Autowired public void setMovieCatalogs(Map movieCatalogs) { this.movieCatalogs = movieCatalogs; } // ...}
对于方法及属性字段的注解默认行为为必须。可以通过配置变更:
public class SimpleMovieLister { private MovieFinder movieFinder; @Autowired(required = false) public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ...}也可以通过java.util.Optional(>= Java 8) 或者@Nullable(Spring Framework 5.0)来标示可选。
public class SimpleMovieLister { @Autowired public void setMovieFinder(Optionalg)装配使用特定已知框架资源movieFinder) { ... }}或者public class SimpleMovieLister { @Autowired public void setMovieFinder(@Nullable MovieFinder movieFinder) { ... }}
例如 BeanFactory、ApplicationContext、Environment、ResourceLoader、ApplicationEventPublisher 及 MessageSource 等。
如上接口的扩展接口,如ConfigurableApplicationContext、ResourcePatternResolver 等。
如下:
public class MovieRecommender { @Autowired private ApplicationContext context; public MovieRecommender() { } // ...}h)附注
@Autowired、@Inject、@Value 及@Resource注解都是通过BeanPostProcessor接口实现处理的。因此不能应用于自定义的BeanPostProcessor或者BeanFactoryPostProcessor类型实现。
2、@Primary当存在多个同类型装配对象时,可以通过@Primary来标示使用哪个对象。如下:
@Configurationpublic class MovieConfiguration { @Bean @Primary public MovieCatalog firstMovieCatalog() { ... } @Bean public MovieCatalog secondMovieCatalog() { ... } // ...}。。。public class MovieRecommender { @Autowired private MovieCatalog movieCatalog; //firstMovieCatalog
// ... }3、Qualifiers
@Qualifier 缩小符合装配的对象范围。
public class MovieRecommender { @Autowired @Qualifier("main") private MovieCatalog movieCatalog; // ...}或public class MovieRecommender { private final MovieCatalog movieCatalog; private final CustomerPreferenceDao customerPreferenceDao; @Autowired public void prepare(@Qualifier("main") MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) { this.movieCatalog = movieCatalog; this.customerPreferenceDao = customerPreferenceDao; } // ...}
符合Qualifier 值得对象可以不唯一。
4、@ResourceJSR-250 注解。byName。
注解 bean 属性字段或者 setter 方法。
public class SimpleMovieLister { private MovieFinder movieFinder; @Resource(name="myMovieFinder") public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; }}
如果不指定名称,丢与属性字段则使用字段名,对于 setter 方法,则使用 bean 属性名。
首先通过名称查找,如果找不到则通过类型查找。
5、@Value用于注入外部配置。
配置文件:application.properties
catalog.name=MovieCatalog
配置:
@Configuration@PropertySource("classpath:application.properties")public class AppConfig { }
使用:
@Componentpublic class MovieRecommender { private final String catalog; public MovieRecommender(@Value("${catalog.name}") String catalog) { this.catalog = catalog; }}八、基于 Java 代码的容器配置1、核心概念
基于 @Configuration注解的类 和 基于@Bean注解的方法。
@Bean注解作用于方法,用于实例化,配置及初始化一个新对象,并且新对象将由 Spring 容器管理。其和基于 XML 配置的
@Bean通常和@Configuration结合使用(也可以用在任何Spring@Component注解管理的对象内)。
@Configuration注解的类主要用作 bean 定义。包括相关依赖 bean 的定义。简单实例如下:
@Configurationpublic class AppConfig { @Bean public MyServiceImpl myService() { return new MyServiceImpl(); }}
等同于:
2、AnnotationConfigApplicationContext
ApplicationContext的一种实现,Spring 3.0 引入。可以同时处理 @Configuration、@Component及 JSR-330 注解的类。
@Configuration 注解的类:包括其内所有@Bean注解的方法都被注册为 bean 定义。
对于@Component及 JSR-330 注解的类:除了类本身会被注册为 bean 定义,并且会处理其内 @Autowired或者@Inject注解相关的注入逻辑。
a)如下基于构造器示例: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(AppConfig.class); MyService myService = ctx.getBean(MyService.class); myService.doStuff();}b)如下基于编码注册处理示例:
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();}c)基于自动扫描处理示例:
@Configuration@ComponentScan(basePackages = "com.acme") (1)public class AppConfig { // ...}// 或
AnnotationConfigApplicationContext 暴露了相应的 scan 接口,用于编码方式执行扫描操作:
public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.scan("com.acme"); ctx.refresh(); MyService myService = ctx.getBean(MyService.class);}d、AnnotationConfigWebApplicationContext
Spring web 应用。包括注册 SpringContextLoaderListenerservlet listener、Spring MVCDispatcherServlet 等等。
3、@Bean 注解a)bean 定义注解方法,用于 bean 定义注册。方法名称默认为 bean 名称。返回值类型为 bean 对象类型(具体实现类或者对应接口类型)。
示例见 1、基础概念处。
也可以通过接口默认方法定义:
public interface BaseConfig { @Bean default TransferServiceImpl transferService() { return new TransferServiceImpl(); }}@Configurationpublic class AppConfig implements BaseConfig {}b)bean 依赖
bean 定义方法可以有任意多个参数,通过如下方式定义依赖:
@Configurationpublic class AppConfig { @Bean public TransferService transferService(AccountRepository accountRepository) { return new TransferServiceImpl(accountRepository); }}c)生命周期回调
public class BeanOne { public void init() { // initialization logic }}public class BeanTwo { public void cleanup() { // destruction logic }}@Configurationpublic class AppConfig { @Bean(initMethod = "init") public BeanOne beanOne() { return new BeanOne(); } @Bean(destroyMethod = "cleanup") public BeanTwo beanTwo() { return new BeanTwo(); }}c)bean 作用域定义
@Configurationpublic class MyConfiguration { @Bean @Scope("prototype") public Encryptor encryptor() { // ... }}
scope-procy:
@Configurationpublic class MyConfiguration { @Bean @Scope(proxyMode = ScopedProxyMode.INTERFACES) public Encryptor encryptor() { // ... }}d)自定义 bean 名称
定义单个名称或者多个:
@Configurationpublic class AppConfig { @Bean("myThing") public Thing thing() { return new Thing(); }}//@Configurationpublic class AppConfig { @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"}) public DataSource dataSource() { // instantiate, configure and return DataSource bean... }}4、@Configuration 注解
如下:通过方法调用注入依赖
@Configurationpublic 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(); }}
默认单例模式管理的 bean 定义。如上,两次 clientDao() 方法调用并不会产生两个 ClientDao 对象。在对象实例化时会首先检查容器相应 bean 实例对象缓存,然后再决定是否需要调用相应的实例化方法。
查找方法注入:
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();}//@Bean@Scope("prototype")public AsyncCommand asyncCommand() { AsyncCommand command = new AsyncCommand(); // inject dependencies here as required return command;}@Beanpublic CommandManager commandManager() { // return new anonymous implementation of CommandManager with createCommand() // overridden to return a new prototype Command object return new CommandManager() { protected Command createCommand() { return asyncCommand(); } }}5、Import 注解
用于引入配置类,如下:
@Configurationpublic class ConfigA { @Bean public A a() { return new A(); }}//@Configuration@Import(ConfigA.class)public class ConfigB { @Bean public B b() { return new B(); }}
如上,容器实例化时,只需要处理 ConfigB 即可:
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class); // now both beans A and B will be available... A a = ctx.getBean(A.class); B b = ctx.getBean(B.class);}
Spring Framework 4.2 开始,除了 @Configuration 注解对象,@Import 可以同时处理其它类型对象。
当需要精细处理对象依赖引入时,可以使用此注解,避免大包扫描。
九、环境抽象Environment接口为容器环境资源抽象,主要包括:profiles和properties两方面。
profile:一种命名的逻辑组。可以设置 bean 定义率属于哪个特定 profile,从而在相应 profile 被激活时进行注册。profile 可以设置当前活跃及默认活跃。
Properties:资源服务接口,提供获取及配置相应资源能力。 源包括:properties 文件、JVM system properties、system environment variables、JNDI、servlet context parameters、ad-hocPropertiesobjects、Mapobjects等。
1、Bean Definition Profiles提供机制实现容器不同环境注册不同的 bean。藉由此,我们可以实现一些类似如下场景功能:
测试环境使用基于内存的数据源,QA 及 生产环境使用 JNDI 数据源。
只在线上环境启动监控功能。
针对不同用户注册不同的功能 bean 对象。
@Profile 注解
如下:JndiDataConfig 配置类只在 profile 为 production 时进行容器注册,使用
@Configuration@Profile("production")public class JndiDataConfig { @Bean(destroyMethod = "") (1) public DataSource dataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); }}
注解 value 可以使单一 profile 值,也可以是多个值得数组,亦或者为组合表达式。
@Profile("production")@Profile({"QA", "production"})@Profile("QA&production")
自定义 profile 环境注解:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Profile("production")public @interface Production {}//等价于@Profile("production")
@Profile 作为顶层环境配置,控制所有组合使用的注解资源,如@Configuration、@Import等。
profile 激活
直接编码方式设置:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();ctx.getEnvironment().setActiveProfiles("development");ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);ctx.refresh();
或环境变量属性配置:
-Dspring.profiles.active="profile1,profile2"2、PropertySource 抽象
SpringEnvironment抽象提供属性查询操作(基于可配置的,层级的属性源),如下:
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);
PropertySource:k-v 组配置资源抽象。
StandardEnvironment基于两类属性源配置:JVM 系统属性(System.getProperties())和系统环境变量(System.getenv())。
StandardServletEnvironment除了上述两项配置外,还包括 servlet config、servlet context parameters 及JndiPropertySource(如果存在 JNDI 资源需求)。
搜索操作是层级执行的,默认情况下,系统属性优先于环境变量,如果同一个属性在两个地方都有设置,则系统属性优先返回。其它的都会被忽略。
StandardServletEnvironment的层次结构如下:
ServletConfig parameters (if applicable — for example, in case of aDispatcherServletcontext)
ServletContext parameters (web.xml context-param entries)
JNDI environment variables (java:comp/env/entries)
JVM system properties (-Dcommand-line arguments)
JVM system environment (operating system environment variables)
我们也可以添加自定义的 PropertySource 并将其添加当前环境 PropertySource 组,如下:
ConfigurableApplicationContext ctx = new GenericApplicationContext();MutablePropertySources sources = ctx.getEnvironment().getPropertySources();sources.addFirst(new MyPropertySource());
通过MutablePropertySources 暴露的方法 addFirst(),将自定义的 MyPropertySource 添加到资源最上层位置,优先供给查询。
@PropertySource 使用
app.properties 文件:
testbean.name=myTestBean
注解引入:
@Configuration@PropertySource("classpath:/com/myco/app.properties")public class AppConfig { @Autowired Environment env; @Bean public TestBean testBean() { TestBean testBean = new TestBean(); testBean.setName(env.getProperty("testbean.name")); return testBean; }}
@PropertySource位置${…}占位符会使用环境内其它已注册的 PropertySource 资源处理。如下:
@Configuration@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")public class AppConfig { @Autowired Environment env; @Bean public TestBean testBean() { TestBean testBean = new TestBean(); testBean.setName(env.getProperty("testbean.name")); return testBean; }}
先查询已注册 PropertySource 资源,查询不到则使用默认 "default/path",如果不存则抛出异常IllegalArgumentException。
十、ApplicationContext 扩展功能1、MessageSource 国际化(i18n)ApplicationContext 通过实现 MessageSource 接口来提供国际化(i18n)功能。
Spring 同时也提供了HierarchicalMessageSource接口,用以逐层获取特定消息。
标准方法:
String getMessage(String code, Object[] args, String default, Locale loc)
ApplicationContext加载时会自动查找容器内定义的MessageSourcebean,且 bean 的名称必须为messageSource。
当执行消息查找获取操作时,Spring 会将操作代理给命名为 messageSource 的 bean。如果不存在此 bean,则从父类中查找,如果找不到则实例化一个空的DelegatingMessageSource用以执行相应的方法操作。
Spring 提供了三种MessageSource接口实现:ResourceBundleMessageSource、ReloadableResourceBundleMessageSource及StaticMessageSource。它们也都实现了HierarchicalMessageSource接口用以执行嵌套消息查询。StaticMessageSource很少使用,主要用于提供编码方式添加消息。
ResourceBundleMessageSource 使用如下:
定义:
@Beanpublic ResourceBundleMessageSource resourceBundleMessageSource() { ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource(); resourceBundleMessageSource.setBasenames("format", "exceptions", "windows") return resourceBundleMessageSource;}// 或
format exceptions windows
format.properties:
message=Alligators rock!
exception.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);}
对于不同的 Locale,则定义相应的不同资源文件:
Locale 示例:
public final class Locale implements Cloneable, Serializable { static private final Cache LOCALECACHE = new Cache(); /** Useful constant for language. */ static public final Locale ENGLISH = createConstant("en", ""); /** Useful constant for language. */ static public final Locale JAPANESE = createConstant("ja", ""); /** Useful constant for language. */ static public final Locale CHINESE = createConstant("zh", ""); /** Useful constant for language. */ static public final Locale SIMPLIFIED_CHINESE = createConstant("zh", "CN"); /** Useful constant for language. */ static public final Locale TRADITIONAL_CHINESE = createConstant("zh", "TW");... ...
资源文件示例:
exceptions_zh_CN.properties、exceptions_ja.properties
Spring MessageSource 是基于 JAVA MessageSource,所以并不会合并相同 basename 的 bundle。
Spring 另外提供了ReloadableResourceBundleMessageSource 接口。除了可以提供如上基本功能外,它可以从任意 Spring 定义的资源位置读取文件,并且支持热加载。
2、标准的及自定义的事件ApplicationContext通过ApplicationEvent类及ApplicationListener接口来处理事件。容器内实现了ApplicationListener接口的对象能够获取任何ApplicationEvent发布的事件。 这种属于标准的观察者模式应用。
Spring 4.2 之后,事件框架做了显著升级,包括基于注解的实现及事件对象不再需要显式的继承 ApplicationEvent。
如下为 Spring 提供的标准事件:
ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent、RequestHandledEvent、ServletRequestHandledEvent。
自定义事件:
事件定义:
public class BlockedListEvent extends ApplicationEvent { private final String address; private final String content; public BlockedListEvent(Object source, String address, String content) { super(source); this.address = address; this.content = content; } // accessor and other methods...}
事件发布:
public class EmailService implements ApplicationEventPublisherAware { private ListblockedList; private ApplicationEventPublisher publisher; public void setBlockedList(List blockedList) { this.blockedList = blockedList; } public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { this.publisher = publisher; } public void sendEmail(String address, String content) { if (blockedList.contains(address)) { publisher.publishEvent(new BlockedListEvent(this, address, content)); return; } // send email... }}
事件监听:
public class BlockedListNotifier implements ApplicationListener{ private String notificationAddress; public void setNotificationAddress(String notificationAddress) { this.notificationAddress = notificationAddress; } public void onApplicationEvent(BlockedListEvent event) { // notify appropriate parties via notificationAddress... }}
基于注解的事件监听:
public class BlockedListNotifier { private String notificationAddress; public void setNotificationAddress(String notificationAddress) { this.notificationAddress = notificationAddress; } @EventListener public void processBlockedListEvent(BlockedListEvent event) { // notify appropriate parties via notificationAddress... }}
异步监听:
@EventListener@Asyncpublic void processBlockedListEvent(BlockedListEvent event) { // BlockedListEvent is processed in a separate thread}
顺序监听:
@EventListener@Order(42)public void processBlockedListEvent(BlockedListEvent event) { // notify appropriate parties via notificationAddress...}3、应用启动追踪
ApplicationContext负责管理 Spring 应用的生命周期。应用启动追踪主要用于测量不同启动步骤时间花费。
AbstractApplicationContext通过ApplicationStartup接口,收集各个启动阶段StartupStep数据:
application context lifecycle (base packages scanning, config classes management)
beans lifecycle (instantiation, smart initialization, post processing)
application events processing
AnnotationConfigApplicationContext应用示例:
// create a startup step and start recordingStartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan");// add tagging information to the current stepscanPackages.tag("packages", () -> Arrays.toString(basePackages));// perform the actual phase we"re instrumentingthis.scanner.scan(basePackages);// end the current stepscanPackages.end();
可以通过实现ApplicationStartup接口,自定义启动追踪类。如下:
.setApplicationStartup(new MyApplicationStartup())