常用方式
我们比较常用的方式是通过xmlBeanFactory读取配置文件后再get获取bean,如下所示
XmlBeanFactory xmlBeanFactory=new XmlBeanFactory(new ClassPathResource("/bean.xml"));
Person person=(Person)xmlBeanFactory.getBean("person");
具体实现
那么具体里面做了什么操作呢?点开xmlbeanFactory源码发现
/**
* Convenience extension of {@link DefaultListableBeanFactory} that reads bean definitions
* from an XML document. Delegates to {@link XmlBeanDefinitionReader} underneath; effectively
* equivalent to using an XmlBeanDefinitionReader with a DefaultListableBeanFactory.
*
* <p>The structure, element and attribute names of the required XML document
* are hard-coded in this class. (Of course a transform could be run if necessary
* to produce this format). "beans" doesn't need to be the root element of the XML
* document: This class will parse all bean definition elements in the XML file.
*
* <p>This class registers each bean definition with the {@link DefaultListableBeanFactory}
* superclass, and relies on the latter's implementation of the {@link BeanFactory} interface.
* It supports singletons, prototypes, and references to either of these kinds of bean.
* See {@code "spring-beans-3.x.xsd"} (or historically, {@code "spring-beans-2.0.dtd"}) for
* details on options and configuration style.
*
* <p><b>For advanced needs, consider using a {@link DefaultListableBeanFactory} with
* an {@link XmlBeanDefinitionReader}.</b> The latter allows for reading from multiple XML
* resources and is highly configurable in its actual XML parsing behavior.
**/
这个类源码很简单,大概注释含义如下:
从一个xml文档中读取bean定义的类DefaultListableBeanFactory的方便扩展。委托给下面的XmlBeanDefinitionReader 调用。
在这个类中要求的xml文档的结构,元素,属性名是硬编码的,当然如果需要的话可以写其他格式,然后转换成这个格式。beans不必要是这个xml文档的根标签,这个类会解析这个文档里的所有bean定义元素。
这个类用父类DefaultListableBeanFactory注册每个bean定义,依赖于后者对beanFactory的实现。支持单例,多例,或者两者的引用。
看spring-beans-3.x.xsd或者由于历史原因spring-beans-2.0.dtd,了解配置方式和可选项的具体细节。
由于更进一步的想,需要,可以使用DefaultListableBeanFactory和XmlBeanDefinitionReader搭配。后者允许从多个xml配置文件读取而且在实际xml解析行为中是高度可配置的。
至此,我们基本了解了xmlbeanFactory做了什么。下面列出具体流程:
ClassPathResource resource=new ClassPathResource("/bean.xml");
DefaultListableBeanFactory factory=new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);
Person person=(Person)factory.getBean("person");
流程总结如下:
1. 定义spring配置文件
2. 通过Resource对象将配置文件抽象成一个Resource对象
3. 创建bean工厂
4. 创建bean定义读取器,并将工厂传入
5. bean定义读取器传入抽象的Resource对象,加载配置好的bean定义文件(包含xml解析过程)
6. 容器创建完毕,可从容器获取bean
针对上面流程,后面我会拆分说一下resource 和definitionReader
上面提到spring会从定义好的xml里面通过Resource这个对象获取文件相关配置信息,那么这些操作具体是如何做的呢?
ClassPathResource继承体系如上所示,spring会把相关操作公共的行为提取到父类或者接口里面,子类要做的就是继承这个父类并实现一些自己特有的内容。
我们逐个分析:
上面我们创建resource用了classpathResource构造方法如下
public ClassPathResource(String path) {
this(path, (ClassLoader) null);
}
/**
* Create a new {@code ClassPathResource} for {@code ClassLoader} usage.
* A leading slash will be removed, as the ClassLoader resource access
* methods will not accept it.
* @param path the absolute path within the classpath
* @param classLoader the class loader to load the resource with,
* or {@code null} for the thread context class loader
* @see ClassLoader#getResourceAsStream(String)
*/
public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
Assert.notNull(path, "Path must not be null");
String pathToUse = StringUtils.cleanPath(path);
if (pathToUse.startsWith("/")) {
pathToUse = pathToUse.substring(1);
}
this.path = pathToUse;
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
这里贴出这块代码主要是想说一种代码风格,通过在一个类中重载方法这样可以方便调用者调用不同的方法,如果调用者只能提供一个参数,可以直接传path进来 ,不需要考虑其他参数的设置方式,其他参数由方法内部自己实现默认值设置,这样避免方法因为调用者传参不合理而导致的一些异常。
我们进入父类AbstractFileResolvingResource里面,父类里面方法如下:
可以看到,父类把一些针对资源处理的比较常用的公共方法提取出来做了默认实现,那么光看这个读者有没有疑惑?父类是个抽象类,既然对isExist()方法做了默认实现,那我们子类如果不重写isExist调用isExist方法,那应该调用的是父类的,但是父类方法不是公共的么,他是怎么拿到资源的类似url的具体信息的?
@Override
public boolean exists() {
try {
URL url = getURL();
if (ResourceUtils.isFileURL(url)) {
// Proceed with file system resolution
return getFile().exists();
}
else {
// Try a URL connection content-length header
URLConnection con = url.openConnection();
customizeConnection(con);
HttpURLConnection httpCon =
(con instanceof HttpURLConnection ? (HttpURLConnection) con : null);
if (httpCon != null) {
int code = httpCon.getResponseCode();
if (code == HttpURLConnection.HTTP_OK) {
return true;
}
else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
return false;
}
}
if (con.getContentLengthLong() > 0) {
return true;
}
if (httpCon != null) {
// No HTTP OK status, and no content-length header: give up
httpCon.disconnect();
return false;
}
else {
// Fall back to stream existence: can we open the stream?
getInputStream().close();
return true;
}
}
}
catch (IOException ex) {
return false;
}
}
/**
* This implementation throws a FileNotFoundException, assuming
* that the resource cannot be resolved to a URL.
*/
@Override
public URL getURL() throws IOException {
throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
}
看父类源码发现,这个方法里面调用了自身的另一个方法getUrl()获取资源,但是这个方法啥都没看,直接抛出了异常。。。那么问题来了,直接抛出异常,那这个方法不应该会报错么,为啥这样做?
机智boy看到这应该已经反应过来了,子类肯定重写了这个getUrl的方法,这样子类调用父类的isExist方法,因为子类重写了getUrl,所以会调用子类的getUrl方法获取资源,最后实现方法调用。
如下:classpathResource重写了方法:
@Override
public URL getURL() throws IOException {
URL url = resolveURL();
if (url == null) {
throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
}
return url;
}
/**
* Resolves a URL for the underlying class path resource.
* @return the resolved URL, or {@code null} if not resolvable
*/
@Nullable
protected URL resolveURL() {
if (this.clazz != null) {
return this.clazz.getResource(this.path);
}
else if (this.classLoader != null) {
return this.classLoader.getResource(this.path);
}
else {
return ClassLoader.getSystemResource(this.path);
}
}
现在回头看这块代码,如我在上面说的,通过把一些公共的方法抽取给个默认实现,针对子类的特殊要求,子类再具体实现一些具体细节,鉴于笔者比较技术水平有限,可能描述不清楚这种设计理念,姑且把他当做模板设计模式,后面我再找找资料。
篇幅问题,spring加载资源的内容我下一篇博客阐述吧
最后,感谢阅读,如有错误之处,请不吝指正。