在第三章中已经提到了,ApplicationContext除了和BeanFactory共同实现了两个接口,但是ApplicationContext有另外实现了其他三个接口,分别是FileSystemXmlApplicationContext、FileSystemXmlApplicationContext、XmlWebApplicationContext,这也就意味着ApplicationContext在功能上面有了扩展,将其当做是BeanFactory的升级版也是可以的
- 统一资源加载策略
spring框架中使用org.springframework.core.io.Resource作为所有的资源文件访问的接口,有几种不同的使用场景的接口:
· ByteArrayResource 该实现根据字节数组的数据,构造相应的ByteArrayInputStream并返回
· ClassPathResource 从Java应用程序的ClassPath中加载具体资源并封装,使用指定的类加载器或者给定的类进行资源加载
· URLResource 通过java.net.URL进行具体的资源加载
· InputStreamResource 较为少用的资源加载Resource的实现类,一般可用ByteArrayResource代替
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;
}
在Resource中定义了七个方法,可以用来对资源文件的查找、访问
ResourceLoader,“更加广义的URL”
- DefaultResourceLoader
ResourceLoader的默认的资源加载机制,默认加载逻辑:
(1) 首先查询资源路径是否是以“classpath:”开头,如果是,就尝试构造ClassPathLoader类型资源返回
(2) 否则,常使用URL定位资源文件位置,如果没有抛出MalformedURLException,就会构造URLResource类型的资源并返回;如果无法找到资源文件,则需要委派getResourceByPath(String xx)方法来定位
- FileSystemResourceLoader
为了避免DefaultResourceLoader在getResourceByPath(String xx)不恰当的处理,FileSystemResourceLoader继承了DefaultResourceLoader并重写了getResourceByPath(String xx)方法,返回FileSystemResourceLoader类型资源,重写方法:
public void testResourceTypesWithFileSystemResourceLoader()
{
ResourceLoader resourceLoader = new FileSystemResourceLoader();
Resource fileResource = resourceLoader.getResource("D:/spring21site/README");
assertTrue(fileResource instanceof FileSystemResource);
assertTrue(fileResource.exists());
Resource urlResource = resourceLoader.getResource("file:D:/spring21site/README");
assertTrue(urlResource instanceof UrlResource);
}
覆写getResourceByPath(String xx)方法的逻辑,以改变DefaultResourceLoader的默认资源加载行为,最终从文件系统中加载并返回FileSystemResource类型的资源。
ResourcePatternResolver——批量查找的ResourceLoader
ResourceLoader是加载一个资源文件,返回一个资源实例,而ResourcePatternResolver是可以批量查找资源文件,返回多个资源实例,ResourcePatternResolver的接口定义:
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}
从接口定义中可以看出,ResourcePatternResolver引入了 Resource[] getResources(String locationPattern),也就是说匹配过程中将资源文件都存入这个数组中,返回的是资源文件夹,并且有了classpath*:的新定义。但是加载资源文件的方式是一致的
各个接口之间的关系
- ApplicationContext与ResourceLoader
所有的ApplicationContext实现类会直接或者间接地继承AbstractApplicationContext,AbstractApplicationContext继承了DefaultResourceLoader,所以说ApplicationContext的实现类可以直接查找加载资源文件
- 可以通过ApplicationContext的实现类加载Spring支持的Resource类型
- 如果某个bean需要依赖特定的ResourceLoader查找资源,可以为其注入ResourceLoader的具体实现
- 国际化信息支持
JavaSE对于国际化有了很好的支持,涉及的两个类是Local和ResourceBundle,都是util包中的类
(1)Local用于代表不同的国家和地区,例如:Local.CHINA代表中国
(2)ResourceBundle用来保存特定于某个Locale的信息(可以是String类型信息,也可以是任何类型的对象),相应的有properties文件,我们就可以通过ResourceBundle的getBundle(String baseName, Locale locale)方法取得不同Locale对应的ResourceBundle,再从ResourceBundle获取信息,对于MessageSource提供了三种方法:
public interface MessageSource {
String getMessage(String code, Object[] args, String defaultMessage, Locale locale);
String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessage zException;
}
第二种方法和第一个方法区别在于找不到资源的话会抛出异常
ApplicationContext也实现了MessageSource接口,它会委派MessageSource接口来完成MessageSource的工作,如果不存在这个接口,容器会默认创建一个不含任何内容的StaticMessageSource实例,完成相应的工作
上图是spring实现Message的三个接口
- 容器内部事件发布
自定义事件类型,通过扩展java.util.EventObject类来实现自定义事件类型,实现针对自定义事件类的事件监听接口,对自定义时间进行监听
public interface MethodExecutionEventListener extends EventListener {
/**
* 处理方法开始执行的时候发布的MethodExecutionEvent事件
*/
void onMethodBegin(MethodExecutionEvent evt);
/** * 处理方法执行将结束时候发布的MethodExecutionEvent事件
*/
void onMethodEnd(MethodExecutionEvent evt);
}
自定义事件事项上面的MethodExecutionEventListener接口,针对不同的事件作出相应的处理
以上为准备工作,有了自定义事件和事件监听器,那么就剩下发布事件,事件发布类需要注意的两点是:
public class MethodExeuctionEventPublisher {
private List<MethodExecutionEventListener> listeners = new
ArrayList<MethodExecutionEventListener>();
public void methodToMonitor()
{
MethodExecutionEvent event2Publish =
new MethodExecutionEvent(this,"methodToMonitor");
publishEvent(MethodExecutionStatus.BEGIN,event2Publish);
// 执行实际的方法逻辑
// ...
publishEvent(MethodExecutionStatus.END,event2Publish);
}
protected void publishEvent(MethodExecutionStatus status, MethodExecutionEvent methodExecutionEvent) {
List<MethodExecutionEventListener> copyListeners =
new ArrayList<MethodExecutionEventListener>(listeners);
for(MethodExecutionEventListener listener:copyListeners)
{
if(MethodExecutionStatus.BEGIN.equals(status))
listener.onMethodBegin(methodExecutionEvent);
else
listener.onMethodEnd(methodExecutionEvent);
}
}
public void addMethodExecutionEventListener(MethodExecutionEventListener listener)
{
this.listeners.add(listener);
}
public void removeListener(MethodExecutionEventListener listener)
{
if(this.listeners.contains(listener))
this.listeners.remove(listener);
}
public void removeAllListeners()
{
this.listeners.clear();
}
}
① 具体时点上的事件发布,methodToMonitor()是事件发布的源头,而且对于整个监听事件做了安全复制(safe-copy),防止在事件注册或者删除的时候影响处理过程
② MethodExeuctionEventPublisher提供了事件监听器的添加和移除操作,防止监听器被MethodExeuctionEventPublisher类无限调用,没有remove方法,即使不在使用这些监听器也会依然在MethodExeuctionEventPublisher的监听器列表中,造成内存泄漏
知道了监听事件的工作流程,那么spring的内部事件监听发布的原理已经渐渐浮现:
- ApplicationEvent 自定义监听事件接口,有三个实现类:
- ContextClosedEvent 用于容器即将关闭的时候发布的事件类型
- ContextRefreshedEvent 容器刷新或者初始化时候发布的事件类型
- ContextHandleEvent Web请求处理后发布的事件类型,并且其子类类ServletRequestHandledEvent专门用于JavaEE的Servlet请求事件
- ApplicationListener 自定义事件监听器接口
- ApplicationContext 容器本身就是事件发布者,但是真正的事件发布并非容器本身,而是委派给org.springframework.context.event.ApplicationEventMulticaster接口
由于ApplicationContext容器将监听委托给其他对象,所以一旦启动容器就会查找名称为ApplicationEventMulticaster的对象实例applicationEventMulticaster,如果不存在此对象,就会默认创建一个SimpleApplicationEventMulticaster来完成任务
Spring容器监听事件内部实现结构图
- Spring容器内事件发布的应用
Spring中的ApplicationContext容器内的事件发布机制,主要用于单一容器内的简单消息通知和处理,不适合分布式、多进程、多容器之间的事件通知。
- 多配置模块加载的简化
ApplicationContext容器比BeanFactory做的更加好,并非特有的功能
引入配置文件中BeanFactory就需要创建FileSystemResource的对象进行文件获取,步骤很繁琐,代码量也很大,但是换做是ApplicationContext就比较简单
书中对于这些知识介绍的比较详细,见P.110