分析完与Spring IoC功能相关的接口之后,接下来我们就要来自定义Spring IoC功能了。
首先,我们先来看一下需求:现要对下面的配置文件进行解析,并自定义Spring框架的IoC功能对涉及到的对象进行管理。
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="userDao" class="com.meimeixia.dao.impl.UserDaoImpl">
<property name="username" value="zhangsan"></property>
<property name="password" value="123456"></property>
</bean>
<bean id="userService" class="com.meimeixia.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
</beans>
要想完成这一需求,可不是件容易的事情,我们得分如下几部分来进行定义。
- 定义bean相关的pojo类。
- 定义注册表相关的类。
- 定义解析器相关的类。
- 定义IoC容器相关的类。
下面,我们先来定义bean相关的pojo类。
定义bean相关的pojo类
首先,我们先使用IDEA来创建一个Maven工程,工程名字你可以取为liayun_spring,然后再来创建相应的包,这里为了让大家清楚地看到我都创建了哪些包,干脆我把最终Maven工程的结构给大家展示出来吧!
PropertyValue类
在这一部分,我们会创建不同的类,第一个类就是PropertyValue,该类的作用就是用来封装bean的属性的。看一下一开始的配置文件,最终我们是要解析该配置文件的,所以严格来说,PropertyValue这个类是用来封装<bean> 标签的<property> 子标签中的属性的。由于<property> 子标签中有name、ref、value等属性,因此PropertyValue类里面至少得有name、ref、value这三个属性。
package com.meimeixia.framework.beans;
public class PropertyValue {
private String name;
private String ref;
private String value;
public PropertyValue() {
}
public PropertyValue(String name, String ref, String value) {
this.name = name;
this.ref = ref;
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRef() {
return ref;
}
public void setRef(String ref) {
this.ref = ref;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
MutablePropertyValues类
创建完PropertyValue类之后,接下来我们再来创建第二个类,那就是MutablePropertyValues。为什么要创建这个类呢?因为一个<bean> 标签可以有多个<property> 子标签,而每一个<property> 子标签都会被封装成一个PropertyValue对象,对于多个PropertyValue对象我们就要进行存储以及管理了,所以在这里我们就要创建一个MutablePropertyValues类,用来存储并管理多个PropertyValue对象了。
注意,在创建该类时,我们需要用到迭代器模式,所以该类得去实现Iterable接口。至于为什么这儿要用到迭代器模式,我不说,相信大家也知道,因为MutablePropertyValues类是用来存储并管理多个PropertyValue对象的,所以它必须是可以迭代的。
package com.meimeixia.framework.beans;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class MutablePropertyValues implements Iterable<PropertyValue> {
private final List<PropertyValue> propertyValueList;
public MutablePropertyValues() {
this.propertyValueList = new ArrayList<PropertyValue>();
}
public MutablePropertyValues(List<PropertyValue> propertyValueList) {
if (propertyValueList == null) {
this.propertyValueList = new ArrayList<PropertyValue>();
} else {
this.propertyValueList = propertyValueList;
}
}
public PropertyValue[] getPropertyValues() {
return propertyValueList.toArray(new PropertyValue[0]);
}
public PropertyValue getPropertyValue(String propertyName) {
for (PropertyValue propertyValue : propertyValueList) {
if (propertyValue.getName().equals(propertyName)) {
return propertyValue;
}
}
return null;
}
public boolean isEmpty() {
return propertyValueList.isEmpty();
}
public MutablePropertyValues addPropertyValue(PropertyValue pv) {
for (int i = 0; i < propertyValueList.size(); i++) {
PropertyValue currentPv = propertyValueList.get(i);
if (currentPv.getName().equals(pv.getName())) {
propertyValueList.set(i, pv);
return this;
}
}
this.propertyValueList.add(pv);
return this;
}
public boolean contains(String propertyName) {
return getPropertyValue(propertyName) != null;
}
@Override
public Iterator<PropertyValue> iterator() {
return propertyValueList.iterator();
}
}
相信大家可以看到,我们在以上MutablePropertyValues类中定义了很多方法,这些方法我也写了一些比较详细的注释,相信大家都能看懂。
BeanDefinition类
接下来,我们再来创建bean相关的pojo类里面的最后一个类,也是最重要的一个类,叫BeanDefinition。其实,之前我们在分析Spring IoC功能的相关接口时,就见过类似这玩意,只不过它是接口,而在这里我们是直接将其定义成类了,主要是为了简单,图省事。
BeanDefinition类主要是用来封装bean信息的,主要包含id(即bean对象的名称)、class(需要交由Spring管理的类的全类名)及子标签<property> 中的数据。
package com.meimeixia.framework.beans;
public class BeanDefinition {
private String id;
private String className;
private MutablePropertyValues propertyValues;
public BeanDefinition() {
this.propertyValues = new MutablePropertyValues();
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public MutablePropertyValues getPropertyValues() {
return propertyValues;
}
public void setPropertyValues(MutablePropertyValues propertyValues) {
this.propertyValues = propertyValues;
}
}
可以看到,虽然这个类特别重要,但是创建起来还是比较简单的。
定义注册表相关的类
BeanDefinitionRegistry接口
定义完bean相关的pojo类之后,接下来我们就要来定义注册表相关的类了。当然,这一部分就不仅仅是有类了,还有接口,说白了,在这一部分,我们会创建一个接口及其子实现类。至于接口的话,我们就命名为BeanDefinitionRegistry了,下面我们就来看一下该接口需要定义哪些功能?
BeanDefinitionRegistry接口应定义注册表的相关操作,所以需要定义如下功能:
- 注册BeanDefinition对象到注册表中。
- 从注册表中删除指定名称的BeanDefinition对象。
- 根据名称从注册表中获取BeanDefinition对象。
- 判断注册表中是否包含指定名称的BeanDefinition对象。
- 获取注册表中BeanDefinition对象的个数。
- 获取注册表中所有的BeanDefinition对象的名称。
以上这些功能,相信大家应该很熟悉,因为之前我们在分析Spring源码里面的BeanDefinitionRegistry接口时就看到过,BeanDefinitionRegistry接口也定义了以上这些功能。
根据以上分析,我们创建出来的BeanDefinitionRegistry接口就应该是下面这个样子的。
package com.meimeixia.framework.beans.factory.support;
import com.meimeixia.framework.beans.BeanDefinition;
public interface BeanDefinitionRegistry {
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);
void removeBeanDefinition(String beanName) throws Exception;
BeanDefinition getBeanDefinition(String beanName) throws Exception;
boolean containsBeanDefinition(String beanName);
int getBeanDefinitionCount();
String[] getBeanDefinitionNames();
}
BeanDefinitionRegistry接口创建完毕之后,接下来我们就要来创建它的子实现类了,注意,这里我们只创建一个子实现类。
SimpleBeanDefinitionRegistry类
我们在创建该类时,要让该类去实现BeanDefinitionRegistry接口,并去重写它里面所有的抽象方法。注意,在该类里面我们还得定义一个Map集合,让其作为注册表容器。
package com.meimeixia.framework.beans.factory.support;
import com.meimeixia.framework.beans.BeanDefinition;
import java.util.HashMap;
import java.util.Map;
public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry {
private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<String, BeanDefinition>();
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
beanDefinitionMap.put(beanName, beanDefinition);
}
@Override
public void removeBeanDefinition(String beanName) throws Exception {
beanDefinitionMap.remove(beanName);
}
@Override
public BeanDefinition getBeanDefinition(String beanName) throws Exception {
return beanDefinitionMap.get(beanName);
}
@Override
public boolean containsBeanDefinition(String beanName) {
return beanDefinitionMap.containsKey(beanName);
}
@Override
public int getBeanDefinitionCount() {
return beanDefinitionMap.size();
}
@Override
public String[] getBeanDefinitionNames() {
return beanDefinitionMap.keySet().toArray(new String[0]);
}
}
定义解析器相关的类
接下来,我们就来定义解析器相关的接口和类。相信大家也都知道了,我们均是参照Spring里面的接口和类来定义的,所以在这一部分我们就将接口命名为BeanDefinitionReader,既然它是一个接口的话,那么它里面定义的便是最基本的功能规范了。还有,我们还得为该接口创建一个子实现类,名字不妨就叫做XmlBeanDefinitionReader。
为什么我们还要创建BeanDefinitionReader接口呢?之前我带着大家分析Spring IoC功能相关的接口时,你也看到了,针对于不同的配置文件,Spring会提供不同的子类来进行解析,例如,解析properties格式的配置文件用的是PropertiesBeanDefinitionReader类,解析XML格式的配置文件用的是XmlBeanDefinitionReader。大家要是能够去看一下BeanDefinitionReader接口的继承体系的话,你会发现这俩类都是其子实现类。当然了,这里我们在自定义Spring IoC功能时,只会针对XML格式的配置文件来创建解析类。
BeanDefinitionReader接口
由于BeanDefinitionReader是用来解析配置文件并在注册表中注册bean的信息的,所以我们应在它里面定义如下两个规范。
- 获取注册表的功能,让外界可以通过该对象获取注册表对象。
- 加载配置文件,并注册bean数据。
根据以上分析,我们创建出来的eanDefinitionReader接口就应该是下面这个样子的。
package com.meimeixia.framework.beans.factory.support;
public interface BeanDefinitionReader {
BeanDefinitionRegistry getRegistry();
void loadBeanDefinitions(String configLocation) throws Exception;
}
XmlBeanDefinitionReader类
BeanDefinitionReader接口创建完毕之后,接下来我们就来创建其子实现类,名字上面我也说了,就叫XmlBeanDefinitionReader。
相信大家也知道了,XmlBeanDefinitionReader类是专门用来解析XML格式的配置文件的,而且创建该类时,不用我说,大家都应该知道该类得实现BeanDefinitionReader接口并去重写它里面的两个功能,如下所示。
package com.meimeixia.framework.beans.factory.xml;
import com.meimeixia.framework.beans.BeanDefinition;
import com.meimeixia.framework.beans.MutablePropertyValues;
import com.meimeixia.framework.beans.PropertyValue;
import com.meimeixia.framework.beans.factory.support.BeanDefinitionReader;
import com.meimeixia.framework.beans.factory.support.BeanDefinitionRegistry;
import com.meimeixia.framework.beans.factory.support.SimpleBeanDefinitionRegistry;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.List;
public class XmlBeanDefinitionReader implements BeanDefinitionReader {
private BeanDefinitionRegistry registry;
public XmlBeanDefinitionReader() {
registry = new SimpleBeanDefinitionRegistry();
}
@Override
public BeanDefinitionRegistry getRegistry() {
return registry;
}
@Override
public void loadBeanDefinitions(String configLocation) throws Exception {
SAXReader reader = new SAXReader();
InputStream is = XmlBeanDefinitionReader.class.getClassLoader().getResourceAsStream(configLocation);
Document document = reader.read(is);
Element rootElement = document.getRootElement();
List<Element> beanElements = rootElement.elements("bean");
for (Element beanElement : beanElements) {
String id = beanElement.attributeValue("id");
String className = beanElement.attributeValue("class");
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setId(id);
beanDefinition.setClassName(className);
MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
List<Element> propertyElements = beanElement.elements("property");
for (Element propertyElement : propertyElements) {
String name = propertyElement.attributeValue("name");
String ref = propertyElement.attributeValue("ref");
String value = propertyElement.attributeValue("value");
PropertyValue propertyValue = new PropertyValue(name, ref, value);
mutablePropertyValues.addPropertyValue(propertyValue);
}
beanDefinition.setPropertyValues(mutablePropertyValues);
registry.registerBeanDefinition(id, beanDefinition);
}
}
}
注意,由于我们要在XmlBeanDefinitionReader类的loadBeanDefinitions方法中使用dom4j进行XML配置文件的解析,所以我们应在工程的pom.xml文件里面导入对应的jar包的坐标,如下所示。
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
定义IoC容器相关的类
定义完解析器相关的接口和类之后,接下来我们来定义IoC容器相关的接口和类。大家要注意了,这一部分是我们自定义Spring IoC功能最核心的部分,而且在这一部分我们需要定义如下这些接口和类。
BeanFactory接口
在该接口中我们需要定义IoC容器的统一规范,即获取bean对象的方法。
package com.meimeixia.framework.beans.factory;
public interface BeanFactory {
Object getBean(String name) throws Exception;
<T> T getBean(String name, Class<? extends T> clazz) throws Exception;
}
ApplicationContext接口
BeanFactory接口创建完毕之后,接下来我们来创建它的一个子接口,这里我们就取名为ApplicationContext了。
之前我带领着大家分析Spring IoC功能相关的接口时,相信大家也知道了ApplicationContext属于非延时加载,也就是说(使用者)在创建容器对象的时候,就会去加载配置文件,并实例化bean对象,最终将其存储在容器里面。
这里,我也不废话了,直接给出ApplicationContext接口的代码,如下所示。
package com.meimeixia.framework.context;
import com.meimeixia.framework.beans.factory.BeanFactory;
public interface ApplicationContext extends BeanFactory {
void refresh() throws Exception;
}
可以看到,我们在创建ApplicationContext接口时,在它里面只定义了一个refresh方法,该方法主要完成以下两个功能。
- 加载配置文件。
- 根据注册表中的BeanDefinition对象封装的数据进行bean对象的创建。
AbstractApplicationContext类
ApplicationContext接口创建完毕之后,接下来我们来创建它的一个子实现类,这里我们就取名为AbstractApplicationContext了。
那么,AbstractApplicationContext类有什么特点以及作用呢?
-
作为ApplicationContext接口的子类,所以该类也是非延时加载,也就是立即加载,这样,我们就需要在该类中定义一个Map集合,以作为bean对象存储的容器。 -
声明BeanDefinitionReader类型的变量,用来进行XML配置文件的解析,这是符合单一职责原则的。也就是说,如果你要去解析配置文件的话,那么就不要在该类里面去自己实现了,而是直接调用解析器的方法进行解析就行了。 当然了,BeanDefinitionReader类型的对象创建应交由子类去实现,因为只有子类明确到底会创建BeanDefinitionReader哪个子实现类对象。
明确了AbstractApplicationContext类具有的以上特点之后,相信大家不难创建出该类,如下所示。
package com.meimeixia.framework.context.support;
import com.meimeixia.framework.beans.factory.support.BeanDefinitionReader;
import com.meimeixia.framework.beans.factory.support.BeanDefinitionRegistry;
import com.meimeixia.framework.context.ApplicationContext;
import java.util.HashMap;
import java.util.Map;
public abstract class AbstractApplicationContext implements ApplicationContext {
protected BeanDefinitionReader beanDefinitionReader;
protected Map<String, Object> singletonObjects = new HashMap<String, Object>();
protected String configLocation;
@Override
public void refresh() throws Exception {
beanDefinitionReader.loadBeanDefinitions(configLocation);
finishBeanInitialization();
}
private void finishBeanInitialization() throws Exception {
BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
String[] beanNames = registry.getBeanDefinitionNames();
for (String beanName : beanNames) {
getBean(beanName);
}
}
}
注意:该类finishBeanInitialization方法中调用getBean方法使用到了模板方法模式。
ClassPathXmlApplicationContext类
接下来,我们来定义IoC容器接口的具体子实现类,这个子实现类的名字不妨就取名为ClassPathXmlApplicationContext,通过该类的类名我们就能知道该类主要是加载类路径下的XML格式的配置文件,并进行bean对象的创建的。
接下来,我们就来看一下该类具体要完成的功能主要有哪些?
- 在构造方法中,创建BeanDefinitionReader对象。因为我们只是在父类中声明了一个BeanDefinitionReader类型的变量而已,而该BeanDefinitionReader类型的对象的创建应交由具体的子类来实现。
- 在构造方法中,调用refresh方法,用于进行配置文件加载、创建bean对象并存储到容器中。
- 重写父接口中的getBean方法,并实现依赖注入(DI)操作。
根据以上分析,我们创建出来的ClassPathXmlApplicationContext类就应该是下面这个样子的。
package com.meimeixia.framework.context.support;
import com.meimeixia.framework.beans.BeanDefinition;
import com.meimeixia.framework.beans.MutablePropertyValues;
import com.meimeixia.framework.beans.PropertyValue;
import com.meimeixia.framework.beans.factory.support.BeanDefinitionRegistry;
import com.meimeixia.framework.beans.factory.xml.XmlBeanDefinitionReader;
import com.meimeixia.framework.utils.StringUtils;
import java.lang.reflect.Method;
public class ClassPathXmlApplicationContext extends AbstractApplicationContext {
public ClassPathXmlApplicationContext(String configLocation) {
this.configLocation = configLocation;
this.beanDefinitionReader = new XmlBeanDefinitionReader();
try {
this.refresh();
} catch (Exception e) {
}
}
@Override
public Object getBean(String name) throws Exception {
Object obj = singletonObjects.get(name);
if (obj != null) {
return obj;
}
BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
BeanDefinition beanDefinition = registry.getBeanDefinition(name);
String className = beanDefinition.getClassName();
Class<?> clazz = Class.forName(className);
Object beanObj = clazz.newInstance();
MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
for (PropertyValue propertyValue : propertyValues) {
String propertyName = propertyValue.getName();
String value = propertyValue.getValue();
String ref = propertyValue.getRef();
if (ref != null && !"".equals(ref)) {
Object bean = getBean(ref);
String methodName = StringUtils.getSetterMethodByFieldName(propertyName);
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (methodName.equals(method.getName())) {
method.invoke(beanObj, bean);
}
}
}
if (value != null && !"".equals(value)) {
String methodName = StringUtils.getSetterMethodByFieldName(propertyName);
Method method = clazz.getMethod(methodName, String.class);
method.invoke(beanObj, value);
}
}
singletonObjects.put(name, beanObj);
return beanObj;
}
@Override
public <T> T getBean(String name, Class<? extends T> clazz) throws Exception {
Object bean = getBean(name);
if (bean == null) {
return null;
}
return clazz.cast(bean);
}
}
由于以上类的getBean方法中需要根据<property> 标签的name属性值拼接set方法名,所以在这里我们就专门创建了一个工具类,即StringUtils,如下所示。
package com.meimeixia.framework.utils;
public class StringUtils {
private StringUtils() {
}
public static String getSetterMethodByFieldName(String fieldName) {
String methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
return methodName;
}
}
测试
至此,咱们自己定义的Spring IoC功能就已经实现了。实现了之后,到底能不能使用还是一个大大的疑问,所以我们还得进行测试。
大家一定要记住,在测试之前,我们得先将我们的Maven工程安装到本地仓库里面,因为安装到本地仓库里面的话,其他的工程就可以引入了。那么,如何将我们的Maven工程安装到本地仓库中呢?
首先,确保Maven工程的打包方式是jar,如下图所示。
然后,按照下图来安装Maven工程,一切尽在不言中。
当在控制台中看到BUILD SUCCESS 时,就代表安装成功了,反正我是安装成功了,因为我在本地仓库里面是能找到打包成的jar文件的,如下图所示。
接下来,我们就要在之前的Maven工程(即spring_demo)中引入该jar文件并使用它了。
首先,在spring_demo工程的pom.xml文件里面注掉之前引入的Spring框架的坐标,然后引入以上jar文件的坐标,如下所示。
<dependency>
<groupId>com.meimeixia</groupId>
<artifactId>liayun_spring</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
然后,按照下图来刷新一下spring_demo工程,以确保工程引入了以上jar文件。很多人都会忘了这一步,切记切记!
以上jar文件的坐标引入完毕之后,我们就要来稍微修改一下UserController类的代码了,其实也并不需要修改,只是将导入的类改变一下就行,如下所示,看到没有,导入的尽是以上jar文件中的类和接口,这些都是我们自个定义的。
package com.meimeixia.controller;
import com.meimeixia.framework.context.ApplicationContext;
import com.meimeixia.framework.context.support.ClassPathXmlApplicationContext;
import com.meimeixia.service.UserService;
public class UserController {
public static void main(String[] args) throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.add();
}
}
现在我们试着来加载一下如下的XML配置文件,看一下咱们自定义的Spring IoC功能好不好使?
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="userDao" class="com.meimeixia.dao.impl.UserDaoImpl"></bean>
<bean id="userService" class="com.meimeixia.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
</beans>
此时,运行以上UserController类的代码,打印结果如下图所示,可以看到确实是我们所想要的结果。
当然,我们现在还需要去验证另外一个问题,就是我们自己定义的Spring IoC容器到底是立即加载的呢,还是延时加载的?
为了验证这一点,我们在UserController类的如下代码处打上了一个断点,然后以Debug的方式来运行UserController类的代码。
此时,程序肯定是要停留在断点处的,然后我们按下F6 键让程序再往下运行一行代码,当你切换到控制台时,你会发现现在控制台有打印结果了,如下图所示。
这也就是说在实例化容器对象后就会自动实例化bean,以此验证了我们自己定义的Spring IoC容器就是属于立即加载的。
接下来,我们再来做一个测试,就是给UserDaoImpl对象里面去注入String类型的数据。
首先,在UserDaoImpl类里面声明如下两个成员变量,注意了,我们这里是通过setter方法来注入的,所以我们还得为这俩成员变量提供对应的setter方法。
package com.meimeixia.dao.impl;
import com.meimeixia.dao.UserDao;
public class UserDaoImpl implements UserDao {
private String username;
private String password;
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public UserDaoImpl() {
System.out.println("userDao被创建了");
}
@Override
public void add() {
System.out.println("UserDao..." + username + "==" + password);
}
}
然后,我们试着来加载一下如下的XML配置文件,看一下咱们自定义的Spring IoC功能还好不好使?
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="userDao" class="com.meimeixia.dao.impl.UserDaoImpl">
<property name="username" value="zhangsan"></property>
<property name="password" value="123456"></property>
</bean>
<bean id="userService" class="com.meimeixia.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
</beans>
最后,我们再来运行以上UserController类的代码,打印结果如下图所示,可以看到对于String类型的数据,我们自定义的Spring IoC容器也是能够进行注入的。
至此,我们就测试完毕了我们自定义的Spring IoC功能,还是挺好使的,是不是啊!大家也不妨去使用一下,用完之后,你会发现我们自定义的Spring IoC功能基本上还是可以的,至少是可以进行依赖管理的,不仅能注入bean对象,还能注入String类型的数据。
总结
接下来,我们对自定义Spring IoC功能进行一个总结。
首先,我们来看一下我们自定义的Spring IoC功能里面使用到了哪些设计模式?总共用到了如下几个设计模式:
-
工厂模式。 这里面使用的是工厂模式 + 配置文件 的方式,其作用是可以提高程序的扩展性以及灵活性。 我在这里还是要多说一嘴,大家可以看到,在配置文件里面我们配了多个bean,并且我们在配置文件里面也体现出来了bean和bean之间的依赖关系。明确了这点之后,咱们来看一下UserServiceImpl这个实现类的代码,如下图所示,可以看到该类得依赖于UserDao子实现类对象,但是在该类里面并没有体现出来它到底要依赖的是哪个子实现类对象,很明显,这就是面向接口编程,作用是能够提高程序的扩展性。后期,如果我们想要去更换UserDao子实现类的话,那么只需要去修改配置文件即可,工程中的代码是不需要进行任何修改的。这便是使用工厂模式 + 配置文件 的方式的好处。 -
单例模式。 Spring IoC管理的bean对象都是单例的,此处的单例并不是通过构造器进行单例的控制的,而是Spring框架对每一个bean只创建了一个对象。 上面这句话什么意思啊?我来给大家解释解释,之前我为大家讲解过创建型模式里面的单例设计模式,想必大家应该都知道单例设计模式该怎么写吧!无非就是私有构造方法并提供一个对象让外界去使用,是不是啊!所以,我们都是通过私有构造器的这种方式来进行单例的控制的。 但是,此处我们是通过Spring框架对每一个bean只创建一个对象来实现单例的。有些童鞋就会想了,为什么会是这样的呢?这是因为别人在使用我们自己定义的Spring IoC功能时,我们不可能去要求用户在定义他自己的类时使用单例设计模式。 -
模板方法模式。 相信大家都看到了,AbstractApplicationContext类中的finishBeanInitialization方法(即完成bean对象的初始化)调用了子类的getBean方法,很显然,这使用的便是模板方法模式。 为什么要去调用子类中的getBean方法呢?这是因为getBean方法的实现和环境息息相关,例如对不同的配置文件来说,获取bean对象的方式就是不一样的,因此getBean方法就应交由具体的子类去实现,这同样也能提高程序的扩展性和灵活性。 -
迭代器模式。 大家也都看到了,对于MutablePropertyValues类的定义,我们使用到了迭代器模式。为什么在定义该类时要使用迭代器模式呢?因为该类存储并管理着PropertyValue对象,也就意味我们可以将其理解为是一个容器了,既然是容器的话,那么我们就可以给该容器提供一种遍历方式。
以上就是我们自定义的Spring IoC功能里面使用到的设计模式。不过,这里我要说明的一点是,Spring框架其实使用到了很多设计模式,如AOP使用到了代理模式,选择JDK代理或者CGLIB代理使用到了策略模式,除此之外,还用到了适配器模式、装饰者模式、观察者模式等。而我们这里只是实现了一个简易的Spring IoC功能,并没有一一使用这些设计模式,大家要明确这一点。
然后,我得告诉大家的是我们自定义的Spring IoC功能符合大部分设计原则,这一点我在编写代码的过程中就已经给大家稍微说了一下,当然了,你要是想体会得更深一点,那你还得自己去实现一下你自个的Spring IoC功能。
最后,我得给大家透个底,我们自定义的Spring框架的整个设计和Spring框架的设计还是有一定的出入的,因为Spring框架的底层是很复杂的,它进行了很深入的封装,并且对外提供了很好的扩展性,大家需要明确这一点。
至于,我们为何要自定义Spring IoC功能,我想是有以下几个目的。
- 了解Spring底层对对象的大体管理机制。
- 了解设计模式在具体的开发中的使用。这个目的才是我们最终的真正目的,因为我们学完23种设计模式之后,就要在具体的开发中去用一用这些设计模式了,看一下它们到底是怎么去使用的。
- 以后如果你要学习Spring源码的话,那么通过该案例的实现,就可以降低你学习Spring源码的入门成本了。
|