《Spring揭秘》——IOC梳理3(资源加载,国际化,容器事件,多配置模块加载)
资源访问
JDK提供的访问资源的类并不能很好的满足各类资源访问需求,Spring提供了Resource接口以实现更强大的访问底层资源的能力。
Resource具体实现类:
ByteArrayResource :二进制数组表示的资源,二进制数组资源可以在内存中通过持续构造;
ClassPathResource :类路径下的资源,资源以相对于类路径的方式表示;
FileSystemResource :文件系统资源,资源以文件系统路径的方式表示;
UrlResource :封装了java.net.URL,使用户能够访问任何可以通过URL表示的资源;
InputStreamResource :以输入流返回表示的资源;
ServletContextResource:为访问Web容器上下文中的资源而设计的类,负责Web应用根目录的路径加载资源;
Resource res1 = new FileSystemResource("D:/c3/file.txt"); Resource res2 = new ClassPathResource("conf/file.txt"); InputStream ins1 = res1.getInputStream(); InputStream ins2 = res2.getInputStream();
//资源文件默认采用系统编码读取资源内容,若要采用特殊编码格式,则使用EncodeResource对象资源进行编码 EncodedResource encRes = new EncodedResource(res2, "UTF-8");
资源地址表达式:
- classpath:从类路径加载资源。classpath*则是指若在多个jar包或文件吸引类路径都拥有一个相同的包名,classpath只会在第一个加载的包下查找,而classpath*会扫描所有类路径下出现的该路径。(注意:ClassPathXmlApplicationContext在即使没有指明classpath情况下,默认从classpath中加载bean定义配置文件。而FileSystemXmlApplicationContext默认从文件系统中加载,若增加classpath:前缀会明确指定从classpath中加载bean定义的配置文件。)
- file:从文件系统目录加载资源,可采用绝对路径或相对路径;
- http:// :从Web服务器中加载资源
- ftp:// :从FTP服务器中装载资源
- 没有前缀:根据ApplicationContext具体实现类采用对应的类型的Resource
Ant风格资源地址表达式:
- ?:匹配文件名中的一个字符;
- *:匹配文件名中的任意个字符;
- **:匹配多层路径;
资源加载
查找和定位资源可使用ResourceLoader,其getResource方法可以根据一个资源地址加载文件资源(仅支持带资源类型前缀的表达式,不支持Ant风格的资源路径表达式)。
ResourcePatternResolver扩展ResourceLoader接口,其getResource方法支持带资源类型前缀及Ant风格的资源路径表达式。PathMatchingResourcePatternResolver是其标准实现类。
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resources = resolver.getResources("classpath*:com/abc/**/*.xml");
要注意的是:在构造PathMatchingResourcePatternResolver实例时,若不指定ResourceLoader,其内部会默认构造一个DefaultResourceLoader实例,内部会将匹配后确定的资源路径委派给他的ResourceLoader来查找和定位资源。可以通过传入其他类型的ResourceLoader来替换内部默认使用的DefaultResourceLoader,从而改变其默认行为,如使用FileSystemResourceLoader ,使PathMatchingResourcePatternResolver的行为跟FileSystemResourceLoader一样。
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(new FileSystemResourceLoader());
此外,如果一个类需要使用ResourceLoader,需要进行ResourceLoader注入时,可以让该类实现ResourceLoaderAware或者ApplicationContextAware接口。(具体参照P91)
总结(p89)
ApplicationContext与ResourceLoader
ApplicationContext继承了ResourcePatternResolver,间接实现了ResourceLoader接口。 所以ApplicationContext支持Spring内统一资源加载策略。
AbstractApplicationContext继承了DefaultResourceLoader,它的getResource(String)直接用DefaultResourceLoader的了。
AbstractApplicationContext类的内部声明有一个resourcePatternResolver,类型是ResourcePatternResolver,对应的实例类型为PathMatchingResourcePatternResolver 。
这样,整个ApplicationContext的实现类就完全可以支持ResourceLoader或者ResourcePatternResolver接口。
国际化(i18n)
“国际化信息”也称为“本地化信息”,包括“语言类型”和“国家/地区的类型”,中文(zh)、英语(en)、中国大陆(CN)、中国台湾(TW)、英国(EN)、美国(US)。
表示语言和国家/地区信息的本地化类为java.lang.Locale。创建本地化对象的示例:
Locale locale1 = Locale.getDefault(); Locale locale2 = Locale.CHINA; Locale locale3 = new Locale("zh", "CN");
此外,JDK中提供了几个支持本地化的格式化操作工具类:NumberFormat、DateFormat、MessageFormat。
//NumberFormat Locale locale = Locale.getDefault(); NumberFormat curFmt = NumberFormat.getCurrencyInstance(locale); System.out.println(curFmt.format(123.4)); //DateFormat DateFormat df = DateFormat.getDateInstance(DateFormat.LONG,locale); System.out.println(df.format(new Date())); //MessageFormat:提供了占位符字符串的格式化功能 String pattern1 = "hello,{0},{1}"; Object[] argu = {"Joey",new GregorianCalendar().getTime()}; System.out.println(MessageFormat.format(pattern1, argu)); //使用默认本地化对象格式化信息 MessageFormat mf = new MessageFormat(pattern1, Locale.US); System.out.println(mf.format(argu)); //使用指定的本地化对象格式化信息
ResourceBundle
Java提供了用于加载本地化资源文件的类ResourceBoundle,其为加载及访问资源文件提供了便捷的操作,从类路径加载名为resource的本地化资源文件:
ResourceBundle rb1 = ResourceBundle.getBundle("com/test/resource",Locale.CHINA); ResourceBundle rb2 = ResourceBundle.getBundle("com/test/resource",Locale.US); System.out.println(rb1.getString("greeting.common")); System.out.println(rb2.getString("greeting.common"));
关于本地化资源文件:
若资源名为resource,若语言为英文,国家为美国,则对应的本地化资源文件为resource_en_US.properties。中国大陆地区为resource_zh_CN.properties。
如果指定的本地化资源文件不存在,按以下顺序尝试加载其他资源:本地系统默认本地化对象对应的资源(比如想要加拿大的但没有,系统默认为中国,则使用resource_zh_CN.properties)—>默认的资源(resource.properties)。若依然不存在则会抛出MissingResourceException异常。
MessageSource
Spring定义了访问国际化信息的MessageSource接口,并提供了几个易用的实现类:
- ResourceBundleMessageSource (最为常用,用于正式生产,对于参数化信息和非参数化信息的处理进行优化);
- StaticMessageSource (多用于测试,不应该用于正式的生产环境);
- ReloadableResourceBundleMessageSource(主要可以定期刷新并检查底层资源文件是否已经改变) ;
ResourceBundleMessageSource
允许用户通过beanName指定一个资源名或通过beanNames指定一组资源名。
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basenames"> <list> <value>com/ioc/test</value> </list> </property> </bean>
一般使用时,id可以是其他(比如myResource),通过ctx.getBean("myResource")获得MessageSource,紧接着对MessageSource操作即可。但ApplicationContext实现了MessageSource接口。默认情况下,ApplicationContext将委派容器中一个名称为messageSource的MessageSource接口实现来完成MessageSource应该完成的职责,如果找不到则ApplicationContext内部会默认实例化一个不包含任何内容的StaticMessageSource实例。(所以,若直接使用ApplicationContext处理国际化资源时,上面配置文件的id必须为messageSource。
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml"); String message = ctx.getMessage("greeting.common", null, Locale.CHINA); Object[] params = {...}; //若greeting.common中使用到占位符 String message = ctx.getMessage("greeting.common", params, Locale.CHINA);
ReloadableResourceBundleMessageSource
与ResourceBundleMessageSource唯一区别就是其定时刷新资源文件,已便在应用程序不重启的情况下感知资源文件的变化,避免重启带来的负面影响。注意:cacheSounds默认值为-1表示永不刷新。
使用ReloadableResourceBundleMessageSource时,应该避免将信息资源文件放到classpath中,因为这无助于ReloadableResourceBundleMessageSource定期加载文件变更。(api文档:Since application servers typically cache all files loaded from the classpath, it is necessary to store resources somewhere else (for example, in the "WEB-INF" directory of a web app).
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>com/test/test</value>
</list>
</property>
<property name="cacheSeconds" value="3"></property>
</bean>
MessageSource类层次结构如下图(p98)
容器事件
Java通过EventObject类和EventListener描述事件和监听器,某个组件或框架要建立自己的事件发布和监听机制,一般都通过扩展它们进行定义。
Spring的ApplicationContext容器内部允许以ApplicationEvent的形式发布事件,容器内注册的ApplicationListener类型的bean会被ApplicationContext容器自动识别,来负责监听容器内发布的所有事件。即一旦容器内发布ApplicationEvent及其子类型的时间,注册到容器的ApplicationListener就会对这些事件进行处理。
ApplicationContext容器的事件发布功能全部委托给了ApplicationEventMulticaster(事件广播器,将事件通知给注册表中的事件监听器)来做,所以,容器启动就会检查容器内是否存在名称为applicationEventMulticaster的对象实例。没有则默认初始化一个SimpleApplicationEventMulticaster作为将会使用的ApplicationEventMulticaster。
案例(Spring3.x企业应用开发实战 p170)
MailSendEvent:
public class MailSendEvent extends ApplicationContextEvent{ private String to; public MailSendEvent(ApplicationContext source,String to) { super(source); this.to = to; } public String getTo() { return to; } }
MailSendListener:
public class MailSendListener implements ApplicationListener<MailSendEvent>{ @Override public void onApplicationEvent(MailSendEvent event) { MailSendEvent mse = (MailSendEvent)event; System.out.println("MailSendListener:向"+mse.getTo()+"发送完一封邮件"); } }
MailSender:
public class MailSender implements ApplicationContextAware{ private ApplicationContext ctx; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.ctx = applicationContext; } public void sendMail(String to){ System.out.println("MailSender:模拟发送邮件..."); MailSendEvent mse = new MailSendEvent(this.ctx, to); //向容器中所有事件监听器发送事件 ctx.publishEvent(mse); } }
配置文件:
<bean class="event.MailSendListener"></bean> <bean id="mailSender" class="event.MailSender"></bean>
Main:
ApplicationContext ctx = new ClassPathXmlApplicationContext("event.xml"); MailSender mailSender = (MailSender) ctx.getBean("mailSender"); mailSender.sendMail("123@123.com");
多配置模块加载
通常会将整个系统的配置信息按照某种关注点进行分割。这样,在加载整个系统的bean定义时,就需要让容器同时读入划分到不同配置文件的信息。相对于BeanFactory来说, ApplicationContext大大简化了这种情况下的多配置文件的加载工作,只要以String[]形式传入这些配置文件所在的路径,即可构造并启动容器。
String[] locations = new String[]{ "conf/dao-tier.springxml", "conf/view-tier.springxml", "conf/business-tier.springxml"}; ApplicationContext container = new FileSystemXmlApplicationContext(locations); // 或者 ApplicationContext container = new ClassPathXmlApplicationContext(locations);
//当然也可以使用通配符
ApplicationContext container = new FileSystemXmlApplicationContext("conf/**/*.springxml");