简化版Spring
Github上有个Spring功能简化的项目:https://github.com/code4craft/tiny-spring ,简单包含了Spring的IoC以及AOP功能。本文简单介绍了它的IoC实现,AOP以后有空再说。
tiny-spring
是为了学习Spring的而开发的,可以认为是一个Spring的精简版。Spring的代码很多,层次复杂,阅读起来费劲。我尝试从使用功能的角度出发,参考Spring的实现,一步一步构建,最终完成一个精简版的Spring。有人把程序员与画家做比较,画家有门基本功叫临摹,tiny-spring可以算是一个程序的临摹版本-从自己的需求出发,进行程序设计,同时对著名项目进行参考。
下面开始进入tiny-spring的项目构建。
从XML中读取Bean信息
从XML中读取信息自然需要XML文件的输入流InputStream
,因此我们对InputStream
流的获取进行约束:
public interface Resource {
InputStream getInputStream() throws IOException;
}
增加一个子接口ResourceLoader
继承Resource
接口:
public interface ResourceLoader extends Resource {
Resource getResource(String location);
}
最后,使用实现类获取指定位置文件的InputStream
:
public class UrlResourceLoader implements ResourceLoader {
private URL url;
@Override
public InputStream getInputStream() throws IOException{
URLConnection urlConnection = url.openConnection();
urlConnection.connect();
return urlConnection.getInputStream();
}
@Override
public Resource getResource(String location) {
URL resource = this.getClass().getClassLoader().getResource(location);
url = resource;
return this;
}
}
测试看看:
@Test
public void test() throws IOException {
ResourceLoader resourceLoader = new UrlResourceLoader();
Resource resource = resourceLoader.getResource("tinyioc.xml");
InputStream inputStream = resource.getInputStream();
Assert.assertNotNull(inputStream);
}
可以看到,获取指定XML文件的输入流可以通过测试。
文件读取没有问题后,剩下的就是解析以及保存读取的内容了。
BeanDefinition
Spring使用BeanDefinition
接口定义作为Bean实例和XML配置文件之间的中间层。
为简便起见,tiny-spring
将BeanDefinition
定义为一个类:
public class BeanDefinition {
private Object bean;
private Class beanClass;
private String beanClassName;
private PropertyValues propertyValues = new PropertyValues();
public BeanDefinition() {
}
//Getter、Setter
}
PropertyValues
属性则封装了从XML中读取的属性:
public class PropertyValues {
private final List<PropertyValue> propertyValueList = new ArrayList<PropertyValue>();
public PropertyValues() {
}
public void addPropertyValue(PropertyValue pv) {
//TODO:这里可以对于重复propertyName进行判断,直接用list没法做到
this.propertyValueList.add(pv);
}
public List<PropertyValue> getPropertyValues() {
return this.propertyValueList;
}
}
其实就是key-value:
public class PropertyValue {
private final String name;
private final Object value;
public PropertyValue(String name, Object value) {
this.name = name;
this.value = value;
}
//Getter、Setter
}
上面说过如何从指定文件获取InputStream
。
UrlResourceLoader
只是提供了获取InputStream
的方法,保存从XML中读取转化出的BeanDefinition
还需要一个类进行保存,tiny-spring
为从XML中加载BeanDefinition
安排了接口BeanDefinitionReader
:
public interface BeanDefinitionReader {
void loadBeanDefinitions(String location) throws Exception;
}
从指定文件中读取配置信息。它的实现类AbstractBeanDefinitionReader
则扩充了该接口的功能,同时保存从XML中读取出的BeanDefinition
:
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader{
private Map<String,BeanDefinition> registry;
private ResourceLoader resourceLoader;
protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
this.registry = new HashMap<String, BeanDefinition>();
this.resourceLoader = resourceLoader;
}
//Getter、Setter
}
这里的registry我觉得可以与后续BeanFactory的beanDefinitionMap整合在一起,不需要将
BeanDefinition
的集合存两个地方。
registry属性将BeanDefinition
的name
作为key, BeanDefinition
自身作为value保存在Map
中。
最后,由XmlBeanDefinitionReader
实现具体的方法:
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
super(resourceLoader);
}
@Override
public void loadBeanDefinitions(String location) throws Exception {
InputStream inputStream = getResourceLoader().getResource(location).getInputStream();
doLoadBeanDefinitions(inputStream);
}
}
接下来就很明显了,doLoadBeanDefinitions需要对XML文件做详细的处理,包括将属性名以及属性值构建为PropertyValues
、获取一个Bean对另一个Bean的引用等。
说到这Bean引用,当前的工具类不能解决一个Bean对另一个Bean的引用,所以这里还需要引入一个代表类引用的类BeanReference
:
public class BeanReference {
private String name;
private Object bean;
//Getter、Setter
}
这样,tiny-spring
就可以完成XML配置到BeanDefinition的转换了。
BeanFactory
众所周知,BeanFactory
是Spring的核心容器接口。如果是借助BeanFactory
操作Bean的话,用的最多的一个方法就是getBean()
。
比如:
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
即从Spring容器中获取名为"helloWorldService"的Bean实例。
如果想要模仿Spring的话,一个BeanFactory
接口必不可少,它代表着Spring对外暴露的接口:
public interface BeanFactory {
Object getBean(String name) throws Exception;
}
有了BeanFactory
自然需要实现类,比如保存实例化后的Bean集合。考虑到对于XML的处理已经由XmlBeanDefinitionReader
等类完成,并已经生成了BeanDefinition
集合。因此,BeanFactory
及其实现类的重点在于完成PropertyValue
的赋值以及Bean的实例化,包括处理依赖注入等。
getBean()
方法无疑是最重要的一个方法,不妨借助抽象类AbstractBeanFactory
对其做一个初步的实现:
@Override
public Object getBean(String name) throws Exception {
// 尝试从缓存中获取BeanDefinition(BeanDefinition中维护了Bean的实例)
BeanDefinition beanDefinition = beanDefinitionMap.get(name);
if (beanDefinition == null) {
throw new IllegalArgumentException("No bean named " + name + " is defined");
}
Object bean = beanDefinition.getBean();
if (bean == null) {
// 实例化Bean
bean = doCreateBean(beanDefinition);
beanDefinition.setBean(bean);
}
return bean;
}
实例化Bean方法:
protected Object doCreateBean(BeanDefinition beanDefinition) throws Exception {
Object bean = createBeanInstance(beanDefinition);
beanDefinition.setBean(bean);
applyPropertyValues(bean, beanDefinition);
return bean;
}
protected Object createBeanInstance(BeanDefinition beanDefinition) throws Exception {
return beanDefinition.getBeanClass().newInstance();
}
/**
* 空方法,留给子类实现
* @param bean
* @param beanDefinition
* @throws Exception
*/
protected void applyPropertyValues(Object bean, BeanDefinition beanDefinition) throws Exception {
}
applyPropertyValues类似于模板设计模式,即确定步骤的执行顺序,但某些步骤的具体实现还未知。
来一个子类AutowireCapableBeanFactory
实现该方法:
public class AutowireCapableBeanFactory extends AbstractBeanFactory {
protected void applyPropertyValues(Object bean, BeanDefinition mbd) throws Exception {
for (PropertyValue propertyValue : mbd.getPropertyValues().getPropertyValues()) {
Object value = propertyValue.getValue();
// 处理Bean引用
if (value instanceof BeanReference) {
BeanReference beanReference = (BeanReference) value;
value = getBean(beanReference.getName());
}
try {
// 执行类的set方法
Method declaredMethod = bean.getClass().getDeclaredMethod(
"set" + propertyValue.getName().substring(0, 1).toUpperCase()
+ propertyValue.getName().substring(1), value.getClass());
declaredMethod.setAccessible(true);
declaredMethod.invoke(bean, value);
} catch (NoSuchMethodException e) {
Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName());
declaredField.setAccessible(true);
declaredField.set(bean, value);
}
}
}
}
测试
// 1.读取配置
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new UrlResourceLoader());
xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");
// 2.初始化BeanFactory并注册bean
AbstractBeanFactory beanFactory = new AutowireCapableBeanFactory();
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {
beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
}
// 3.获取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
Assert.assertNotNull(helloWorldService);
没问题,详细代码可以访问tiny-spring
项目获取。
小结
Spring源码实在庞大,新手很难一下子适应,尤其是它错综复杂的继承以及实现关系,看多了头疼。
tiny-spring
构建了简单的IoC以及AOP实现,像咱这样的新手看看还挺不错。
网友评论