6.AOP
面向切面编程,在我们的应用中,经常需要做一些事情,但是这些事情与核心业务无关,比如,要记录所有update*方法的执行时间时间,操作人等等信息,记录到日志,
通过spring的AOP技术,就可以在不修改update*的代码的情况下完成该需求。
7.AOP的实现原理------代理
一 先了解一下ioc:
ioc是控制反转的意思, 控制反转是一种通过描述(在java中可以是xml或者注解)并通过第三方去产生或者获取特定对象的方式。实现他滴方法叫依赖注入
个人理解就是:给我原材料我给你造或者帮你找出来,举一个媒婆的例子
二 ioc容器
spring就是bean和bean关系的整合 那么负责这一块的容器就叫ioc容器
我们分析ioc就要从实现ioc开始 想想上面讲的 我们要关注两个方面原材料和怎么找
Ioc容器的设计主要是基于BeanFactory和 ApplicationContext, ApplicationContext是beanFactory的子接口,扩展了很多功能
常用的方法有 isSingleton, isPrototype(非单例), getBean, getType
Spring 以容器管理所有的 bean 对象,容器的实体是一个 BeanFactory 对象。但我们常用的容器是另一个 ApplicationContext ,它在内部持有了 BeanFactory,所有和 BeanFactory 相关的操作都会委托给内部的 BeanFactory 来完成
IOC容器初始化
SpringBoot 中的 ApplicationContext 初始化在 refresh() 方法中,会进行刷新前准备,其中重要的过程就是
1)bean的定义:
1. resources定位:读取XML或者注解资源
2. beandefinition的载入:讲resources定位的信息保存到beandfinition中
3)beandefinition的注册:讲beandefinition的信息发布到容器之中
始化的过程主要就是,并解析,最终注册到Bean Factory中: 此时没有bean的实例
2) 注入依赖和初始化
如果bean没有设置lazy-init(延迟加载)属性,那么bean的实例就会在初始化IOC完成之后,自动进行初始化。如果设置为true的话
,会在容器一次索取bean的时候发生初始化。
依赖注入是完成初始化的关键,三种方式:构造器,setter,接口(应该算是静态工厂或者实例工厂的一种方法)
3)bean是通过工厂模式创建的 或者可以构造函数创建(也包括有参 无参构造函数)
4)依赖注入的实现方式是反射 首先通过class.forname得到类,然后创建newStance实例对象,setProperty方法完成属性的注入
总结为一下四步:
1.资源文件定位
2.解析,
3.注册
4.实例化
结束,下面是详细过程
1)底层接口BeanFacory
在Spring当中最基本的IOC容器接口是BeanFactory,这个接口当中定义作为一个IOC容器最基本的一些操作和功能,下来看看它的源码:
/**
* BeanFactory作为最原始同时也最重要的Ioc容器,
它主要的功能是为依赖注入 (DI) 提供支持,BeanFactory 和相关的接口.
这里定义的只是一系列的接口方法,通过这一系列的BeanFactory接口,
可以使用不同的Bean的检索方法很方便地从Ioc容器中得到需要的Bean,
从而忽略具体的Ioc容器的实现,
从这个角度上看,这些检索方法代表的是最为基本的容器入口。
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Chris Beams
* @since 13 April 2001
*/
public interface BeanFactory {
/**
* 转定义符"&" 用来引用实例,或把它和工厂产生的Bean区分开,就是说,如果一个FactoryBean的名字为a,那么,&a会得到那个Factory
*
* FactoryBean和BeanFactory 是在Spring中使用最为频繁的类,它们在拼写上很相似。一个是Factory,也就是Ioc容器或对象工厂;一个
* 是Bean。在Spring中,所有的Bean都是由BeanFactory(也就是Ioc容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Be
* an,而是一个能产生或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。
*/
String FACTORY_BEAN_PREFIX = "&";
/**
* 五个不同形式的getBean方法,获取实例
* @param name 检索所用的Bean名
* @return Object(<T> T) 实例对象
* @throws BeansException 如果Bean不能取得
*/
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
/**
* 让用户判断容器是否含有指定名字的Bean.
* @param name 搜索所用的Bean名
* @return boolean 是否包含其中
*/
boolean containsBean(String name);
/**
* 查询指定名字的Bean是否是Singleton类型的Bean.
* 对于Singleton属性,可以在BeanDefinition指定.
* @param name 搜索所用的Bean名
* @return boolean 是否包是Singleton
* @throws NoSuchBeanDefinitionException 没有找到Bean
*/
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
/**
* 查询指定名字的Bean是否是Prototype类型的。
* 与Singleton属性一样,可以在BeanDefinition指定.
* @param name 搜索所用的Bean名
* @return boolean 是否包是Prototype
* @throws NoSuchBeanDefinitionException 没有找到Bean
*/
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
/**
* 查询指定了名字的Bean的Class类型是否是特定的Class类型.
* @param name 搜索所用的Bean名
* @param typeToMatch 匹配类型
* @return boolean 是否是特定类型
* @throws NoSuchBeanDefinitionException 没有找到Bean
*/
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
/**
* 查询指定名字的Bean的Class类型.
* @param name 搜索所用的Bean名
* @return 指定的Bean或者null(没有找到合适的Bean)
* @throws NoSuchBeanDefinitionException 没有找到Bean
*/
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
/**
* 查询指定了名字的Bean的所有别名,这些都是在BeanDefinition中定义的
* @param name 搜索所用的Bean名
* @return 指定名字的Bean的所有别名 或者一个空的数组
*/
String[] getAliases(String name);
}
可以看出上面包含了获取bean 看bean的类别啊等多种方法,在BeanFactory里面只对IOC容器最基本的行为做了定义,而不关心Bean是怎样定义和加载的。如果我们想要知道一个工厂具体生产对象的过程,就需要去看这个接口的实现类,在Spring当中实现这个接口的有很多子类,下面我们来看看其一个常用的实现类XmlBeanFacotry的代码吧。
在这个之前我们先看个例子: 利用XmlBeanFactory实现最原始Ioc容器
准备工作
1、实体类User.java
package main.ioc;
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
一个实体
2、XML资源文件beans.xml
<?xml version= "1.0" encoding ="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="user1" class="main.ioc.User">
<property name = "name" value="yanxiao"></property>
<property name = "age" value="20"></property>
</bean>
</beans>
这是不是就相当于实体user的原材料
3.最原始IOC容器的使用
package main.test;
import main.ioc.User;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class MyBeanFactory {
public static void main(String[] args){
ClassPathResource resource = new ClassPathResource("resources/ioc.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
//原料, 实体
User user = beanFactory.getBean("user1", User.class);
System.out.println(user.getName()+ ": "+ user.getAge());
}
}
定位资源, 创建工厂,工厂产生user,得到对象,这样一来我们只需要修改原料xml就可得到不同的美味实体对吧
跟踪上面的代码流程
使用单步调试了解上面代码的主要内部调用流程,这里没有跟踪getBean()方法的过程,该步骤后设计Bean的解析过程,后面我们再说这个
通过上面的流程,我们可以简单的将使用IOC容器的步骤概括如下:
1.创建Ioc配置文件的抽象资源,这个抽象资源包含了BeanDefinition的定义信息
2.创建一个BeanFactory,这里使用了DefaultListableBeanFactory
3.创建一个载入BeanDefinition的读取器,这里使用XmlBeanDefinitionReader来载入XML文件形式的BeanDefinition
4.然后将上面定位好的Resource,通过一个回调配置给BeanFactory
5.从定位好的资源位置读入配置信息,具体的解析过程由XmlBeanDefinitionReader完成
6.完成整个载入和注册Bean定义之后,需要的Ioc容器就初步建立起来了
大致概括为: 读bean的信息接口--读xml文件的信息接口--创建bean的工厂接口
我们对这个内部流程有了了解了以后,下面可以在实践当中进行应用,我们之前写的MySimpleBeanFactory代码便可以更改为下面的形式:
public static void main(String[] args) {
ClassPathResource resource = new ClassPathResource("META-INF/beans.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);
User user = factory.getBean("user", User.class);
System.out.println(user.getName()+" : "+user.getAge());
}
我们现在对IOC大体上有了初步了解后,下面对一些关键点进行细节上的一些分析。首先,从我们第一步创建的ClassPathResource对象说起,重新回过头来认识Spring当中的Resource。
Resource接口体系
在上面的文章我们也简单提到过 Resource 接口的作用,其实Spring使用自己的抽象结构对整个框架中所用到资源进行统一的封装,封装这个接口的原因其实大家也大致可以想到,我们在编程当中所遇到的资源文件类型多种多样有File、InputStream、UrlResource、ContextResource等等,面对如此多的资源类型框架内部要是不设计出一套成熟合理的资源封装体系的话,无疑对框架的实现和开发者的编程来说造成很多的复杂性。面向对象编程当中的封装和继承特性,在Spring当中可谓应用的淋漓尽致,有时间自己可以花点功夫体会Spring框架这一套设计体系对我们的以后编写可扩展和可用代码有很大的好处。
继续展开上面Resource接口的介绍,该接口作为资源的一个原始封装,它继承自InputStreamResource接口,InputStreamResource只有InputStream getInputStream()这样一个方法,通过这次继承赋予了通过Resource对象获得InputStram流的功能。下面看看接口的源码:
InputStreamResource接口:
package org.springframework.core.io;
import java.io.IOException;
import java.io.InputStream;
/**
* @author Juergen Hoeller
* @since 20.01.2004
*/
public interface InputStreamSource {
/**
* 返回InputStream的类,比如File、Classpath下的资源和Byte Array等
* @return InputStream 返回一个新的InputStream的对象
* @throws IOException
*/
InputStream getInputStream() throws IOException;
}
Resource接口
package org.springframework.core.io;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
/**
* Resource接口抽象了所有Spring内部使用到的底层资源:File、URL、Classpath等。
* 同时,对于来源不同的资源文件,Resource也有不同实现:文件(FileSystemResource)、Classpath资源(ClassPathResource)、
* URL资源(UrlResource)、InputStream资源(InputStreamResource)、Byte数组(ByteArrayResource)等等。
*
* @author Juergen Hoeller
* @since 28.12.2003
*/
public interface Resource extends InputStreamSource {
/**
* 判断资源是否存在
* @return boolean 是否存在
*/
boolean exists();
/**
* 判断资源是否可读
* @return boolean 是否可读
*/
boolean isReadable();
/**
* 是否处于开启状态
* @return boolean 是否开启
*/
boolean isOpen();
/**
* 得到URL类型资源,用于资源转换
* @return URL 得到URL类型
* @throws IOException 如果资源不能打开则抛出异常
*/
URL getURL() throws IOException;
/**
* 得到URI类型资源,用于资源转换
* @return URI 得到URI类型
* @throws IOException 如果资源不能打开则抛出异常
*/
URI getURI() throws IOException;
/**
* 得到File类型资源,用于资源转换
* @return File 得到File类型
* @throws IOException 如果资源不能打开则抛出异常
*/
File getFile() throws IOException;
/**
* 获取资源长度
* @return long 资源长度
* @throws IOException 如果资源不能打开则抛出异常
*/
long contentLength() throws IOException;
/**
* 获取lastModified属性
* @return long 获取lastModified
* @throws IOException 如果资源不能打开则抛出异常
*/
long lastModified() throws IOException;
/**
* 创建一个相对的资源方法
* @param relativePath 相对路径
* @return Resource 返回一个新的资源
* @throws IOException 如果资源不能打开则抛出异常
*/
Resource createRelative(String relativePath) throws IOException;
/**
* 获取文件名称
* @return String 文件名称或者null
*/
String getFilename();
/**
* 得到错误处理信息,主要用于错误处理的信息打印
* @return String 错误资源信息
*/
String getDescription();
}
支持多种资源的转换
ClassPathResource
再看看我们上面代码当中所用到的Resource接口的实现类ClassPathResource的部分需要重点关注的源码
package org.springframework.core.io;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* @author Juergen Hoeller
* @author Sam Brannen
* @since 28.12.2003
* @see ClassLoader#getResourceAsStream(String)
* @see Class#getResourceAsStream(String)
*/
public class ClassPathResource extends AbstractFileResolvingResource {
private final String path;
private ClassLoader classLoader;
private Class<?> clazz;
/**
通过资源的路径创建一个ClassPathResource对象
*/
public ClassPathResource(String path) {
this(path, (ClassLoader) null);
}
/**
上面构造方法的具体实现
*/
public ClassPathResource(String path, 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());
}
/**
* Create a new {@code ClassPathResource} for {@code Class} usage.
* The path can be relative to the given class, or absolute within
* the classpath via a leading slash.
* @param path relative or absolute path within the class path
* @param clazz the class to load resources with
* @see java.lang.Class#getResourceAsStream
*/
public ClassPathResource(String path, Class<?> clazz) {
Assert.notNull(path, "Path must not be null");
this.path = StringUtils.cleanPath(path);
this.clazz = clazz;
}
/**
获得InputStram的具体实现
* This implementation opens an InputStream for the given class path resource.
* @see java.lang.ClassLoader#getResourceAsStream(String)
* @see java.lang.Class#getResourceAsStream(String)
*/
@Override
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
/**
获得文件名
* This implementation returns the name of the file that this class path
* resource refers to.
* @see org.springframework.util.StringUtils#getFilename(String)
*/
@Override
public String getFilename() {
return StringUtils.getFilename(this.path);
}
}
通过地址或者类加载器去构建输入流
DefaultListableBeanFactory类
我们已经把资源层面上的接口和实现了解了,下面分析我们之前代码当中的DefaultListableBeanFactory factory = new DefaultListableBeanFactory();这句。在这里我们有引入了一个新类DefaultListableBeanFactory,从IOC类图中可以看到这个是BeanFactory的一个默认实现类,其实例对象可以作为一个独立使用的IOC容器,可以认为该对象是容纳Bean对象的一个容器,我们定义好的Bean对象经过Spring的载入和加载等处理后,最终交由改工厂进行管理,我们需要得到Bean对象时便可以向它获取。 我们调用其无参构造方法创建了factory对象,看看底层都做了哪些事情。
@SuppressWarnings("serial")
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
/**
* Create a new DefaultListableBeanFactory.
*/
public DefaultListableBeanFactory() {
super();//调用了父类的无参构造方法
}
}
接下来跟踪父类AbstractAutowireCapableBeanFactory中源码
AbstractAutowireCapableBeanFactory类
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
/**
* Create a new AbstractAutowireCapableBeanFactory.
*/
public AbstractAutowireCapableBeanFactory() {
super();//父类AbstractBeanFactory的无参构造方法,代码在下面↓↓↓
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
}
AbstractBeanFactory类
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
/**
* Create a new AbstractBeanFactory.
*/
public AbstractBeanFactory() {
//然而这里并没有做什么
}
}
BeanDefinition的载入、解析和注册
上面我们创建好了 DefaultListableBeanFactory 对象,已经有了容纳Bean的容器了,但我们通过刚才分析源码可以看到现在我们还没有对该factory进行其他实用性的操作,里面还没有任何东西。那么,我们定义在配置文件定义的Bean是如何进入Spring容器当中的,就得继续分析下面的代码了。我们先分析XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);的实现。
从XmlBeanDefinitionReader类名中我们又见到一个新名词BeanDefinition,我们先了解下Spring当中BeanDefinition的作用。
BeanDefinition类似于Resource接口的功能,起到的作用就是对所有的Bean进行一层抽象的统一,把形式各样的对象统一封装成一个便于Spring内部进行协调管理和调度的数据结构,BeanDefinition屏蔽了不同对象对于Spring框架的差异。
XmlBeanDefinitionReader类
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//对Resource参数首先做一层封装处理,主要是为了获得getReader()这个方法的实现,源码在下面↓↓↓↓
return loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
// 调用DefaultResourceLoader的getResources方法完成具体的Resource定位
try {
//从EncodedResource中获取已经封装的Resource对象并再次从Resource中获取inputStream
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
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();
}
}
}
/**
* 1.获取XML文件的验证模式,为正确加载XML做好准备
* 2.从定位好的资源位置处加载XML,对XML进行解析获得Document对象
* 3.返回的Document,在工厂中注册Bean
*/
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 对XML文件进行解析获得Document对象, 这个解析过程由DefaultDocumentLoader完成
Document doc = doLoadDocument(inputSource, resource);
// 启动对BeanDefinition解析的详细过程, 解析过程中会使用到Spring的Bean配置规则
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
//loadDocument直接用于注册Document,getValidationModeForResource方法作用于XML的加载
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
EncodedResource类
//Resource的封装正是为了使用该getReader方法的功能
public Reader getReader() throws IOException {
if (this.charset != null) {
return new InputStreamReader(this.resource.getInputStream(), this.charset);
}
else if (this.encoding != null) {
return new InputStreamReader(this.resource.getInputStream(), this.encoding);
}
else {
return new InputStreamReader(this.resource.getInputStream());
}
}
XML的验证
上面的源码涉及到了对XML加载的过程,而对于一个XML的加载可以将其分为三步
1.读取XML文件的验证模式,即判读其使用的是DTD还是XSD
2.依据上面的验证模式对XML文件进行验证
3.加载XML文件
上面的过程在源码的体现如下:
XmlBeanDefinitionReader类
public static final int VALIDATION_AUTO = XmlValidationModeDetector.VALIDATION_AUTO;
public static final int VALIDATION_XSD = XmlValidationModeDetector.VALIDATION_XSD;
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = getValidationMode();
//如果手动指定了验证模式则使用指定的验证模式
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
//如果没有指定,则交由Spring进行判断
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
return VALIDATION_XSD;
}
protected int detectValidationMode(Resource resource) {
if (resource.isOpen()) {
throw new BeanDefinitionStoreException(
"Passed-in Resource [" + resource + "] contains an open stream: " +
"cannot determine validation mode automatically. Either pass in a Resource " +
"that is able to create fresh streams, or explicitly specify the validationMode " +
"on your XmlBeanDefinitionReader instance.");
}
InputStream inputStream;
try {
inputStream = resource.getInputStream();
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
"Did you attempt to load directly from a SAX InputSource without specifying the " +
"validationMode on your XmlBeanDefinitionReader instance?", ex);
}
try {
return this.validationModeDetector.detectValidationMode(inputStream); //自动检测在detectValidationMode完成
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
resource + "]: an error occurred whilst reading from the InputStream.", ex);
}
}
XmlValidationModeDetector类
//对XML的验证模式进行枚举
public static final int VALIDATION_NONE = 0;
public static final int VALIDATION_AUTO = 1;
public static final int VALIDATION_DTD = 2;
public static final int VALIDATION_XSD = 3;
private static final String DOCTYPE = "DOCTYPE";
//XML验证模式的自动检测实现方法
public int detectValidationMode(InputStream inputStream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
boolean isDtdValidated = false;
String content;
while ((content = reader.readLine()) != null) {
content = consumeCommentTokens(content);
if (this.inComment || !StringUtils.hasText(content)) {
continue;
}
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
if (hasOpeningTag(content)) {
break;
}
}
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
}
catch (CharConversionException ex) {
return VALIDATION_AUTO;
}
finally {
reader.close();
}
}
private boolean hasDoctype(String content) {
return content.contains(DOCTYPE);
}
private boolean hasOpeningTag(String content) {
if (this.inComment) {
return false;
}
int openTagIndex = content.indexOf('<');
return (openTagIndex > -1 && (content.length() > openTagIndex + 1) &&
Character.isLetter(content.charAt(openTagIndex + 1)));
}
解析XML获得Document对象
通过以上的验证准备,就可以对XML文件进行加载了。而加载的过程XmlBeanDefinitionReader并没有自己去完成,而是由DocumentLoader接口去完成的。跟踪源码我们发现具体的实现是DefaultDocumentLoader.loadDocument(),源码体现如下
XmlBeanDefinitionReader类
//自己并没有实现loadDocment的过程,而是调用documentLoader成员的loadDocument
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
//documentLoader的类型为DefaultDocumentLoader
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
接着看DefaultDocumentLoader中的方法实现
DefaultDocumentLoader类
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
通过这个方法便可以获得XML的Document对象了。
获得BeanDefinition对象
现在我们已经获得XML解析后的Document对象,接下来只要在对该Document结构进行分析便可以知道Bean在XML中是如何定义的,也就能将其转换为BeanDefinition对象。
这个过程调用的是XmlBeanDefinitionReader类的registerBeanDefinitions(Document doc, Resource resource)方法,代码如下:
XmlBeanDefinitionReader类
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
// 具体的解析过程在BeanDefinitionDocumentReader的registerBeanDefinitions方法中完成
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
}
DefaultBeanDefinitionDocumentReader类
public static final String BEAN_ELEMENT = BeanDefinitionParserDelegate.BEAN_ELEMENT;
public static final String NESTED_BEANS_ELEMENT = "beans";
public static final String ALIAS_ELEMENT = "alias";
public static final String ALIAS_ATTRIBUTE = "alias";
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement(); // 获得Document的根元素
doRegisterBeanDefinitions(root);
}
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
preProcessXml(root);
//这里我们看到XmlBeanDefinitionReader对具体元素属性的解析是通过调用parseBeanDefinitions完成的
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele); /
}
}
}
}
else {
delegate.parseCustomElement(root); // 解析自定义元素根节点
}
}
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { // 解析import元素
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // 解析alias元素
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { // 解析bean元素
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // 解析内嵌beans元素, 作为根节点递归解析
doRegisterBeanDefinitions(ele);
}
}
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 具体的解析委托给BeanDefinitionParserDelegate来完成
// BeanDefinitionHolder是BeanDefinition的封装类, 封装了BeanDefinition、Bean的名字和别名, 用它来完成向IoC容器注册.
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
看了上面的代码发现其实最后解析的重任交给了processBeanDefinition这个方法,而这个方法里面的实现过程在BeanDefinitionParserDelegate这个类当中。
BeanDefinition的注册
我们配置的Bean的信息经过解析在Spring内部已经转换为BeanDefinition这种统一的结构,但这些数据还不能供IoC容器直接使用,需要在IoC容器中对这些BeanDefinition数据进行注册。
注册过程调用的是DefaultListableBeanFactory的registerBeanDefinition方法