DefaultListableBeanFactory
XmlBeanFactory继承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现,而对于XmlBeanFactory与DefaultListableBeanFactory不同的地方其实是XmlBeanFactory中使用了自定义读取器XmlBeanDefinitionReader,实现了个性化的BeanDefinitionReader读取,DefaultListableBeanFactory继承了AbstractAutowireCapableBeanFactory并实现了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口。
- AliasRegistry : 定义对alias的简单增删改等操作
public interface AliasRegistry { //给定名称,为其注册一个别名。如果别名已在使用中可能不会被覆盖,throws IllegalStateException void registerAlias(String name, String alias); // 从此注册表中删除指定的别名。 如果未找到此类别名,则抛出IllegalStateException void removeAlias(String alias); // 确定此给定名称是否定义为别名 boolean isAlias(String name); //返回给定名称的别名(如果已定义),返回别名,如果没有别名则为空数组 String[] getAliases(String name); }
- SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口AliasRegistry进行实现
在注册别名时需要考虑以下几点
- 如果beanA的alias是beanA,则将beanA从aliasMap中移除。
- 如果beanA的别名己经注册,则不需要再注册。
- 如果beanA的别名是a ,此时aliasMap中己经有a别名指向了beanB,则需判断是否配置了allowAliasOverriding属性,如果不允许覆盖,则抛出IllegalStateException异常。
- 别名循环依赖抛出IllegalStateException,如别名a指向beanA,别名beanB指向a,别名beanA又指向beanB,则抛出Circular reference…异常。
获取别名数组需要注意
- getAliases()返回给定名称的别名(如果已定义),这个有点抽象哦,来举个例子,如a别名指向beanA,b别名指向a别名,c别名指向b别名 或 a别名指向beanA,b别名指向a别名,c别名指向a别名,返回beanA的别名数组都为[“a”,“b”,“c”]
- SingletonBeanRegistry : 定义对单例的注册及获取
public interface SingletonBeanRegistry { /** * 以指定的名字将给定Object注册到BeanFactory中。 * 给定的Object必须是被完全初始化了的,此注册接口不会提供任何用以初始化的回调函数,需要特意提及的 * 一点是InitializingBean的afterPropertiesSet方法也不会被此注册接口调用,并且,指定实例 * 也不会收到destroy的信息. * 如果此接口的实现类是一个BeanFactory,最好将你的类注册成BeanDefinition而不是直接使用对象 * 注册,前一种注册方式的好处在于,它可以使你定义的Bean收到initialization和destruction回调。 * 此方法通常在配置和注册Bean期间调用,但是也不排斥在运行时动态的向其中注册单例对象。因此实现类 * 应该同步代码对单例Bean的访问方法 */ void registerSingleton(String beanName, Object singletonObject); /** * 以Object的形式返回指定名字的Bean。仅仅返回已经初始化完成的Bean,对于还没有初始化的 * BeanDefinition不予以考虑 * 此方法的主要目的是提供一种手动获取已注册单例Bean的方式,同样的对于通过BeanDefinition注册的 * Bean也可以通过此方式获得 * 但是要注意,此方法并不支持使用别名对Bean进行查找,如果只有别名的话,要先通过BeanFactory的接口 * 获取到Bean对应的全限定名称 */ Object getSingleton(String beanName); /** * Check if this registry contains a singleton instance with the given name. * 检查此实例是否包含指定名字的并且!!!已经初始化完成的单例Bean。 * 此方法的主要目标是提供一种手动检测Bean是否初始化完成的手段。也可用于检测通过BeanDefinition * 定义的Bean是否创建完成。 * 如果要检测BeanFactory中是否包含指定name的BeanDefinition(不管是否初始化完毕),可使用BeanFactory * 的containsBeanDefinition。同时判断containsBeanDefinition和本方法,可以判断BeanFactory是否包含 * 已经初始化完毕的Bean * 而BeanFactory的containsBean方法是用于通用检测一个BeanFactory或者其父上下文是否有 * 一个指定名字的Bean的手段。 * 不支持别名查找 */ boolean containsSingleton(String beanName); String[] getSingletonNames(); int getSingletonCount(); Object getSingletonMutex(); }
- BeanFactory : 定义获取bean及bean的各种属性
public interface BeanFactory { //getBean("userService")获取到的是userService实例, //如果想获取userService的工厂类,则需getBean("&userService") String FACTORY_BEAN_PREFIX = "&"; // 返回指定bean的实例,该实例可以是共享的,也可以是独立的。 // 此方法允许使用Spring BeanFactory替代 Singleton或Prototype // 设计模式。对于Singleton Bean,调用者可以保留对返回对象的引用。 // 将别名转换回相应的规范bean名称。 将询问父工厂是否在该工厂实例中找不到该bean。 // 如果无法获得bean,则throws BeansException Object getBean(String name) throws BeansException; //需要注意的是requiredType必需是bean的本身或超类或bean必需实现了requiredType接口,否则会抛出ClassCastException异常 <T> T getBean(String name, Class requiredType) throws BeansException; //返回与给定对象类型唯一匹配的bean实例(如果有)。 //requiredType bean必须匹配的类型;可以是接口或超类。 禁止使用null参数。 //此方法进入ListableBeanFactory按类型查找区域,但也可以根据给定类型的名称 //转换为常规的按名称查找。要在各组Bean之间进行更广泛的检索操作, //请使用ListableBeanFactory和/或BeanFactoryUtils。 //返回与所需类型匹配的单个bean的实例,如果未找到给定类型的bean,则抛出NoSuchBeanDefinitionException //如果找到多个给定类型的bean,则抛出NoUniqueBeanDefinitionException //从3.0开始*请参阅ListableBeanFactory <T> T getBean(Class<T> requiredType) throws BeansException; //名称要检索的bean的名称@param使用显式参数创建bean实例时使用的args参数 //仅在创建新实例而不是检索现有实例时才应用 Object getBean(String name, Object... args) throws BeansException; //@param args使用显式参数创建bean实例时使用的参数 //(仅在创建新实例而不是检索现有实例时才应用),返回bean的实例 // 如果没有这样的bean定义,throws NoSuchBeanDefinitionException <T> T getBean(Class<T> requiredType, Object... args) throws BeansException; // 此bean工厂是否包含具有给定名称的bean定义或外部注册的singleton 实例? //如果给定名称是别名,它将被转换回相应的规范bean名称。 //如果该工厂是子工厂,则将询问任何父工厂,如果在该工厂实例中找不到该bean。 //如果找到与给定名称匹配的bean定义或单例实例,则无论命名的bean定义是具体的还是抽象的, //惰性的,此方法都将返回 true。因此,请注意,此方法的true返回值不一定表示getBean //将能够获取具有相同名称的实例。 name要查询的bean的名称 boolean containsBean(String name); //判断一个bean是不是单例 boolean isSingleton(String name) throws NoSuchBeanDefinitionException; //判断一个bean是不是多例 boolean isPrototype(String name) throws NoSuchBeanDefinitionException; //检查具有给定名称的Bean是否与指定的类型匹配 boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException; //检查具有给定名称的Bean是否与指定的类型匹配 boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException; //确定具有给定名称的bean的类型 Class<?> getType(String name) throws NoSuchBeanDefinitionException; //返回给定bean名称的别名 String[] getAliases(String name); }
getBean(String name, Object… args),仅在创建新实例而不是检索现有实例时才应用,这句话什么意思呢?先来看一个例子
public class Car { private String color ; public Car(){ } public Car(String color){ this.color = color; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } } @Test public void test1_1() throws Exception { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:spring_1_100/config_1_10/spring1_1.xml"); // 此时获取到容器帮我们创建的无参 bean Car car1 = (Car) ctx.getBean("car", "red"); System.out.println(JSON.toJSONString(car1)); DefaultSingletonBeanRegistry beanDefReg = (DefaultSingletonBeanRegistry) ctx.getBeanFactory(); //DefaultSingletonBeanRegistry获取中的removeSingleton方法,将car从容器中移除 Method method = DefaultSingletonBeanRegistry.class.getDeclaredMethod("removeSingleton", String.class); System.out.println(method.getName()); method.setAccessible(true); method.invoke(beanDefReg, new Object[]{"car"}); // 容器中bean己经被删除,因此调用getBean,将调用doCreateBean方法,调用有参构造方法实例Car对象 Car car2 = (Car) ctx.getBean("car", "red"); System.out.println(JSON.toJSONString(car2)); }
执行结果如下:
现在终于理解了仅在创建新实例而不是检索现有实例时才应用这句话的意思了,之前我也觉得纳闷,无论传什么参数,获取到的bean都一样。
- DefaultSingletonBeanRegistry : 对接口SingletonBeanRegistry各函数的实现,在这个类中有一个非常重要的属性singletonObjects,所有的单例bean都存储于这个属性中,因此才能对bean进行增删改查操作。
- HierarchicalBeanFactory : 继承BeanFactory,也就是在BeanFactory定义的功能的基础上增加了对parentFactory支持。
public interface HierarchicalBeanFactory extends BeanFactory { //返回父bean工厂,如果没有,则返回null BeanFactory getParentBeanFactory(); //返回本地bean工厂是否包含给定名称的bean,忽略在祖先上下文中定义的bean。 //这是containsBean的替代方法,它忽略了祖先bean工厂中具有给定名称的bean boolean containsLocalBean(String name); }
- BeanDefinitionRegistry : 定义了对BeanDefinition的各种增删改操作
public interface BeanDefinitionRegistry extends AliasRegistry { // 使用此注册表注册新的bean定义。 必须支持RootBeanDefinition和ChildBeanDefinition。 // beanName要注册的bean实例的名称 // bean要注册的bean实例的定义,如果BeanDefinition无效, // 或者如果指定的bean名称已经有BeanDefinition,则抛出BeanDefinitionStoreException void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException; //删除给定名称的BeanDefinition。 beanName要注册的bean实例的名称, //如果没有这样的bean定义,则抛出NoSuchBeanDefinitionException void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException; // 返回给定bean名称的BeanDefinition。beanName Bean的名称,以查找其定义 // 返回给定名称的BeanDefinition,肯定不会返回null,如果没有这样的bean定义, // 则抛出NoSuchBeanDefinitionException BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException; //检查此注册表是否包含具有给定名称的Bean定义 boolean containsBeanDefinition(String beanName); //返回此注册表中定义的所有bean的名称,如果没有,则返回一个空数组 String[] getBeanDefinitionNames(); //返回在注册表中定义的bean数 int getBeanDefinitionCount(); //确定给定的bean名称是否已在此注册表中使用,即是否在此名称下注册了本地bean或别名 boolean isBeanNameInUse(String beanName); }
- FactoryBeanRegistrySupport : 在DefaultSingletonBeanRegistry基础上增加了对FactoryBean的特殊处理。
- ConfigurableBeanFactory : 提供了配置Factory的各种方法。
public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry { //标准单例作用域的作用域标识符:“ singleton” String SCOPE_SINGLETON = "singleton"; //标准多例标识符 String SCOPE_PROTOTYPE = "prototype"; //设置此bean工厂的父级。请注意,父级不能更改:如果在工厂实例化时不可用,则只能在构造函数外部设置。 //抛出如果此工厂已经与父BeanFactory关联,再设置,则抛出IllegalStateException异常 void setParentBeanFactory(BeanFactory parentBeanFactory) throws IllegalStateException; //设置用于加载Bean类的类加载器。 默认为线程上下文类加载器 void setBeanClassLoader(ClassLoader beanClassLoader); //返回此工厂的类加载器以加载Bean类 ClassLoader getBeanClassLoader(); // 指定用于类型匹配目的的临时ClassLoader。 默认为无,只需使用标准bean ClassLoader。 // 如果涉及到加载时织入,通常仅指定一个临时的ClassLoader,以确保尽可能延迟地加载实际的bean类。 // 一旦BeanFactory完成其引导阶段,便将临时加载器删除。 void setTempClassLoader(ClassLoader tempClassLoader); //返回临时ClassLoader以用于类型匹配,如果有的话 ClassLoader getTempClassLoader(); //设置是否缓存bean元数据,例如给定的bean定义(以合并方式)和已解析的bean类。默认为开。 //关闭此标志以启用对bean定义对象(特别是bean类)的热刷新。如果关闭此标志,则任何bean //实例的创建都将重新查询bean类加载器以获取新解析的类。 void setCacheBeanMetadata(boolean cacheBeanMetadata); //返回是否缓存Bean元数据,例如给定的Bean定义(以合并方式)和已解析的Bean类。 boolean isCacheBeanMetadata(); //为bean定义值中的表达式指定解析策略。 默认情况下,BeanFactory中不支持任何表达式支持。 //ApplicationContext通常会在此处设置标准的表达式策略,以统一EL兼容样式支持“#{...}”表达式。 void setBeanExpressionResolver(BeanExpressionResolver resolver); //返回bean定义值中表达式的解析策略。 BeanExpressionResolver getBeanExpressionResolver(); //指定一个Spring 3.0 ConversionService用于转换属性值,以替代JavaBeans PropertyEditors void setConversionService(ConversionService conversionService); //返回关联的ConversionService(如果有) ConversionService getConversionService(); //添加一个PropertyEditorRegistrar应用于所有bean创建过程。 //这样的注册服务商会创建新的PropertyEditor实例,并在给定的注册表中注册它们, //对于每次创建bean的尝试都是新鲜的。这避免了在自定义编辑器上进行同步的需要; //因此,通常最好使用此方法代替 void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar); //为给定类型的所有属性注册给定的定制属性编辑器 void registerCustomEditor(Class<?> requiredType, Class<? extends PropertyEditor> propertyEditorClass); //使用已在此BeanFactory中注册的自定义编辑器初始化给定的PropertyEditorRegistry。 void copyRegisteredEditorsTo(PropertyEditorRegistry registry); //设置此BeanFactory用于转换bean属性值,构造函数参数值等的自定义类型转换器。 //这将覆盖默认的PropertyEditor机制,因此使任何自定义编辑器或自定义编辑器注册器均不相关。 void setTypeConverter(TypeConverter typeConverter); //获得此BeanFactory使用的类型转换器。对于每个调用,这可能是一个新的 //实例,因为TypeConverters通常不是线程安全的。 如果默认的PropertyEditor机制处于活动状态, //则返回的TypeConverter将知道已注册的所有自定义编辑器。 自2.5开始 TypeConverter getTypeConverter(); void addEmbeddedValueResolver(StringValueResolver valueResolver); String resolveEmbeddedValue(String value); //添加一个新的BeanPostProcessor,它将应用于该工厂创建的bean。在出厂配置期间调用。 //注意:此处提交的后处理器将按照注册的顺序应用;通过实现 org.springframework.core.Ordered接口表示的任何排序语义都将被忽略。 //请注意自动检测到的后处理器(例如,作为ApplicationContext中的bean)将始终以编程方式注册后的处理器。 void addBeanPostProcessor(BeanPostProcessor beanPostProcessor); //返回已注册的BeanPostProcessor的当前数量 int getBeanPostProcessorCount(); void registerScope(String scopeName, Scope scope); String[] getRegisteredScopeNames(); Scope getRegisteredScope(String scopeName); //提供与此工厂有关的安全访问控制上下文。 AccessControlContext getAccessControlContext(); //从给定的其他工厂复制所有相关配置。 应包括所有标准配置设置以及BeanPostProcessor, //范围和工厂特定的内部设置。 不应包含任何实际bean定义的元数据,例如BeanDefinition对象和bean名称别名。 void copyConfigurationFrom(ConfigurableBeanFactory otherFactory); //给定一个bean名称,创建一个别名。我们通常使用此方法支持XML ID(用于bean名称)中非法的名称。 //通常在出厂配置期间调用,但也可以用于别名的运行时注册。因此,factory 实现应同步别名访问。 void registerAlias(String beanName, String alias) throws BeanDefinitionStoreException; //解析所有别名目标名称和在此工厂中注册的别名,将给定的StringValueResolver应用于它们。 //例如,值解析器可以解析目标bean名称甚至别名中的占位符 void resolveAliases(StringValueResolver valueResolver); //返回给定bean名称的合并BeanDefinition,如有必要,将子bean定义与其父级合并。 也考虑祖先工厂中的bean定义。 //beanName要检索给定bean的合并定义的bean的名称,返回给定bean的(可能合并的)BeanDefinition //如果没有给定名称的bean定义,抛出NoSuchBeanDefinitionException BeanDefinition getMergedBeanDefinition(String beanName) throws NoSuchBeanDefinitionException; //确定具有给定名称的Bean是否为FactoryBean。如果没有给定名称的bean,则抛出NoSuchBeanDefinitionException boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException; //明确控制指定bean的当前增量状态。 仅供容器内部使用。 //beanName Bean的名称 //inCreation是否当前正在创建该bean void setCurrentlyInCreation(String beanName, boolean inCreation); //确定当前是否正在创建指定的bean boolean isCurrentlyInCreation(String beanName); //为给定的bean注册一个依赖bean,在给定的bean被销毁之前被销毁。 void registerDependentBean(String beanName, String dependentBeanName); //返回依赖于指定bean的所有bean的名称(如果有),如果没有,则返回一个空数组 String[] getDependentBeans(String beanName); //返回指定bean所依赖的所有bean的名称(如果有),如果没有,返回空数组 String[] getDependenciesForBean(String beanName); //根据其bean定义销毁给定的bean实例(通常是从该工厂获得的原型实例)。 //在破坏过程中发生的任何异常都应该被捕获并记录下来,而不是传播到此方法的调用者。 void destroyBean(String beanName, Object beanInstance); //销毁当前目标作用域中的指定作用域bean(如果有)。 //在破坏过程中发生的任何异常都应该被捕获*并记录下来,而不是传播到此方法的调用者。 void destroyScopedBean(String beanName); //销毁该工厂中的所有单例bean,包括已被注册为一次性的bean。在工厂关闭时被调用。 //在破坏过程中发生的任何异常都应该被捕获并记录下来,而不是传播到此方法的调用者。 void destroySingletons(); }
- ListableBeanFactory : 根据各种条件获取Bean的配置清单。
public interface ListableBeanFactory extends BeanFactory { //检查此bean工厂是否包含具有给定名称的bean定义。 //不考虑该工厂可能参与的任何层次结构,并且忽略通过除bean定义以外的其他方式注册的任何单例bean。 boolean containsBeanDefinition(String beanName); //返回工厂定义的bean数。 不考虑该工厂可能参与的任何层次结构,并且忽略通过 //除bean定义以外的其他方式注册的任何单例bean int getBeanDefinitionCount(); //返回此工厂中定义的所有bean的名称。 不考虑该工厂可能参与的任何层次结构, //并且忽略通过除bean定义以外的其他方式注册的任何单例bean。 String[] getBeanDefinitionNames(); //返回与给定类型(包括子类)匹配的bean的名称,根据bean定义或 getObjectType的值(对于FactoryBeans)进行判断。 //注意:此方法仅自检顶级bean。它不会不检查也可能与指定类型匹配的嵌套bean。 //是否考虑由FactoryBeans创建的对象,这意味着将初始化FactoryBeans 。如果由FactoryBean创建的对象不匹配,则原始FactoryBean本身将与该类型匹配。 //不考虑该工厂可能参与的任何层次结构。使用BeanFactoryUtils的beanNamesForType In includedAncestors 也将Bean包括在祖先工厂中。 //注意:不是会忽略通过bean定义以外的其他方式注册的单例bean。 //此版本的 getBeanNamesForType可以匹配所有类型的bean,无论是单例,原型还是FactoryBeans。 //在大多数实现中,结果与getBeanNamesForType(type,true,true)的结果相同。 //此方法返回的 Bean名称应始终尽可能在后端配置中按定义的顺序返回Bean名称 // param输入要匹配的类或接口,或者为所有bean名称输入null //返回与给定对象类型(包括子类)匹配的bean(或由FactoryBeans创建的对象)的名称或空数组如果没有 String[] getBeanNamesForType(ResolvableType type); //返回与给定类型(包括子类)匹配的bean的名称 String[] getBeanNamesForType(Class<?> type); //返回与给定类型(包括子类)匹配的bean的名称 String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit); //根据FactoryBeans的bean定义或 getObjectType的值判断,返回与给定对象类型(包括子类)匹配的bean实例。 <T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException; <T> Map<String, T> getBeansOfType(Class<T> type, boolean includeNonSingletons, boolean allowEagerInit) throws BeansException; //查找其具有提供的注解类型的bean的所有名称,而无需创建任何bean实例 String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType); //查找其具有提供的注解类型的所有bean,返回带有相应bean实例的bean名称的映射。 Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) throws BeansException; //在指定的bean上找到注解,如果本类找不到,遍历其接口和超类(如果在给定类本身上找不到注解) <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType) throws NoSuchBeanDefinitionException; }
-
AbstractBeanFactory : 综合FactoryBeanRegistrySupport 和 ConfigurableBeanFactory 的功能。
-
AutowireCapableBeanFactory : 提供了创建bean , 自动注入,初始化以及应用bean的后各自器
-
AbstractAutowireCapableBeanFactory : 综合AbstractBeanFactory 并对接口AutowireCapableBeanFactory的实现
public interface AutowireCapableBeanFactory extends BeanFactory { int AUTOWIRE_NO = 0; //按名称自动装配bean属性 int AUTOWIRE_BY_NAME = 1; //按类型自动装配bean属性 int AUTOWIRE_BY_TYPE = 2; //指示自动装配可以满足的最贪婪的构造函数的常数(涉及解析适当的构造函数) int AUTOWIRE_CONSTRUCTOR = 3; @Deprecated int AUTOWIRE_AUTODETECT = 4; //完全创建给定类的新bean实例。执行Bean的完全初始化,包括所有适用的BeanPostProcessors。 //注意:这用于创建一个新实例,填充带注解的字段和方法,以及应用所有标准的bean初始化回调。 //不暗示属性的传统按名称或按类型自动装配; 为此使用createBean(Class,int,boolean)。 // beanClass要创建的bean的类,返回新的bean实例,如果实例化或连接失败,则抛出BeansException <T> T createBean(Class<T> beanClass) throws BeansException; //通过应用实例化后的回调和bean属性后处理(例如,用于注解驱动的注入)来填充给定的bean实例。 //注意:这本质上是用于(重新)填充带注释的字段和方法,无论是用于新实例还是反序列化实例。 //不是暗含传统的按名称或按类型自动装配属性; existingBean现有的bean实例 //如果注入失败,则抛出BeansException void autowireBean(Object existingBean) throws BeansException; //配置给定的原始bean:自动装配bean属性,应用bean属性值,应用工厂回调 //(例如 setBeanName和 setBeanFactory,以及应用所有bean后处理器(包括那些可能会包装给定的bean)。 //这实际上是initializeBean提供的超集,完全应用了由相应bean定义指定的配置。 //注意:此方法需要给定名称的bean定义! // existingBean现有的bean实例 // beanName bean的名称,必要时将传递给它 //必须返回该名称的定义返回要,使用的原始或包装的bean实例 Object configureBean(Object existingBean, String beanName) throws BeansException; //解决针对此工厂中定义的bean的指定依赖项。 // descriptor 描述符依赖项的描述符 // beanName声明当前依赖项的bean的名称,返回解析的对象, //如果找不到则返回 null ,如果依赖关系解析失败,throws BeansException Object resolveDependency(DependencyDescriptor descriptor, String beanName) throws BeansException; //使用指定的自动装配策略完全创建给定类的新bean实例。这里支持此接口中定义的所有常量。 //执行Bean的完全初始化,包括所有适用的BeanPostProcessors。这实际上是autowire提供的功能的超集, //添加了initializeBean行为。 //@param beanClass要创建的bean的类 //@param autowireMode使用此接口中的常量通过名称或类型 //@param dependencyCheck是否对对象执行依赖检查(不适用于自动装配构造函数,因此在此处被忽略) //@返回新的bean实例 //如果实例化或连接失败,则throws BeansException Object createBean(Class<?> beanClass, int autowireMode, boolean dependencyCheck) throws BeansException; //使用指定的自动装配策略实例化给定类的新bean实例。这里支持此接口中定义的所有常量。 //也可以使用 AUTOWIRE_NO进行调用,以便仅应用实例化之前的回调(例如,用于注释驱动的注入)。 Object autowire(Class<?> beanClass, int autowireMode, boolean dependencyCheck) throws BeansException; //按名称或类型自动装配给定bean实例的bean属性 void autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck) throws BeansException; //将具有给定名称的bean定义的属性值应用于给定的bean实例。 Bean定义可以定义一个完全自包含的Bean, //重新使用其属性值,或者仅定义要用于现有Bean实例的属性值。 void applyBeanPropertyValues(Object existingBean, String beanName) throws BeansException; //初始化给定的原始bean,应用工厂回调 Object initializeBean(Object existingBean, String beanName) throws BeansException; //将BeanPostProcessors应用于给定的现有bean实例,调用其postProcessBeforeInitialization方法。 //返回的bean实例可能是原始实例的包装。 Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException; //将 BeanPostProcessors应用于给定的现有bean 实例,调用其postProcessAfterInitialization方法。 //返回的bean实例可能是原始实例的包装。 //@param existingBean是新的bean实例 //@param beanName Bean的名称 //@返回要使用的bean实例,可能是原始实例也可能是包装的实例 //如果发生任何后期处理失败,则抛出BeansException Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException; //销毁给定的bean实例(通常来自createBean),应用 org.springframework.beans.factory.DisposableBean合同以及 //已注册的 DestructionAwareBeanPostProcessor DestructionAwareBeanPostProcessors}。 //在破坏过程中发生的任何异常都应该被捕获*并记录下来,而不是传播到此方法的调用者 void destroyBean(Object existingBean); //解决针对此工厂中定义的bean的指定依赖项。 //@param描述符是依赖项的描述符 //@param beanName是声明当前依赖项的bean的名称 //@param autowiredBeanNames一个应该将所有自动绑定bean的名称(用于解决当前依赖项)的集合 //@param typeConverter用于转换数组和集合的TypeConverter //@返回已解析的对象,如果找不到则返回 null //如果依赖关系解析失败 throws BeansException Object resolveDependency(DependencyDescriptor descriptor, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException; }
- ConfigurableListableBeanFactory : BeanFactory配置清单,指定忽略类型及接口等。
public interface ConfigurableListableBeanFactory extends ListableBeanFactory, AutowireCapableBeanFactory, ConfigurableBeanFactory { //忽略给定的依赖类型进行自动装配:例如,字符串。默认为无。 void ignoreDependencyType(Class<?> type); //忽略给定的依赖关系接口以进行自动装配。 通常由应用程序上下文用来注册以其他方式解析的依赖项, //例如通过BeanFactoryAware的BeanFactory或通过ApplicationContextAware的ApplicationContext。 //默认情况下,仅BeanFactoryAware接口被忽略。 要忽略其他类型,请为每种类型调用此方法。 void ignoreDependencyInterface(Class<?> ifc); void registerResolvableDependency(Class<?> dependencyType, Object autowiredValue); //确定指定的bean是否符合自动装配候选条件,注入到声明匹配类型依赖项的其他bean中。 此方法还检查祖先工厂。 //@param beanName要检查的bean的名称 //@param描述符要解析的依赖项的描述符 //@return是否应将bean视为自动装配候选 //@如果没有给定名称的bean,则抛出NoSuchBeanDefinitionException boolean isAutowireCandidate(String beanName, DependencyDescriptor descriptor) throws NoSuchBeanDefinitionException; //返回指定Bean的注册BeanDefinition,从而允许访问其属性值和构造函数参数值(可以在Bean工厂后处理期间进行修改)。 //返回的BeanDefinition对象不应是副本,而应是工厂中注册的原始定义对象。这意味着如有必要,应将其强制转换为更具体的实现类型。 //注意:此方法不考虑祖先工厂。 用于访问该工厂的本地bean定义。 //@param beanName bean的名称 //如果工厂中没有给定名称的bean ,则抛出NoSuchBeanDefinitionException BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException; //返回对此工厂管理的所有bean名称的统一视图。 Iterator<String> getBeanNamesIterator(); //清除合并的Bean定义缓存,删除Bean的条目尚不适合完全元数据缓存的Bean,通常在更改原始bean定义后触发 void clearMetadataCache(); //冻结所有bean定义,表示已注册的bean定义不会再被修改或后处理。 这允许工厂积极地缓存bean定义元数据。 void freezeConfiguration(); //回是否冻结了该工厂的Bean定义,即不应再对其进行修改或后处理 boolean isConfigurationFrozen(); //确保所有非延迟初始化单例都实例化,如果需要,通常在出厂设置结束时调用。 //如果无法创建一个单例bean将throws BeansException。 注意:这可能已经离开工厂, //并且已经初始化了一些bean! 在这种情况下,请调用 destroySingletons() //进行全面清理。 void preInstantiateSingletons() throws BeansException; }
-
DefaultListableBeanFactory : 综合上面的所有的功能,主要是对bean注册后处理。
-
XmlBeanFactory : 对DefaultListableBeanFactory类进行了扩展,主要用于从XML文档中读取BeanDefinition,对于注册及获取bean都是从父类DefaultListableBeanFactory继承的方法来实现,而唯一与父类不同的个性化实现就是增加了XmlBeanDefinitionReader类型的reader属性,在XmlBeanFactory中主要使用reader属性对资源文件进行读取和注册。
public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); //使用给定的资源创建一个新的XmlBeanFactory 必须使用DOM可以解析。 public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); } }
2. XmlBeanDefinitionReader
XML配置文件的读取是Spring中重要的功能,因为Spring的大部分功能都是以配置作为切入点的,那么我们可以从XMLBeanDefinitionReader中梳理一下资源文件读取,解析及注册的大致脉络,首先我们看看各个类的功能。
- ResourceLoader : 定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource
public interface ResourceLoader { String CLASSPATH_URL_PREFIX = "classpath:"; //返回指定资源的资源句柄。句柄应该始终是可重用的资源描述符,允许多个getInputStream()调用。必须支持标准网址, // 例如“文件:C:/test.dat”。 必须支持类路径伪URL,例如“ classpath:test.dat”。 //应支持相对文件路径,例如“ WEB-INF / test.dat”。 *(这是特定于实现的,通常由ApplicationContext实现提供。) //请注意,资源句柄并不意味着现有资源; 您需要调用 Resource 的exists()方法来检查是否存在。 Resource getResource(String location); //公开此ResourceLoader使用的ClassLoader。 需要直接访问ClassLoader的客户端可以使用ResourceLoader以统一的方式进行操作, //而不是依赖于线程上下文ClassLoader。即使系统无法访问,也只能返回null ClassLoader getClassLoader(); }
- BeanDefinitionReader:主要定义资源文件读取并转换为BeanDefinition的各个功能。
public interface BeanDefinitionReader { //返回注册Bean定义工厂。工厂通过BeanDefinitionRegistry接口公开,封装了与bean定义处理相关的方法。 BeanDefinitionRegistry getRegistry(); //返回资源加载器以用于资源位置。 可以检查 ResourcePatternResolver 接口,并进行相应的 //转换,以针对给定的资源模式加载多个资源。 Null表示此bean定义阅读器不支持绝对资源加载。 //这主要是用于从bean定义资源中导入其他资源,例如通过XML bean定义中的“ import” 标签。 //但是,建议相对于定义资源应用此类进口;只有明确的完整资源位置才会触发绝对资源加载。 //还有一个 loadBeanDefinitions(String)方法可用,用于从资源位置(或位置模式)加载bean定义。 //这是避免显式ResourceLoader处理的便利 ResourceLoader getResourceLoader(); //返回用于Bean类的类加载器。建议不要急于加载Bean类而是只注册具有类名的Bean定义。 ClassLoader getBeanClassLoader(); //返回BeanNameGenerator用于匿名bean (未指定显式bean名称)。 BeanNameGenerator getBeanNameGenerator(); //从指定资源加载bean定义。 int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException; //从指定的资源加载bean定义。 int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException; //从指定的资源位置加载bean定义。该bean定义阅读器的ResourceLoader是ResourcePatternResolver,该位置也可以是位置模式。 int loadBeanDefinitions(String location) throws BeanDefinitionStoreException; //从指定的资源位置加载bean定义。 int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException; }
- EnvironmentCapable : 主要定义获取Environment方法
public interface EnvironmentCapable { Environment getEnvironment(); }
- DocumentLoader : 定义从资源文件加载到转换为Document的功能。
public interface DocumentLoader { //从提供的 InputSource来源加载文档。 //@param inputSource将要加载的文档的源 //@param entityResolver用于解析任何实体的解析器 //@param errorHandler用于在文档加载期间报告任何错误 //@param validateMode验证的类型 //org.springframework.util.xml.XmlValidationModeDetector#VALIDATION_DTD DTD或 //org.springframework.util.xml.XmlValidationModeDetector#VALIDATION_XSD XSD}) //@param namespaceAware 如果 true 支持XML名称空间将被提供 Document loadDocument( InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception; }
-
AbstractBeanDefinitionReader : 对EnvironmentCapable,BeanDefinitionReader类定义的功能进行实现。
-
BeanDefinitionDocumentReader : 定义读取Document并注册BeanDefinition功能。
public interface BeanDefinitionDocumentReader { //从给定的DOM文档中读取bean定义,并在给定的阅读器上下文中向注册表注册它们。 //@param doc DOM文档 //@param readerContext阅读器的当前上下文(包括目标注册表和正在解析的资源) void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) throws BeanDefinitionStoreException; }
- BeanDefinitionParserDelegate:定义解析Element的各种方法。
经过以上的分析,我们可以梳理出整个XML配置文件读取的大致流程,如下图所示,XmlBeanDefinitionReader主要包含以下的几个流程。
- 通过继承自AbstractBeanDefinitionReader中的方法,来使用ResourceLoader将资源文件路径转换为对应的Resource文件
- 通过继承自DocumentLoader对Resource文件进行转换,将Resource文件转换成Document文件。
- 通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析,并使用BeanDefinitionParserDelegate对Element进行解析。
2.5 容器的基础XmlBeanFactory
好了,到这里我们己经对Spring容器的功能有了大致的了解了,尽管你可能还很迷糊,但是不要紧,接下来我们继续探索每一个步骤,再次重申一下代码,我们接下来要深入研究以下功能代码。
BeanFactory bf = new XmlBeanFactory(new ClassPathResource(“beanFactoryTest.xml”));
通过XmlBeanFactory初始化时序图,我们来看看上面的代码执行逻辑。
时序图从beanFactoryTest测试类开始,通过时序图我们可以一目了然地看到整个逻辑处理顺序,在测试的BeanFactoryTest中首先调用ClassPathResource的构造函数来构造Resource资源文件的实例对象,这样后续的资源处理就可以用Resource提供各种服务来操作了,当我们有了Resource后就可以进行XmlBeanFactory的初始化了,那么resource资源是如何封装的呢?
2.5.1配置文件封装
Spring的配置文件读取通过ClassPathResource进行封装,如new ClassPathResource(“beanFactory.xml”)那么ClassPathResource完成了什么功能呢?
在java中,将不同的来源的资源抽象成URL,通过注册不同的handler(URLStreamHandler)来处理不同来源的资源读取逻辑,一般handler的类型使用不同的前缀(协义,Protocol)来识别,如"file",“http:”,"jar"等,然而URL没有默认的定义相对Classpath或ServletContext等资源handler,虽然可以注册自己的URLStreamHandler来解析特定的URL前缀(协义),比如检查当前资源是否存在,检查当前资源是否可读等方法,因而Spring对其内部使用到的资源实现了自己的抽象,Resource接口封装底层资源。
public interface InputStreamSource { InputStream getInputStream() throws IOException ; } public interface Resource extends InputStreamSource{ boolean exists(); boolean isReadable(); boolean isOpen(); URL getURL() throws IOException; URL getURI() throws IOException; File getFile() throws IOException; long lastModified() throws IOException; Resource createRelative(String relativePath) throws IOException; String getFilename(); String getDescription(); }
InputStreamSource封装任何能返回InputStream的类,比如File,Classpath下的资源和ByteArray等,它只有一个方法定义:getInputStream() ,该方法返回一个新的InputStream对象。
Resource接口抽象了所有的Spring内部使用到的底层资源,File,URL,Classpath等,首先它定义了3个判断当前资源状态的方法,存在性(exists),可读性(isReadable),是否处于打开状态(isOpen),另外,Resource接口还提供了不同资源到URL,URI,FILE类型的转换,以及获取 lastModified属性,文件名(不带路径信息的文件名,getFilename())的方法,为了便于操作,Resource还提供了基于当前资源创建一个相对资源的方法,createRelative()。在错误处理中需要详细的打印出错的资源文件,因而Resource还提供了getDescription()方法用来在错误处理中打印信息。
对于不同来源的资源文件有相应的Resource实现:文件(FileSystemResource),Classpath资源(ClassPathResource),URL资源(UrlResource),InputStream资源(InputStreamResource),Byte数组(ByteArrayReSource)等,相关的类图如下图所示。
在日常开发中,资源文件时可以使用以下代码:
Resource resource = new ClasspathResource(“beanFactoryTest.xml”);
InputStream inputStream = resource.getInputStream();
得到inputStream后,我们就可以按照以前的开发方式进行实现了,并且我们可以利用Resource及其子类为我们提供诸多我特性。
有了Resource接口便可以对所有的资源文件进行统一处理,至于实现,其实是非常简单的,以getInputStream为例,ClassPathResource中实现方式便是通过classs或者classLoader提供的底层方法进行调用,而对于FileSystemResource的实现其实更简单,直接使用FileInputStream对文件进行实例化。
ClassPathReSource.java
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; }
XmlBeanFactory.java
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
构造函数再次调用内部的构造函数:
// prentBeanFactory为父类BeanFactory用于factory合并,可以为空
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
上面的函数中的代码this.reader.loadBeanDefinitions(resource)才是资源加载的真正的实现,也是我们重点分析之一,我们可以看到时序图中反映到的XmlBeanDefinitionReader加载数据就是在这里完成的,但是XmlBeanDefinitionReader加载数据前还有一个调用父类构造函数初始化的过程:super(parentBeanFactory),跟踪代码到父类
AbstractAutowireCapableBeanFactory.java
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
这里有必要提及ignoreDependencyInterface方法,ignoreDependencyInterface的主要功能是忽略给定接口的自动装配功能,那么,这样做的目的是什么呢?会产生什么样的效果呢?
举例来说,当A中有属性B,那么当Spring在获取A的Bean的时候如果其属性B还没有初始化,那么Spring自动初始化B,这也是Spring提供的一个重要特性,但是在某些特殊情况下,B不会被初始化,其中的一种情况就是B实现了BeanNameAware接口,Spring中是这样介绍的,自动装配忽略给定的依赖接口,典型的应用就是通过其他的方式解析Application上下文注册依赖,类似于BeanFactory通过BeanFactoryAware进行注入或者ApplicationContext通过ApplicationContextAware来进行注入。
2.5.2 加载Bean
之前提到在XmlBeanFactory构造函数中调用了XmlBeanDefinitionReader类型的reader属性提供的方法this.reader.loadBeanDefinitions(resource),而这句代码则是整个资源加载的切入点,我们先来看看这个方法的时序图,如图所示。
看到上面的图我们才知道山路十八弯,绕了这么半天还没有真正的切入正题,比如加载XML文档和解析注册Bean,一直还没有做准备工作,我们根据上面的时序图来分析一下这里究竟在准备什么?从上面的时序图中我们尝试梳理整个处理过程如下:
- 封装资源文件,当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodedResource类进行封装。
- 获取输入流,从Resource中获取对应的InputStream并构造InputSource 。
- 通过构造InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions。
我们来看一下loadBeanDefinitions函数具体的实现过程。
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); }
那么EncodeResource的作用是什么呢?通过名称,我们可以大致的推断这个类主要用于对资源文件的编码处理的,其中的主要逻辑体现在getReader()方法中,当设置了编码属性的时候Spring会使用相应的编码作为输入流编码。
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());
}
}
上面代码构造了一个编码(encoding)的InputStreamReader,当构造好encodedResource对象后,再次转入了可复用方法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 currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
//从encodedResource中获取己经封装的Resource对象并再次从Resource中获取其实的inputStream
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//InputSource这个类并不来自于Spring,它的全路径是org.xml.sax.InputSource
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();
}
}
}
我们再次整理数据准备阶段的逻辑,首先通过传入的resource参数做封装,目的是考虑到Resource可能存在编码要求的情况,其次,通过SAX读取XML文件的方式来准备InputSource对象,最后将准备的数据通过参数传入真正的核心处理部分
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
int num = getValidationModeForResource(resource);
Document doc =this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
num, isNamespaceAware());
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);
}
}
在上面的冗长的代码中假如不考虑异常类的代码,其实只做了三件事,这三件事的每一件都必不可少。
- 获取对XML文件的验证模式。
- 加载XML文件,并得到对应的Document。
- 根据返回的Document注册Bean信息。
第三个步骤支撑整个Spring容器的实现,尤其是第三个对配置文件的解析,逻辑非常的复杂,我们先从获取XML文件的验证模式中讲起。
2.6 获取XML的验证模式
了解XML文件读者都应该知道XML文件的验证模式保证了XML文件的正确性,而比较常用的验证模式有:DTD和XSD,它们之间有什么区别呢?
2.6.1 DTD与XSD区别
DTD(Document Type Definition)即文档类型定义,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分,DTD是一种保证XML文档格式正确的有效方法,可以通过比较XML文档和DTD文件来看文档是否符合规范,元素和标签使用是否正确,一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实例或符号规则 。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http://www.Springframework.org/dtd/Springbean2.0.dtd">
<beans>
......
</beans>
XML Schema语言就是XSD(XML Schemas Definition),XML Schema 描述了XML文档的结构,可以用一个指定的XML Schema来验证某个XML文档,以检查该XML文档是否符合要求,文档设计者可以通过XML Schema指定XML文档所允许的结构和内容,并可以根据此检查XML文档是否有效的,XML Schema本身是XML文档,它符合XML语法结构,可以通过的XML解析器解析它。
在使用XML Schema文档对XML实例文档进行检验,除了要声明空间外(xmlns=http://www.Springframework.org/schema/beans),还必需指定该名称空间所对应的XML Schema文档在存储位置,通过schemaLocation属性来指定名称空间所对应的XML Schema文档的存储位置,它包含两个部分,一个部分是名称空间的URL,另一个部分就是该名称空间所标识的XML Schema文件位置或URL地址(xsi:schemaLocation=“http://www.springframework.org/schema/bean http://www.Springframework.org/schema/bean-bean.xsd”)。
<?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.xsd">
Spring-bean-4.2.xsd 部分代码如下
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <xsd:schema xmlns="http://www.springframework.org/schema/beans" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.springframework.org/schema/beans"> <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/> <xsd:annotation> <xsd:documentation><![CDATA[ Spring XML Beans Schema, version 4.2 ... ]]></xsd:documentation> </xsd:annotation> <!-- base types --> <xsd:complexType name="identifiedType" abstract="true"> <xsd:annotation> <xsd:documentation><![CDATA[ The unique identifier for a bean. The scope of the identifier is the enclosing bean factory. ]]></xsd:documentation> </xsd:annotation> <xsd:attribute name="id" type="xsd:string"> <xsd:annotation> <xsd:documentation><![CDATA[ The unique identifier for a bean. A bean id may not be used more than once within the same <beans> element. ]]></xsd:documentation> </xsd:annotation> </xsd:attribute> </xsd:complexType> <!-- Top-level <beans> tag --> .... <!-- simple internal types --> <xsd:simpleType name="defaultable-boolean"> <xsd:restriction base="xsd:NMTOKEN"> <xsd:enumeration value="default"/> <xsd:enumeration value="true"/> <xsd:enumeration value="false"/> </xsd:restriction> </xsd:simpleType> </xsd:schema>
我们是可以简单的介绍一下XML文件的验证模式相关的知识,目的就是让读者对后续的知识理解有连续性,如果对XML有兴趣的读者可以进一步的查阅相关的资料。
2.6.2 验证模式的读取
了解了DTD与XSD的区别后我们再去分析Spring中对于验证模式的提取就更加容易理解了,通过之前的分析我们锁定了Spring通过getValidationModeForResource方法来获取双塘不的资源的验证模式。
protected int getValidationModeForResource(Resource resource) { //如果指定了验证模式则使用指定的验证模式 int validationModeToUse = getValidationMode(); if (validationModeToUse != VALIDATION_AUTO) { return validationModeToUse; } //如果未指定则使用自动检测 int detectedMode =detectValidationMode(resource); if (detectedMode != VALIDATION_AUTO) { return detectedMode; } return VALIDATION_XSD; }
方法的实现其实还是非常的简单的,无非是如果设定了验证模式则使用设定的验证模式(可以通过对调用XmlBeanDefinitionReader中的setValidationMode方法进行设定),否则使用自动检测的方式,而自动检测验证模式的功能是在函数detectValidationMode方法中实现的,在detectValidationMode函数中又将自动检测验证模式的工作委托给了专门的处理类XmlValidationModeDetector,调用了XmlValidationModeDetector的ValidationModeDetector方法,具体的代码如下:
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); } catch (IOException ex) { throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + resource + "]: an error occurred whilst reading from the InputStream.", ex); } }
XmlValidationModeDetector.java
public int detectValidationMode(InputStream inputStream) throws IOException { // Peek into the file to look for DOCTYPE. 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; } //如果包含xml中DOCTYPE行,则是Dtd文档 if (hasDoctype(content)) { isDtdValidated = true; break; } //读取到<开始符号,验证模式一定会在开始符号之前 if (hasOpeningTag(content)) { // End of meaningful data... break; } } return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD); } catch (CharConversionException ex) { // Choked on some character encoding... // Leave the decision up to the caller. return VALIDATION_AUTO; } finally { reader.close(); } }
只要我们理解了XSD与DTD的使用方法,理解上面的代码应该不会太难,Spring用来检测验证模式的办法是判断是否包含DOCTYPE,如果包含就是DTD文档,否则就是XSD文档。
2.7 获取Document
经过了验证模式准备的步骤就可以进行Document加载了,同样XmlBeanFactoryReader类对于文档读取并没有亲力亲为,而是委托给了DocumentLoader去执行,这里DocumentLoader这个接口,而真正的调用是在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); }
对于这部分代码其实并没有太多的可描述的,因为通过SAX解析XML文档的套路大致都差不多,Spring在这里并没有什么特殊的地方,同样是首先创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析InputSource来返回Document对象,对此感兴趣的读者可以在网上获取更多的资料,这里有必要提及一下EntityResolver,对于参数entityResolver,传入的是通过getEntityResolver()函数来获取返回值的,如下代码:
protected EntityResolver getEntityResolver() { if (this.entityResolver == null) { // Determine default EntityResolver to use. ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader != null) { this.entityResolver = new ResourceEntityResolver(resourceLoader); } else { this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader()); } } return this.entityResolver; }
那么EntityResolver到底是做什么用的呢?
2.7.1 EntityResolver用法
在loadDocument方法中涉及一个参数EntityResolver,何为EntityResolver?官网这样解释:如果SAX应用程序需要实现一个自定义处理外部实体,则必需实现此接口并使用setEntityResolver方法向SAX驱动器注册一个实例,也就是说,对于解析一个XML,SAX首先读取XML文档上的声明,根据声明去寻找相应的DTD定义,以便文档进行一个验证,默认的寻找规则,即通过网络(实现上就是声明DTD的URI地址)来下载相应的DTD声明,并进行认证,下载的过程是一个漫长的过程,而且当网络中断或不可用时,这里会报错,就是因为相应的DTD声明没有被找到的原因。
EntityResolver的作用是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,比如我们将DTD文件放到项目某处,在实现时直接将此文档读取并返回给SAX即可,这样就避免了通过网络寻找相应的声明。
首先我们来看看entityResolver的接口方法声明。
InputSource resolverEntity(String publicId,String systemId) ;
这里,它接收的两个参数publicId和SystemId ,并返回一个InputResource对象,这里我们特定配置文件来进行讲解。
- 如果我们解析验证模式为XSD的配置文件,代码如下:
<?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.xsd">
读取到以下的两个参数:
- publicId:null
- systemId:http://www.springframework.org/schema/beans/spring-beans.xsd
如果我们在解析验证DTD的配置文件,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http://www.Springframework.org/dtd/Springbean2.0.dtd">
<beans>
......
</beans>
- publicId:-//Spring//DTD BEAN 2.0//EN
- systemId:http://www.Springframework.org/dtd/Springbean2.0.dtd
之前己经提到过,验证文件默认的加载方式是通过URL进行网络下载获取,这样会造成延迟,用户体验也不好,一般的做法都是验证文件放置在自己的工程里,那么怎样做才能将这个URL转换为自己工程里对就的地址呢?我们以加载DTD文件为例来看看Spring中是如何实现的,根据之前Spring中通过getEntiryResolver()方法来对EntityResolver的获取,我们知道,Spring中使用DelegatingEntityResolver类为EntityResolver的实现类,resolveEntiry实现方法如下:
DelegatingEntityResolver.java
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { if (systemId != null) { if (systemId.endsWith(DTD_SUFFIX)) { //如果是dtd从这里解析 return this.dtdResolver.resolveEntity(publicId, systemId); } else if (systemId.endsWith(XSD_SUFFIX)) { return //通过调用META-INF/Spring.schemas解析 this.schemaResolver.resolveEntity(publicId, systemId); } } return null; }
我们可以看到对于不同的验证模式,Spring使用了不同的解析器解析,这里简单的描述一下原理,比如加载DTD类型的BeansDtdResolver的resolveEntity是直接截取SystemId最后的xx.dtd然后去当前路径下寻找,而加载XSD类型的PluggableSchemaResolver类的resolveEntiry是默认到META-INF/Spring.schemas文件中找到systemId所对应的XSD文件并加载。
BeansDtdResolver.java
public InputSource resolveEntity(String publicId, String systemId) throws IOException { if (logger.isTraceEnabled()) { logger.trace("Trying to resolve XML entity with public ID [" + publicId + "] and system ID [" + systemId + "]"); } if (systemId != null && systemId.endsWith(DTD_EXTENSION)) { int lastPathSeparator = systemId.lastIndexOf("/"); int dtdNameStart = systemId.indexOf(DTD_NAME); if (dtdNameStart > lastPathSeparator) { String dtdFile = DTD_FILENAME + DTD_EXTENSION; if (logger.isTraceEnabled()) { logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath"); } try { Resource resource = new ClassPathResource(dtdFile, getClass()); InputSource source = new InputSource(resource.getInputStream()); source.setPublicId(publicId); source.setSystemId(systemId); if (logger.isDebugEnabled()) { logger.debug("Found beans DTD [" + systemId + "] in classpath: " + dtdFile); } return source; } catch (IOException ex) { if (logger.isDebugEnabled()) { logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex); } } } } // Use the default behavior -> download from website or wherever. return null; }
2.8 解析及注册BeanDefinitions
当把文件转换为Document后,接下来的提取注册bean就是我们的重头戏,继续上面的分析,当程序己经拥有XML文档文件的Document实例对象时,就会引入下面的这个方法。
XmlBeanDefinitionReader.java
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
其中参数doc是通过上一节loadDocument加载转换出来的,这个方法中很好的应用了面向对象中单一职责原则,将逻辑处理委托给单一的类进行处理,而这个逻辑处理类就是BeanDefinitionDocumentReader,BeanDefinitionDocumentReader是一个接口,而实例化的工作就是在createBeanDefinitionDocumentReader()中完成的,而通过此方法,BeanDefinitionDocumentReader真正的类型其实己经是DefaultBeanDefinitionDocumentReader了,进入DefaultBeanDefinitionDocumentReader后,发现这个方法的的重要上的之一就是提取root,以便于将root作为参数继续BeanDefinition的注册。
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); }
经过了艰难险阻,磕磕绊绊, 我们终于到了核心逻辑的底部,如果说以前一直是XML加载解析的准备阶段,那么doRegisterBeanDefinitions算是真正的开始进行解析了,我们期待的核心部分真正的开始了。
protected void doRegisterBeanDefinitions(Element root) { //专门处理解析 BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) { //处理profile属性 String profileSpec = root.getAttribute("profile"); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, ",; "); if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { return; } } } //解析前处理,留给子类实现 preProcessXml(root); parseBeanDefinitions(root, this.delegate); //解析后处理,留给子类实现 postProcessXml(root); this.delegate = parent; }
通过上面的代码我们可以看到了处理流程,首先是对profile的处理,然后开始进行解析,可是当我们跟进preProcessXml(root)或者postProcessXml(root)发现代码是空的,既然是空的写着还有什么意思呢?就像面向对象设计方法学习中常说的一句话,一个类要么是面向继承设计的,要么就是final修饰的,在DefaultBeanDefinitionDocumentReader中并没有用final来修饰,所以它是面向继承而设计的,这两个方法正是为子类设计的,如果读者有了了解过设计模式,可以很快速的反映出这个模版方法模式,如果继承自DefaultBeanDefinitionDocumentReader的子类需要在Bean解析前后做一些处理的话,那么只需要重写这个两个方法就可以了。
2.8.1 profile属性的使用
我们注意到在注册Bean最开始是对PROFILE_ATTRIBUTE属性的解析,可能对于我们来说,profile属性并不是很常用,让我们先了解一下这个属性。
分析profile前我们先了解一下profile的用法,官方示例代码如下所示:
<?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.xsd"> <beans profile="dev"> ... ... </beans> <beans profile="production"> ... ... </beans> </beans>
集成到Web环境中时,在web.xml中加入以下代码。
<context-param> <param-name>spring.profiles.active</param-name> <param-value>dev</param-value> </context-param>
有了这个特性我们就可以同时配置文件部署两套配置来适用于生产环境和开发环境,这样就可以方便的进行切换开发,部署环境,最常用的就是更换不同的数据库。
了解profile的使用再来分析代码会清晰很多,首先程序会获取beans节点是否定义了profile属性,如果定义了则需要到环境变量中去寻找,所以这里首先断言environment不可能为空,因为profile是可以同时指定多个的,需要程序对其拆分,并解析每个profile是都符合环境变量中所定义的,不定义则不会浪费性能去解析。
2.8.2 解析并注册BeanDefinition
处理了profile后就可以进行XML的读取了,跟踪代码进入parseBeanDefinition(root,this.delegate)。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { //对beans进行处理 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)) { //对bean的处理 parseDefaultElement(ele, delegate); } else { //对bean处理 delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
上面代码看起来逻辑还是蛮清晰的,因为在Spring的XML配置里面有两大类Bean的声明,一个是默认的,如
<bean id = “test” class = “test.TestBean”>
另一类是自定义的,如:
<tx:annotation-driven/>
而两种方式读取及解析差别非常大的,如果采用Spring默认的配置,Spring当然知道怎样做,但是如果是自定义的,那么就需要用户实现一些接口及配置了,对于根节点或者子节点如果是默认的命名空间的话,则采用parseDefaultElement方法进行解析,否则使用delegate.parseCustomElment方法对自定义命名空间进行解析,而判断是否默认命名空间还是自定义命名空间的办法其实是使用node.getNamespaceUrI()获取命名空间,并与Spring中固定的命名空间http://www.springframework.org/schema/beans进行比对,如果一致则认为中是默认的,否则就认为是自定义,而对于默认解析标签与自定义标签解析我们将后面的博客再来分析。