spring container的设计与实现分析
简介
在之前的一些学习过程当中,对spring框架的一些设计思路和基本实现做了一些了解。结合书本的描述和自己的一些分析做一个初步的分析。这里是针对最早期spring框架实现的一个雏形进行的分析,和现在最新版本的比起来虽然简陋很多,但是少了很多纷繁芜杂的细节,对于我们理解它的设计思想更加方便一些。
spring框架之前
在使用spring框架之前,我们如果要开发一些应用,并使得它们能够很好工作的话,需要考虑和使用到比较多的地方就是应用组件的配置以及组合。比如说,我们怎么配置一个类,使得它能够被其他的类使用起来而不至于太麻烦。同时,根据应用的需要,一些对应的应用的配置该用什么样的方式来处理?是使用properties文件还是使用xml文件?不管使用哪一种文件,有没有一种统一的方式来读取和解析配置文件呢?我们希望有一个框架来解决这个问题而使得自己更多的集中在系统业务逻辑上面。
除了上述的问题,还有各种零零散散的问题,这里就不再赘述了。总的来说,怎么样构造出一个松散耦合的系统,使得他们易于配置和组合是一个值得深入思考的问题。在spring的设计思路里,就直接使用了JavaBean的思路。
JavaBean
首先一个,什么是JavaBean呢?这好像是一个很不起眼但又有点模糊的概念。从JavaBean官方的文档说明来看,它首先是一个标准。对于一个JavaBean来说,它有这么几个属性:
1. 它是一个可序列化的对象(通常指实现serializable接口,能够正确的实现序列化和反序列化)。
2. 它所有的属性是私有的,所有对这些属性的访问都是通过get/set方法来访问。
3. 它有默认的无参数构造函数。
从这几点看来,这更像是一个我们日常设计类和编码的一个规范。因为我们大多数的实现也会按照这个规范来。现在回过头来看,为什么要采用这种方式呢?
其实这种方式有一个比较好的地方,对于每个对象自己的属性来说,它不是直接暴露在外面,而是通过给定的的一些约定的方法来访问。这样就正好符合了我们设计思想中的紧内聚和松耦合的意思。从更高粒度的角度来看,如果每一个组件都采用JavaBean的方式来构造的话,我们的软件构造就相当于拼接一块块的组件。这些组件的拼接就取决于这些约定俗成的JavaBean访问方式。
当然,关于JavaBean的这几个特性的描述只是它的基础。在javaee的官方规范上提供了对JavaBean这种组件模型的详细支持,它使得我们可以很方便的将软件组件组合在一起。我们还有必要对这些官方支持的api作一个进一步的了解。
JavaBean相关的API
在深入的去看spring框架的一些详细设计和实现之前,还有一个需要详细了解的就是几个相关的API。在java.beans 这个包里包含了一系列和JavaBean相关特性的API。其中比较常用的几个类如下:
在实际的应用里,我们会使用Introspector来分析Java Bean的方法和属性。Introspector通过几个静态方法getBeanInfo(Class)来获取某个给定的类所对应的元数据信息。这个信息统一包含在BeanInfo类里。BeanInfo里面提供了关于method, property, event等bean相关的基础信息。而且对于这几类信息分别提供了BeanDescriptor, MethodDescriptor, PropertyDescriptor等描述类信息。通过这几个类,我们就能得到给定一个类里的详细信息。通过这种方式相当于得到了一个类的所有元数据信息。这样,不管我们定义的Bean是什么具体类型,我们都可以通过这种方式来获取到它们的基础信息。这几个部分在后续详细实现的分析里会起到很大的作用。
在这里,我们可能会有一个疑问。在什么地方我们会用上这些个api呢?这些api有什么用呢?在前面我们提到过一点,就是spring框架的设计思想里考虑过要采用JavaBean的方式来构造和组合各种对象。如果有使用过spring框架的经验的话可能会更好理解一点。在spring框架里,我们会定义一些类,然后在代码里或者xml文件里将他们关联起来。而这里的关联和耦合就类似于JavaBean里头属性的设置。那么这里就有一个问题,在实际的工程中,我们会定义一系列的类,然后在框架运行的时候会创建对应的对象,可能为单例的,也可能非单例的。那么这意味着我们需要有一个地方来保存这些类的元数据信息,以方便后面创建对象。然后在后面需要建立对象来进行耦合的时候再根据这些信息来构建。在具体的应用里,每个类它的方法和属性都是不一样的,我们如果要构造这个类的对象的话是没法根据每个类的具体情况来构建的,肯定需要一种比较通用的方式来构造。那么这就需要一种统一抽象的方式来构造和记录他们。
在前面的讨论里,我们已经看到通过Introspector得到的Bean的属性,方法和事件等信息。如果把这些信息给组织起来,在后面构建对象的时候就会方便很多的。
三个层面的抽象
在spring框架的设计里,将对象容器设计抽象成3个层面。BeanWrapper, BeanFactory和ApplicationContext。每个层面都是基于前一个层面为基础。这里,我们会根据它们的具体实现来讨论。
BeanWrapper
BeanWrapper这一层主要是用来操作JavaBean对象和属性的。比如说我们在配置文件里定义了一个bean,而且这个bean还有它的若干个暴露出来的bean方法。我们怎么来设置它的值和对应的属性呢?我们怎么来获取它的值和属性呢?在前面的讨论里我们也提到,通过一些JavaBean的api可以拿到一些bean的描述信息,这些信息该怎么组织来方便我们操作和构造bean呢?这些就是我们这一层所需要考虑的。在这一层里,相关的几个类如下图:
我们针对它们的实现详细看过来。
我们一开始最关心的肯定就是怎么来获取和设置属性的值。所以它们最重要的就是要提供一组getProperty, setProperty的方法。在BeanWrapper里面就定义了一系列相关的方法,它本身是一个接口,相关的几个方法如下:
public interface BeanWrapper { /** * Set a property value. This method is provided for convenience only. * The setPropertyValue(PropertyValue) * method is more powerful, providing the ability to set indexed properties etc. * @param propertyName name of the property to set value of * @param value the new value */ void setPropertyValue(String propertyName, Object value) throws PropertyVetoException, BeansException; void setPropertyValue(PropertyValue pv) throws PropertyVetoException, BeansException; /** * Get the value of a property * @param propertyName name of the property to get the value of * @return the value of the property. * @throws FatalBeanException if there is no such property, * if the property isn't readable or if the property getter throws * an exception. */ Object getPropertyValue(String propertyName) throws BeansException; void setPropertyValues(Map m) throws BeansException; void setPropertyValues(PropertyValues pvs) throws BeansException; void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, PropertyValuesValidator pvsValidator) throws BeansException; /** Get the property descriptor for a particular property, or null if there * is no such property * @param propertyName property to check status for * @return the property descriptor for a particular property, or null if there * is no such property */ PropertyDescriptor getPropertyDescriptor(String propertyName) throws BeansException; /** * Return the bean wrapped by this object. * Cannot be null * @return the bean wrapped by this object */ Object getWrappedInstance(); /** * Change the wrapped object. Implementations are required * to allow the type of the wrapped object to change. * @param obj wrapped object that we are manipulating */ void setWrappedInstance(Object obj) throws BeansException; /** * Return the class of the wrapped object * @return the class of the wrapped object */ Class getWrappedClass(); /** * Invoke the named method. This interface is designed * to encourage working with bean properties, rather than methods, * so this method shouldn't be used in most cases, * but it's also useful to provide a simple means to invoking * a named method. * @param methodName name of the method to invoke * @param args args to pass * @return follows java.util.Method.invoke(). Void calls * return null; primitives are wrapped as objects */ Object invoke(String methodName, Object[] args) throws BeansException; }
这里省略了一部分代码。主要的几个方法除了getProperty, setProperty,还有getWrappedInstance, getWrappedClass, invoke等方法。这就相当于,如果我们有一个实现该接口的对象。针对给定的类型,我们就可以通过统一的这几个方法来设置它的属性值或者调用它的方法了。这样,我们就达到了一个类似于反射的效果。
在类里面依赖的两个类PropertyValue和PropertyValues是两个包装类。其中PropertyValue表示我们用来设置property传递的参数,它包含name, value两个部分,表示我们要设置的属性名和值。而PropertyValues则用来构造多个PropertyValue,方便实现多个PropertyValue。
所以,这里实现的重点就是BeanWrapperImpl。它是对BeanWrapper接口的详细实现。
在BeanWrapperImpl里,我们会看到如下几个重要的成员:
/** The wrapped object */ private Object object; private CachedIntrospectionResults cachedIntrospectionResults;
object表示我们wrapper里要包装的对象。用我们这个类包装好的对象就可以通过前面的get/setProperty来访问对象了。而cachedIntrospectionResults则是用来保存前面Introspector分析的类元数据的。
我们先看一下CachedIntrospectionResults里面的结构。
private static HashMap $cache = new HashMap(); //--------------------------------------------------------------------- // Instance data //--------------------------------------------------------------------- private BeanInfo beanInfo; /** Property desciptors keyed by property name */ private HashMap propertyDescriptorMap; /** Property desciptors keyed by property name */ private HashMap methodDescriptorMap;
这是它里面定义的几个成员变量。其中$cache变量是一个static的全局HashMap,它主要保存的是以class为key,CachedIntrospectionResults对象为value的结构。这样,对于每个类型的class来说,它会在全局的cache里保存一份且仅有一份信息。这些正好就是用来描述类元数据的。
而其他的3个类成员变量则分别记录了BeanInfo信息, HashMap<String, PropertyDescriptor>, HashMap<String, MethodDescriptor>信息。那么,它们几个的信息是如何被构造出来的呢?主要取决于如下方法:
private CachedIntrospectionResults(Class clazz) throws BeansException { try { logger.info("Getting BeanInfo for class " + clazz); beanInfo = Introspector.getBeanInfo(clazz); logger.fine("Caching PropertyDescriptors for class " + clazz); propertyDescriptorMap = new HashMap(); // This call is slow so we do it once PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); for (int i = 0; i < pds.length; i++) { logger.fine("Found property [" + pds[i].getName() + "] of type [" + pds[i].getPropertyType() + "]; editor=[" + pds[i].getPropertyEditorClass() + "]"); propertyDescriptorMap.put(pds[i].getName(), pds[i]); } logger.fine("Caching MethodDescriptors for class " + clazz); methodDescriptorMap = new HashMap(); // This call is slow so we do it once MethodDescriptor[] mds = beanInfo.getMethodDescriptors(); for (int i = 0; i < mds.length; i++) { logger.fine("Found method [" + mds[i].getName() + "] of type [" + mds[i].getMethod().getReturnType() + "]"); methodDescriptorMap.put(mds[i].getName(), mds[i]); } } catch (IntrospectionException ex) { throw new FatalBeanException("Cannot get BeanInfo for object of class [" + clazz.getName() + "]", ex); } }
在这里CachedIntrospectionResults通过一个私有的带class类型参数的构造函数来解析传入的class对象。通过获取它的BeanInfo, PropertyDescriptor, MethodDescriptor等信息来构造出这个信息。同时CachedIntrospectionResults里的forClass方法保证这个构造函数里获取的对象会被保存到全局的$cache这个HashMap中来:
public static CachedIntrospectionResults forClass(Class clazz) throws BeansException { Object o = $cache.get(clazz); if (o == null) { try { o = new CachedIntrospectionResults(clazz); } catch (BeansException ex) { o = ex; } $cache.put(clazz, o); } else { logger.finest("Using cached introspection results for class " + clazz); } // o is now an exception or CachedIntrospectionResults // We already have data for this class in the cache if (o instanceof BeansException) throw (BeansException) o; return (CachedIntrospectionResults) o; }
上述代码里前10行就是典型的一个创建对象并将对象加入缓存的操作手法。有了CachedIntrospectionResults提供的解析类元数据的功能之后,BeanWrapperImpl里其他的实现就是基于它来对包装的对象进行属性设置了。
我们先看构造函数相关的部分,在BeanWrapperImpl的构造函数里,他们基本上都调用了一个方法setObject,它的实现如下:
private void setObject(Object object) throws BeansException { if (object == null) throw new FatalBeanException("Cannot set BeanWrapperImpl target to a null object", null); this.object = object; if (cachedIntrospectionResults == null || !cachedIntrospectionResults.getBeanClass().equals(object.getClass())) { cachedIntrospectionResults = CachedIntrospectionResults.forClass(object.getClass()); } setEventPropagationEnabled(this.eventPropagationEnabled); // assert: cachedIntrospectionResults != null }
这部分主要是通过设置对应的对象和解析出对应的CachedIntrospectionResult对象以方便后面使用。需要注意的是,只要我们每调用一次CachedIntrospectionResult的forClass方法,这个对象对应的class元数据就会被解析后放到cache里去了。
我们再来看看setPropertyValue的实现:
public void setPropertyValue(PropertyValue pv) throws PropertyVetoException, BeansException { if (isNestedProperty(pv.getName())) { try { BeanWrapper nestedBw = getBeanWrapperForNestedProperty(this, pv.getName()); nestedBw.setPropertyValue(new PropertyValue(getFinalPath(pv.getName()), pv.getValue())); return; } catch (NullValueInNestedPathException ex) { // Let this through throw ex; } catch (FatalBeanException ex) { // Error in the nested path throw new NotWritablePropertyException(pv.getName(), getWrappedClass()); } } // WHAT ABOUT INDEXED PROEPRTIES!? int pos = pv.getName().indexOf(NESTED_PROPERTY_SEPARATOR); // Handle nested properties recursively if (pos > -1) { String nestedProperty = pv.getName().substring(0, pos); String nestedPath = pv.getName().substring(pos + 1); logger.fine("Navigating to property path '" + nestedPath + "' of nested property '" + nestedProperty + "'"); // Could consider caching these, but they're not that expensive to instantiate BeanWrapper nestedBw = new BeanWrapperImpl(getPropertyValue(nestedProperty), false); nestedBw.setPropertyValue(new PropertyValue(nestedPath, pv.getValue())); return; } if (!isWritableProperty(pv.getName())) throw new NotWritablePropertyException(pv.getName(), getWrappedClass()); PropertyDescriptor pd = getPropertyDescriptor(pv.getName()); Method writeMethod = pd.getWriteMethod(); Method readMethod = pd.getReadMethod(); Object oldValue = null; // May stay null if it's not a readable property PropertyChangeEvent propertyChangeEvent = null; try { if (readMethod != null && eventPropagationEnabled) { // Can only find existing value if it's a readable property try { oldValue = readMethod.invoke(object, new Object[] { }); } catch (Exception ex) { // The getter threw an exception, so we couldn't retrieve the old value. // We're not really interested in any exceptions at this point, // so we merely log the problem and leave oldValue null logger.logp(Level.WARNING, "BeanWrapperImpl", "setPropertyValue", "Failed to invoke getter '" + readMethod.getName() + "' to get old property value before property change: getter probably threw an exception", ex); } } // Old value may still be null propertyChangeEvent = createPropertyChangeEventWithTypeConversionIfNecessary(object, pv.getName(), oldValue, pv.getValue(), pd.getPropertyType()); // May throw PropertyVetoException: if this happens the PropertyChangeSupport // class fires a reversion event, and we jump out of this method, meaning // the change was never actually made if (eventPropagationEnabled) { vetoableChangeSupport.fireVetoableChange(propertyChangeEvent); } // Make the change if (logger.isLoggable(Level.FINEST)) logger.finest("About to invoke write method [" + writeMethod + "] on object of class '" + object.getClass().getName() + "'"); writeMethod.invoke(object, new Object[] { propertyChangeEvent.getNewValue() }); if (logger.isLoggable(Level.FINEST)) logger.finest("Invoked write method [" + writeMethod + "] ok"); // If we get here we've changed the property OK and can broadcast it if (eventPropagationEnabled) propertyChangeSupport.firePropertyChange(propertyChangeEvent); } catch (InvocationTargetException ex) { if (ex.getTargetException() instanceof PropertyVetoException) throw (PropertyVetoException) ex.getTargetException(); if (ex.getTargetException() instanceof ClassCastException) throw new TypeMismatchException(propertyChangeEvent, pd.getPropertyType(), ex); throw new MethodInvocationException(ex.getTargetException(), propertyChangeEvent); } catch (IllegalAccessException ex) { throw new FatalBeanException("illegal attempt to set property [" + pv + "] threw exception", ex); } catch (IllegalArgumentException ex) { throw new TypeMismatchException(propertyChangeEvent, pd.getPropertyType(), ex); } } // setPropertyValue
这部分的代码看起来比较复杂,因为前30行主要是用来处理嵌套的属性以及索引的属性方式访问。 而从第33行到第65行主要是根据readMethod来获取属性旧的值,并在如果允许触发属性变化事件的情况下去触发这个事件。而后面的部分则是通过writeMethod.invoke()方法来真正修改property值。至此,我们会发现,当给定一个对象的属性名和属性值的时候,我们是通过反射调用它的writeMethod来设置它的值的。那么对于给定属性名获取它的属性值呢?这就是getPropertyValue的实现:
public Object getPropertyValue(String propertyName) throws BeansException { if (isNestedProperty(propertyName)) { BeanWrapper nestedBw = getBeanWrapperForNestedProperty(this, propertyName); logger.finest("Final path in nested property value '" + propertyName + "' is '" + getFinalPath(propertyName) + "'"); return nestedBw.getPropertyValue(getFinalPath(propertyName)); } PropertyDescriptor pd = getPropertyDescriptor(propertyName); Method m = pd.getReadMethod(); if (m == null) throw new FatalBeanException("Cannot get scalar property [" + propertyName + "]: not readable", null); try { return m.invoke(object, null); } catch (InvocationTargetException ex) { throw new FatalBeanException("getter for property [" + propertyName + "] threw exception", ex); } catch (IllegalAccessException ex) { throw new FatalBeanException("illegal attempt to get property [" + propertyName + "] threw exception", ex); } }
它的实现就好像是写属性的对称实现一样,它是通过调用readMethod里的方法来获取值的。本质上还是反射。
有了前面的对象封装之后,BeanWrapper接口的getWrappedClass和getWrappedInstance就简单了很多:
public Class getWrappedClass() { return object.getClass(); } public Object getWrappedInstance() { return object; }
在这里还有一个重要的方法就是invoke:
public Object invoke(String methodName, Object[] args) throws BeansException { try { MethodDescriptor md = this.cachedIntrospectionResults.getMethodDescriptor(methodName); if (logger.isLoggable(Level.FINE)) logger.fine("About to invoke method [" + methodName + "]"); Object returnVal = md.getMethod().invoke(this.object, args); if (logger.isLoggable(Level.FINE)) logger.fine("Successfully invoked method [" + methodName + "]"); return returnVal; } catch (InvocationTargetException ex) { //if (ex.getTargetException() instanceof ClassCastException) // throw new TypeMismatchException(propertyChangeEvent, pd.getPropertyType(), ex); // CHECK!!!! throw new MethodInvocationException(ex.getTargetException(), null); } catch (IllegalAccessException ex) { throw new FatalBeanException("Illegal attempt to invoke method [" + methodName + "] threw exception", ex); } catch (IllegalArgumentException ex) { throw new FatalBeanException("Illegal argument to method [" + methodName + "] threw exception", ex); } }
这里的实现也很简单,因为有前面的cache保存好对应的methodDescriptor了,只要找到对应的method,再去调用它就行了。
总的来说,上述的BeanWrapperImpl的实现是通过将目标对象包装起来,在初始化的时候通过 CachedIntrospectionResults解析类元数据并设置缓存。在后续的属性设定里通过找到对ing的元数据信息再调用对应的反射方法就可以了。
有了前面BeanWrapperImpl实现的基础,我们对于对象属性的操作就可以采用另外一种方式了。比如说我们有一个如下的接口:
public interface ITestBean { int getAge(); void setAge(int age); String getName(); void setName(String name); ITestBean getSpouse(); void setSpouse(ITestBean spouse); }
同时有实现这个接口的对象TestBean。我们通过包装这个TestBean对象来操作属性值的方法如下:
TestBean bean = new TestBean(); BeanWrapper bw = new BeanWrapperImpl(bean); bw.setPropertyValue("age", new Integer(32)); bw.setPropertyValue("name", "frank"); bw.setPropertyValue("spouse", new TestBean()); bw.setPropertyValue("spouse.name", "kerry"); bw.setPropertyValue("spouse.spouse", bean); Integer age = (Integer) bw.getPropertyValue("age"); String name = (String) bw.getPropertyValue("name"); String spouseName = (String) bw.getPropertyValue("spouse.name");
BeanFactory
有了前面部分对BeanWrapper的定义和实现,我们得到了一个操作对象和属性的基础。当然,在实际的应用中,光有这个东西还是远远不够的。还有几个点我们需要考虑:
1. 我们希望通过框架的支持来将应用给组合起来。在前面的基础里,如果需要组合各组件的话,还太低级。我们希望能够通过一种统一配置的方式来获取bean。
2. 另外一个,我们也希望这个框架能够提供一种统一的方式来读取配置属性和创建对象。
3. 这些创建的对象对于应用来说是可见的,由于具体应用的复杂性,它可能构成一个复杂的对象图,这些都需要进行管理。我们也需要使得这些创建的每个对象都是唯一的。
正是基于上述的原因,这里定义了更高的一层,来提供一些统一的方式来读取应用配置信息,同时也提供统一的对象创建和管理功能。提到对于对象的创建和管理,这里还有一个需要提到的,就是它和singleton以及factory模式应用的比较。在一些场景下,我们实际上只使用了唯一的一个对象,于是我们可以考虑用singleton或者factory方法的方式来创建它们。但是这种方法存在着一些问题:
1. 我们希望应用更多的是直接面向接口编程,而如果使用singleton或者factory的时候,就直接和具体的类耦合了,如果根据需要对一些实现做调整的话,会比较困难。
2. 每个单例对象可能需要去读取一些配置信息,比如properties文件或者JDBC等。在每个实现里对于配置的读取和管理将散落在各个地方。这既牵扯了一些无关的精力又使得实现更加混乱。好多和业务无关的东西也搅和到里面了。
3. 还有一个问题就是像singleton模式本身不灵活,如果我们需要不仅仅一个对象的时候,在原有的实现里将会很麻烦,需要做大的改动。
基于上述的讨论,spring里对于BeanFactory相关的实现类结构如下图:
我们先从BeanFactory这边看过来,它的实现如下:
public interface BeanFactory { Object getBean(String name) throws BeansException; Object getBean(String name, Class requiredType) throws BeansException; }
后续详细的实现就是围绕着这个接口进行的。有了这些详细的实现,我们后面要定义和使用对象的方式如下:
Mylnterface mi = (Mylnterface) getBean("mylnterface"); Mylnterface mi = (Mylnterface) getBean("mylnterface", Mylnterface.class);
通过这种方式,我们实现了在BeanFactory里实现具体的配置管理,而在应用层面来说,它只需要关心具体的业务逻辑。这样实现了配置管理和应用代码的分离。我们可以在具体的BeanFactory实现里来选择具体的配置读取方式和解析方法。这样也可以支持不同格式的应用配置。
在BeanFactory的实现里,AbstractBeanFactory的实现非常重要,它对getBean方法的实现如下:
public final Object getBean(String name) { BeanDefinition bd = getBeanDefinition(name); return bd.isSingleton() ? getSharedInstance(name) : createBean(name); }这里采用了template method的模式思路,getBeanDefinition的实现在这里是一个抽象方法,具体的实现由它的子类来定义。我们来看看getSharedInstance()和createBean()这两个方法。getSharedInstance()方法的实现如下:
private final synchronized Object getSharedInstance(String name) throws BeansException { Object o = sharedInstanceCache.get(name); if (o == null) { logger.info("Cached shared instance of Singleton bean '" + name + "'"); o = createBean(name); sharedInstanceCache.put(name, o); } else { if (logger.isLoggable(Level.FINE)) logger.fine("Returning cached instance of Singleton bean '" + name + "'"); } return o; }在AbstractBeanFactory里定义了一个HashMap,用来保存bean名字和创建的bean对象:
private HashMap sharedInstanceCache = new HashMap();所以每次我们调用这个方法的时候会首先去这个全局缓存里查找bean的名字以提高bean查找的效率。这个方法的实现也在一定条件下调用createBean()方法。createBean()方法实现如下: