7.3.5 创建容器
ClassPathXmlApplicationContext 对Bean配置资源的载入是从refresh()方法开始的。refresh()方法是一个模板方法,规定了 IOC 容器的启动流程,有些逻辑要交给其子类实现。它对 Bean 配置资源进行载入,ClassPathXmlApplicationContext 通过调用其父类 AbstractApplicationContext 的 refresh()方法启动整个IOC容器对Bean定义的载入过程。
7.4 自定义SpringIOC
现要对下面的配置文件进行解析,并自定义Spring框架的IOC对涉及到的对象进行管理。
7.4.1 定义bean相关的pojo类
包结构
7.4.1.1 PropertyValue类
用于封装bean的属性,体现到上面的配置文件就是封装bean标签的子标签property标签数据。
/**
* 用来封装 bean 标签下的 property 标签的属性
* name
* ref
* value:用来给基本类型 或者 String 赋值
* @author tt11
* @create 2022-09-03 13:38
*/
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; } }
7.4.1.2 MutablePropertyValues类
一个bean标签可以有多个property子标签,所以再定义一个MutablePropertyValues类,用来存储并管理多个PropertyValue对象。
/** * 用来存储和管理多个 PropertyValue 对象 * @author tt11 * @create 2022-09-03 14:09 */ public class MutablePropertyValues implements Iterable { // 定义 list 集合,用来存储 PropertyValue 对象 private final List propertyValueList; public MutablePropertyValues() { this.propertyValueList = new ArrayList<>(); } public MutablePropertyValues(List propertyValueList) { if (propertyValueList == null) { this.propertyValueList = new ArrayList<>(); } else { this.propertyValueList = propertyValueList; } } // 获取所有的 PropertyValue 对象,以数组形式返回 public PropertyValue[] getPropertyValues() { return propertyValueList.toArray(new PropertyValue[propertyValueList.size()]); } // 根据 propertyName 来获取 PropertyValue 对象 public PropertyValue getPropertyValue(String propertyName) { for (PropertyValue propertyValue : propertyValueList) { if (propertyName == propertyValue.getName()) { return propertyValue; } } return null; } // 判断集合是否为空 public boolean isEmpty() { return propertyValueList.isEmpty(); } // 判断是否包含指定 name 的 PropertyValue 对象 public boolean contains(String propertyName) { return getPropertyValue(propertyName) != null; } // 添加 PropertyValue 对象 public MutablePropertyValues addPropertyValue(PropertyValue propertyValue) { // 判断是否有重复 for (int i = 0; i < propertyValueList.size(); i++) { PropertyValue currentPv = propertyValueList.get(i); if (currentPv.getName() == propertyValue.getName()) { // 重复,覆盖掉原来的值 propertyValueList.set(i, propertyValue); return this; } } // 不重复 propertyValueList.add(propertyValue); return this; // 为了可以链式调用 } // 获取迭代器对象 @Override public Iterator iterator() { return propertyValueList.iterator(); } }
7.4.1.3 BeanDefinition类
BeanDefinition类用来封装bean信息的,主要包含id(即bean对象的名称)、class(需要交由spring管理的类的全类名)及子标签property数据。
/** * 封装 Bean 对象 * id:对象id * className:全类名 * Property属性 * @author tt11 * @create 2022-09-03 14:34 */ public class BeanDefinition { private String id; private String className; private MutablePropertyValues propertyValues; public BeanDefinition() { 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; } }
7.4.2 定义注册表相关类
7.4.2.1 BeanDefinitionRegistry接口
BeanDefinitionRegistry接口定义了注册表的相关操作,定义如下功能:
- 注册BeanDefinition对象到注册表中
- 从注册表中删除指定名称的BeanDefinition对象
- 根据名称从注册表中获取BeanDefinition对象
- 判断注册表中是否包含指定名称的BeanDefinition对象
- 获取注册表中BeanDefinition对象的个数
- 获取注册表中所有的BeanDefinition的名称
/** * 注册表接口 */ public interface BeanDefinitionRegistry { //注册BeanDefinition对象到注册表中 void registerBeanDefinition(String beanName, BeanDefinition beanDefinition); //从注册表中删除指定名称的BeanDefinition对象 void removeBeanDefinition(String beanName) throws Exception; //根据名称从注册表中获取BeanDefinition对象 BeanDefinition getBeanDefinition(String beanName) throws Exception; // 判断注册表中是否包含指定名称的BeanDefinition对象 boolean containsBeanDefinition(String beanName); // 获取注册表中BeanDefinition对象的个数 int getBeanDefinitionCount(); // 获取注册表中所有的BeanDefinition的名称 String[] getBeanDefinitionNames(); }
7.4.2.2 SimpleBeanDefinitionRegistry类
该类实现了BeanDefinitionRegistry接口,定义了Map集合作为注册表容器。
/** * 注册表子实现类 * @author tt11 * @create 2022-09-03 14:41 */ public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry { // 定义一个map,用来存储 BeanDefinition 对象 private Map beanDefinitionMap = new HashMap<>(); @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]); } }
7.4.3 定义解析器相关类
7.4.3.1 BeanDefinitionReader接口
BeanDefinitionReader 是用来解析配置文件并在注册表中注册bean的信息。定义了两个规范:
- 获取注册表的功能,让外界可以通过该对象获取注册表对象。
- 加载配置文件,并注册bean数据。
public interface BeanDefinitionReader { //获取注册表对象 BeanDefinitionRegistry getRegistry(); //加载配置文件并在注册表中进行注册 void loadBeanDefinitions(String configLocation) throws Exception; }
7.4.3.2 XmlBeanDefinitionReader类
XmlBeanDefinitionReader 类是专门用来解析xml配置文件的。该类实现BeanDefinitionReader接口并实现接口中的两个功能。
引入依赖 dom4j
dom4j
dom4j
1.6.1
聚合注册表对象,提供构造器方法
1.使用 SAXReader 解析 xml 文件
2.得到根标签(beans)
3.得到 bean 标签 集合
4.解析数据并封装为 BeanDefinition 对象
4.1 id
4.2 class
5.得到 property 标签集合
6.解析数据
6.1 name
6.2 ref
6.3 value
7.封装为 MutablePropertyValues 对象,设置在 BeanDefinition 对象中
8.在注册表中注册对象
/** * 用来解析 xml 配置文件 * @author tt11 * @create 2022-09-03 15:07 */ public class XmlBeanDefinitionReader implements BeanDefinitionReader { // 聚合注册表对象,提供构造器方法 private BeanDefinitionRegistry registry; public XmlBeanDefinitionReader() { this.registry = new SimpleBeanDefinitionRegistry(); } @Override public BeanDefinitionRegistry getRegistry() { return registry; } /** * 加载配置文件,在注册表中注册 bean 信息 * @param configLocation 配置文件路径 * @throws Exception */ @Override public void loadBeanDefinitions(String configLocation) throws Exception { // 1.使用 SAXReader 解析 xml 文件 SAXReader reader = new SAXReader(); InputStream is = XmlBeanDefinitionReader.class.getClassLoader().getResourceAsStream(configLocation); // 需要一个输入流 Document document = reader.read(is); // 2.得到根标签(beans) Element beansElement = document.getRootElement(); // 3.得到 bean 标签 集合 List beanElement = beansElement.elements("bean"); // 4.解析数据并封装为 BeanDefinition 对象 for (Element element : beanElement) { // 4.1 id String id = element.attributeValue("id"); // 4.2 class String className = element.attributeValue("class"); // 4.3 封装为 BeanDefinition 对象 BeanDefinition beanDefinition = new BeanDefinition(); beanDefinition.setId(id); beanDefinition.setClassName(className); MutablePropertyValues propertyValues = new MutablePropertyValues(); // 5.得到 property 标签集合 List propertyElements = element.elements("property"); for (Element property : propertyElements) { // 6.解析数据 // 6.1 name String name = property.attributeValue("name"); // 6.2 ref String ref = property.attributeValue("ref"); // 6.3 value String value = property.attributeValue("value"); PropertyValue propertyValue = new PropertyValue(name, ref, value); // 7.封装为 MutablePropertyValues 对象,设置在 BeanDefinition 对象中 propertyValues.addPropertyValue(propertyValue); } beanDefinition.setPropertyValues(propertyValues); // 8.在注册表中注册对象 registry.registerBeanDefinition(id, beanDefinition); } } }
7.4.4 IOC容器相关类
7.4.4.1 BeanFactory接口
在该接口中定义IOC容器的统一规范即获取bean对象。
public interface BeanFactory { //根据bean对象的名称获取bean对象 Object getBean(String name) throws Exception; //根据bean对象的名称获取bean对象,并进行类型转换 T getBean(String name, Class clazz) throws Exception; }
7.4.4.2 ApplicationContext接口
该接口的所以的子实现类对bean对象的创建都是非延时的,所以在该接口中定义 refresh()
方法,该方法主要完成以下两个功能:
- 加载配置文件。
- 根据注册表中的BeanDefinition对象封装的数据进行bean对象的创建。
public interface ApplicationContext extends BeanFactory { //进行配置文件加载并进行对象创建 void refresh() throws IllegalStateException, Exception; }
7.4.4.3 AbstractApplicationContext类
- 作为ApplicationContext接口的子类,所以该类也是非延时加载,所以需要在该类中定义一个Map集合,作为bean对象存储的容器。
- 声明BeanDefinitionReader类型的变量,用来进行xml配置文件的解析,符合单一职责原则。
BeanDefinitionReader类型的对象创建交由子类实现,因为只有子类明确到底创建BeanDefinitionReader哪儿个子实现类对象。
/** * ApplicationContext 接口的子实现类,用于立即加载 * @author tt11 * @create 2022-09-03 15:49 */ public abstract class AbstractApplicationContext implements ApplicationContext { // 声明解析器变量,由子类去初始化(因为可能有多种解析器) protected BeanDefinitionReader beanDefinitionReader; // 声明map集合,用来存放 bean 对象 protected Map singletonObjects = new HashMap<>(); // 用来存放配置文件路径,在子类中初始化 protected String configLocation; @Override public void refresh() throws Exception { // 加载配置文件 beanDefinitionReader.loadBeanDefinitions(configLocation); // 初始化 bean finishBeanInitialization(); } // bean的初始化 private void finishBeanInitialization() throws Exception { // 获取注册表 BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry(); String[] beanDefinitionNames = registry.getBeanDefinitionNames(); for (String beanName : beanDefinitionNames) { // 初始化对象,将 getBean(beanName); } } }
注意:该类finishBeanInitialization()方法中调用getBean()方法使用到了模板方法模式。
7.4.4.4 ClassPathXmlApplicationContext类
构造方法
1.设置configLocation
2.创建 reader 的子实现类对象
3.调用父类的 refresh 方法
getBean(name)
1.判断容器内是否有此对象
2.有就直接返回
3.根据注册表得到 beanDefinition 对象
4.若为空,直接返回空
5.得到对象的全类名
6.根据反射得到对象
7.返回之前需要设置 Property
8.由 beanDefinition 得到 MutablePropertyValues(迭代器模式)
9.遍历集合
10.解析 name,ref,value
11.ref不为空
11.1 拼接方法名
11.1 执行方法
12.value不为空
12.1 拼接方法
12.2 执行方法
13.将 bean 加入map容器中
14.返回对象
该类主要是加载类路径下的配置文件,并进行bean对象的创建,主要完成以下功能:
- 在构造方法中,创建BeanDefinitionReader对象。
- 在构造方法中,调用refresh()方法,用于进行配置文件加载、创建bean对象并存储到容器中。
- 重写父接口中的getBean()方法,并实现依赖注入操作。
/** * IOC容器的具体子实现类:用于加载类路径下的xml格式的配置文件 * @author tt11 * @create 2022-09-03 20:19 */ public class ClassPathXmlApplicationContext extends AbstractApplicationContext{ public ClassPathXmlApplicationContext(String configLocation) { // 1.设置configLocation this.configLocation = configLocation; // 2.创建 reader 的子实现类对象 this.beanDefinitionReader = new XmlBeanDefinitionReader(); try { // 3.调用父类的 refresh 方法 this.refresh(); } catch (Exception e) { e.printStackTrace(); } } @Override public Object getBean(String name) throws Exception { // 1.判断容器内是否有此对象 Object obj = singletonObjects.get(name); // 2.有就直接返回 if (obj != null) { return obj; } // 3.根据注册表得到 beanDefinition 对象 BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry(); BeanDefinition beanDefinition = registry.getBeanDefinition(name); // 4.若为空,直接返回空 if (beanDefinition == null) { return null; } // 5.得到对象的全类名 String className = beanDefinition.getClassName(); // 6.根据反射得到对象 Class clazz = Class.forName(className); Object bean = clazz.newInstance(); // 7.返回之前需要设置 Property 对象 // 8.由 beanDefinition 得到 MutablePropertyValues(迭代器模式) MutablePropertyValues propertyValues = beanDefinition.getPropertyValues(); // 9.遍历集合 for (PropertyValue propertyValue : propertyValues) { // 10.解析 name,ref,value String propertyName = propertyValue.getName(); String ref = propertyValue.getRef(); String value = propertyValue.getValue(); // 11.ref不为空 if (ref != null && !"".equals(ref)) { Object propertyBean = getBean(ref); // 11.1 拼接方法名 String methodName = StringUtils.getSetterMethodNameByFieldName(propertyName); // 11.1 执行方法 Method[] methods = clazz.getMethods(); for (Method method : methods) { if (method.getName().equals(methodName)) { method.invoke(bean, propertyBean); } } } // 12.value 不为空 if (value != null && !"".equals(value)) { // 12.1 拼接方法 String methodName = StringUtils.getSetterMethodNameByFieldName(propertyName); // 12.2 执行方法 Method method = clazz.getMethod(methodName, String.class); method.invoke(bean, value); } } // 13.将 bean 加入map容器中 singletonObjects.put(name, bean); // 14.返回对象 return bean; } @Override public T getBean(String name, Class clazz) throws Exception { Object bean = getBean(name); return clazz.cast(bean); } } spring_demo 模块引入依赖 com.tt11 tt11_spring 1.0-SNAPSHOT
测试成功!!!!!
7.4.5 自定义Spring IOC总结
目录结构
7.4.5.1 使用到的设计模式
- 工厂模式。这个使用工厂模式 + 配置文件的方式。
- 单例模式。Spring IOC管理的bean对象都是单例的,此处的单例不是通过构造器进行单例的控制的,而是spring框架对每一个bean只创建了一个对象。
- 模板方法模式。AbstractApplicationContext类中的finishBeanInitialization()方法调用了子类的getBean()方法,因为getBean()的实现和环境息息相关。
- 迭代器模式。对于MutablePropertyValues类定义使用到了迭代器模式,因为此类存储并管理PropertyValue对象,也属于一个容器,所以给该容器提供一个遍历方式。
spring框架其实使用到了很多设计模式,如AOP使用到了代理模式,选择JDK代理或者CGLIB代理使用到了策略模式,还有适配器模式,装饰者模式,观察者模式等。
7.4.5.2 符合大部分设计原则
7.4.5.3 整个设计和Spring的设计还是有一定的出入
spring框架底层是很复杂的,进行了很深入的封装,并对外提供了很好的扩展性。而我们自定义SpringIOC有以下几个目的:
- 了解Spring底层对对象的大体管理机制。
- 了解设计模式在具体的开发中的使用。
- 以后学习spring源码,通过该案例的实现,可以降低spring学习的入门成本。
8,闲聊
本次学到的技术:
- 5种软件设计原则
- 23种设计模式
- 自定义 IOC 容器
历时 两个星期,学习时间 28小时22分。