配置就是xml,spring将对物理资源的访问方式抽象成Resource。
Resource是个接口,继承了InputStreamSource,定义了资源的基本操作(全是读操作) InputStreamSource有唯一一个方法getInputStream
主要是根据不同的资源,定义了不同类的实现。
- ServletContextResource负责以相对于Web应用程序根目录的路径加载资源,支持以流或url的形式进行访问,在war包解压出来的情况下,也可以通过file的形式访问
- ClassPathResource用于访问类加载路径下的资源,对于web应用来说,可以自动加载WEB-INF/classes目录下的资源文件,无需使用绝对路径访问
- FileSystemResource用于访问文件系统资源,优势不明显,java的File类也可以做到
EncodedResource
主要实现对资源文件的编码处理,其具体的逻辑实现在getReader 当我们给资源设置了编码属性之后,Spring会使用相应的编码作为输入流的编码
AbstractResource
主要提供了Resource方法的大部分的默认公共实现,如果想要自定义Resource,不推荐直接继承Resource接口,而更应该继承这个抽象类。
WritableResource
FileSystemResource为了能实现写操作,继承了WritableResource,其中有返回输出流实例的方法
强大的加载资源的方式:
- 自动识别"classpath:"、"file:"等资源地址前缀
- 支持自动解析Ant风格带通配符的资源地址
Ant: 路径匹配表达式,用来对URI进行匹配
- ?匹配任何单字符
- *匹配0或者任意数量的字符
- **匹配0或者更多的目录
实现不同的Resource加载策略,按需返回特定类型的Resource:
是个接口,Resource getResource(String location);方法可以根据传入的location自动返回一个Resource实例(前面说的三个具体实现类)。
还提供了ClassLoader getClassLoader();方法暴露出来类加载器 DefaultResourceLoader.java提供了ResourceLoader的实现,最关键的是getResource方法
// 获取Resource的具体实现类实例
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
// ProtocolResolver用户自定义协议资源解决策略
// 有的话就拿过来用一下,去解析location(看用户是否提前指定好了根据不同的location去解析resource实例)
for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
// 如果是以/开头,则构造ClassPathContextResource返回
if (location.startsWith("/")) {
return getResourceByPath(location);
}
// 若以classpath:开头,则构造 ClassPathResource 类型资源并返回,在构造该资源时,通过 getClassLoader()获取当前的 ClassLoader
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
// 构造URL,尝试通过它进行资源定位,若没有抛出MalformedURLException异常,
// 判断是否为FileURL,如果是则构造 FileUrlResource 类型资源,否则构造UrlResource。
// 若在加载过程中抛出MalformedURLException异常,
// 则委派 getResourceByPath实现资源定位加载
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
// 返回的是ClassPathContextResource
return getResourceByPath(location);
}
}
}
这里使用的是策略模式,Resource是策略接口,以及很多的实现子类是策略类,通过DefaultResourceLoader,决定返回的是哪个具体的策略(实现类)。策略模式需要用户明确知道策略,即什么样的资源用什么样的Resource,工厂模式用户不必知道细节。
ResourcePatternResolver提供了根据路径匹配模式,返回多个Resource实例,
Resource[] getResources(String locationPattern) throws IOException;
同时新增了一个协议前缀,该前缀由子类负责实现
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
在继承关系表中可以发现ApplicationContext也间接实现了ResourceLoader接口
AbstractApplicationContext继承了DefaultResourceLoader,并且里面有getResourcePatternResolver方法,用于生成PathMatchingResourcePatternResolver这个实例。
这个方法是在 AbstractApplicationContext 的构造函数里用的,也就意味着AbstractApplicationContext 可以调用 ResourcePatternResolver 的 getResources 方法,来根据路径返回多个Resource实例。
综上所述,AbstractApplicationContext 的实现完全可以支持ResourceLoader和ResourcePatternResolver,这也是高级容器为什么支持统一资源加载的原因。委托给了PathMatchingResourcePatternResolver 和 DefaultResourceLoader 来执行。
利器的使用者
- 读取BeanDefinition
- BeanDefinitionRegistry
BeanDefinitionReader会利用ResourceLoader或者ResourcePatternResolver将配置信息解析成一个个的BeanDefinition,并最终借助BeanDefinitionRegistry的注册接口,将BeanDefinition给注册到容器里。
BeanDefinitionReader定义了一系列加载BeanDefinition的接口,针对单个配置文件:
int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
针对多个配置文件:
int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
针对单个Resource资源实例:
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
针对多个Resource资源实例:
int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
最终的目的就是将配置文件的配置转换成一个个的BeanDefinition
还有其他方法:
/** * 获取BeanDefinitionRegistry对象,这个类的主要作用将BeanDefinition注册到BeanDefinition的注册表中 * @return */
BeanDefinitionRegistry getRegistry();
/** * Bean的名字生成器,为匿名bean生成一个名字,就是id * @return */
BeanNameGenerator getBeanNameGenerator();
BeanDefinitionReader体系结构
AbstractBeanDefinitionReader实现了公共处理逻辑,主要的一个方法是loadBeanDefinitions,方法内先获取ResourceLoader,再判断获取的实例是哪一个实例,如果是ResourcePatternResolver的话则代表需要加载多个资源,else里面是只加载单个资源,最终都会调用loadBeanDefinitions方法做进一步的加载。
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
// 获取资源加载器,主要的功能就是根据路径和类加载器获取Resource对象
ResourceLoader resourceLoader = getResourceLoader();
// 判断资源加载器是否为空
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
}
// ResourcePatternResolver 用于加载多个文件或者能够加载Ant风格路径的文件资源
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int count = loadBeanDefinitions(resources);
if (actualResources != null) {
Collections.addAll(actualResources, resources);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
}
return count;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.
// 加载单个文件资源
// 直接使用ResouceLoader加载
Resource resource = resourceLoader.getResource(location);
int count = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
}
return count;
}
}
XmlBeanDefinitionReader
从xml文件中读取bean definitions的方法
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
又主要调用了上面的loadBeanDefinitions的if里面的逻辑 会根据location获取到Resource实例。
loadBeanDefinitions方法会先指定编码以解析xml资源:
new EncodedResource(resource)
之后执行XmlBeanDefinitionReader的loadBeanDefinitions,这个方法返回一个int类型的数据,表示本次加载并注册到容器里面的bean definition的个数。
为了支持多线程加载,加载是一个耗时过程,方法里面用到了resourcesCurrentlyBeingLoaded,是一个ThreadLocal类型的本地变量来存储正在加载的资源。(使用ThreadLocal修饰的变量只能当前线程对其修改,key是线程号,value是线程对应的资源)。
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}
// 从本地线程变量中获取当前的正在加载的资源
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
// 如果本地线程变量中不存在正在加载的资源,那么将其添加进去
if (currentResources == null) {
currentResources = new HashSet<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
// 如果encodedResource添加进入currentResources失败,表明其中已经存在这个资源,只不过没有加载完成
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
// 获取文件的输入流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
// 封装成InputSource,其中指定了输入流和编码格式
InputSource inputSource = new InputSource(inputStream);
// 如果存在编码,那么将其添加进入InputSource中
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 调用同类的方法继续解析
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
// 关闭输入流
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
其中调用了doLoadBeanDefinitions方法,解析出一个个属性并注册到容器,完成我们的需求。