Spring框架自定义实现IOC基础功能/IDEA如何手动实现IOC功能
继续整理记录这段时间来的收获,详细代码可在我的Gitee仓库Java设计模式克隆下载学习使用!
7.4 自定义Spring IOC
创建新模块,结构如图![[Pasted image 20230210173222.png]]
7.4.1 定义bean相关POJO类
7.4.1.1 定义propertyValue类
/** * @Author:Phil * @ClassName: PropertyValue * @Description: * 用来封装bean标签下的property标签属性 * name属性 * ref属性 * value属性:给基本数据类型及String类型赋值 * @Date 2023/2/8 21:45 * @Version: 1.0 **/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 定义MultiplePropertyValue类
一个bean 标签可以有多个property子标签,故用multiplePropertyValue类来存储PropertyValue对象
public class MultiplePropertyValues implements Iterable<PropertyValue>{
// 定义list集合对象,用来存储PropertyValue对象 private final List<PropertyValue> propertyValueList; public MultiplePropertyValues() { this.propertyValueList = new ArrayList<PropertyValue> (); } public MultiplePropertyValues(List<PropertyValue> propertyValueList) { if(propertyValueList == null) this.propertyValueList = new ArrayList<PropertyValue>(); else this.propertyValueList = propertyValueList; }
// 获取所有propertyValue对象,以数组形式返回 public PropertyValue[] getPropertyValues(){ return propertyValueList.toArray(new PropertyValue[0]); }
// 根据name属性值返回对应PropertyValue对象 public PropertyValue getPropertyValues(String propertyName){
// 遍历集合返回 for (PropertyValue propertyValue : propertyValueList) { if(propertyValue.getName().equals(propertyName)) return propertyValue; } return null; }
// 判断集合是否为空 public boolean isEmpty(){ return propertyValueList.isEmpty(); }
// 添加PropertyValue对象 public MultiplePropertyValues addPropertyValue(PropertyValue pv){
// 若有则进行覆盖 for (int i = 0; i < propertyValueList.size(); i++) { if(propertyValueList.get(i).getName().equals(pv.getName())){ propertyValueList.set(i,pv); return this;//目的是链式编程 } }
// 添加新的 this.propertyValueList.add(pv); return this; }
// 判断是否有指定name的PropertyValue对象 public boolean contains(String propertyName){ return getPropertyValues(propertyName) != null; }
// 获取迭代器对象 @Override public Iterator<PropertyValue> iterator() { return propertyValueList.iterator(); }
}
7.1.4.3 BeanDefinition类
BeanDefinition类用来封装bean信息,主要包含id(bean 名称),class(bean全类名)及子标签property对象数据
public class BeanDefinition { private String id; private String className; private MultiplePropertyValues multiplePropertyValues; public BeanDefinition() { multiplePropertyValues = new MultiplePropertyValues(); } 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 MultiplePropertyValues getMultiplePropertyValues() { return multiplePropertyValues; } public void setMultiplePropertyValues(MultiplePropertyValues multiplePropertyValues) { this.multiplePropertyValues = multiplePropertyValues; }
}
7.4.2 定义注册表类
7.4.2.1 定义BeanDefinitionRegistry接口
BeanDefinitionRegistry接口定义了注册表相关操作,定义如下功能:
- 注册BeanDefinition对象到注册表中
- 根据名称从注册表中获取后去BeanDefinition对象
- 从注册表中删除指定名称的BeanDefinition对象
- 判断注册表中是否包含指定名称的BeanDefinition对象
- 获取注册表中BeanDefinition对象个数
- 获取注册表中所有的Bean
public interface BeanDefinitionRegistry { //往注册表中注册bean void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) ; //从注册表删掉指定名称bean void removeBeanDefinition(String beanName) throws Exception; //获取指定名称bean BeanDefinition getBeanDefinition(String beanName) throws Exception; //判断是否包含指定名称bean boolean containsBeanDefinition(String beanName); //获取所有bean String[] getBeanDefinitionNames(); int getBeanDefinitionCount(); boolean isBeanNameInUse(String var1);
}
7.4.2.2 SimpleBeanDefinitionRegistry类
public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry{
// 创建容器,用于存储 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 String[] getBeanDefinitionNames() { return beanDefinitionMap.keySet().toArray(new String[0]); } @Override public int getBeanDefinitionCount() { return beanDefinitionMap.size(); } @Override public boolean isBeanNameInUse(String var1) { return beanDefinitionMap.containsKey(var1); }
}
7.4.3 定义解析器类
7.4.3.1 BeanDefinitionReader接口
BeanDefinitionReader用来解析配置文件并在注册表中注册bean的信息,定义了两规范:
- 获取注册表功能,让外界可通过该对象获取注册表对象
- 加载配置文件,并注册bean数据
public interface BeanDefinitionReader{//获取注册表对象BeanDefinitionRegistry getRegistry();//加载配置文件斌在注册表中进行注册void loadBeanDefinitions(String configuration);
}
7.4.3.2 XmlBeanDefinitionReader类
XmlBeanDefinitionReader类是专门来解析xml配置文件,实现了BeanDefinitionReader接口的两个功能。
public class XmlBeanDefinitionReader implements BeanDefinitionReader {
// 声明注册表对象 private BeanDefinitionRegistry registry; public XmlBeanDefinitionReader() { this.registry = new SimpleBeanDefinitionRegistry(); } @Override public BeanDefinitionRegistry getRegistry() { return registry; } @Override public void loadBeanDefinitions(String configuration) throws Exception{
// 使用dom4j进行xml配置文件的解析 SAXReader saxReader = new SAXReader();
// 后去类路径下的配置文件 InputStream resourceAsStream = XmlBeanDefinitionReader.class.getClassLoader().getResourceAsStream(configuration); Document document = saxReader.read(resourceAsStream);
// 根据Document对象获取根标签对象(beans) Element rootElement = document.getRootElement();
// 获取根标签下所有的bean标签对象 List<Element> elements = rootElement.elements("bean");
// 遍历集合 for (Element element : elements) {
// 获取id属性 String id = element.attributeValue("id");
// 获取className String className = element.attributeValue("class");
// 将id和className封装到BeanDefinition对象中
// 创建BeanDefinition对象 BeanDefinition beanDefinition = new BeanDefinition(); beanDefinition.setId(id); beanDefinition.setClassName(className);
// 创建MultiplePropertyValue对象 MultiplePropertyValues multiplePropertyValues = new MultiplePropertyValues();
// 获取bean标签下的所有property标签对象 List<Element> propertyElements = element.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); multiplePropertyValues.addPropertyValue(propertyValue); }
// 将multiplePropertyValues封装到BeanDefinition中 beanDefinition.setMultiplePropertyValues(multiplePropertyValues);
// 将BeanDefinition注册到注册表中 registry.registerBeanDefinition(id,beanDefinition); } }
}
7.4.4 容器相关类
7.4.4.1 BeanFactory接口
该接口定义IOC容器的统一规范即获取bean对象
public interface BeanFactory{//根据bean对象的名称获取bean对象Object getBean(String name) throws Exception;//根据bean对象的名称获取bean对象,并进行类型转换<T> T getBean(String name,Class<? extends T> clazz) throws Exception;
}
7.4.4.2 ApplicationContext接口
该接口的子实现类对bean 对象的创建都是非延时的,所以该接口定义refresh方法,主要有两功能:
- 加载配置文件
- 根据注册表中BeanDefinition对象封装的数据进行bean对象的创建
public interface ApplicationContext extends BeanFactory{ void refresh()throws Exception;
}
7.4.4.3 AbstractApplicationContext接口
- 作为ApplicationContext接口的子类,故该类是非延时加载,故需要在该类中定义Map集合,作为bean对象存储容器
- 声明BeanDefinition类型变量,用来进行xml配置文件解析,符合单一职责原则
- BeanDefinition类型对象创建交由子类实现,子类明确创建BeanDefinitionReader
public abstract class AbstractApplicationContext implements ApplicationContext {
// 声明解析器对象 protected BeanDefinitionReader beanDefinitionReader;
// 存储bean容器,key存储的bean的id,value是bean对象 protected Map<String,Object> singletonObject = new HashMap<String,Object>();;
// 存储配置文件路径 String configLocation; public void refresh() throws Exception{
// 加载BeanDefinition beanDefinitionReader.loadBeanDefinitions(configLocation);
// 初始化bean finishBeanInitialization(); } public void finishBeanInitialization() throws Exception{
// 获取注册表对象 BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
// 获取BeanDefinition对象 String [] beanNames = registry.getBeanDefinitionNames();
// 初始化bean for (String beanName : beanNames) { getBean(beanName); } }
}
7.4.4.4 ClassPathXmlApplicationContext接口
该类主要是加载类路径下的配置文件,并进行bean对象的创建,主要有以下功能:
- 在构造方法中,创建BeanDefinitionReader对象
- 在构造方法中,调用refresh方法,用于进行配置文件加载,创建bean对象并存储到容器中
- 重写父类中的getBean方法,并实现依赖注入
public class ClassPathXmlApplicationContext extends AbstractApplicationContext{ public ClassPathXmlApplicationContext(String configLocation){ this.configLocation = configLocation;
// 构建解析器对象 beanDefinitionReader = new XmlBeanDefinitionReader(); try { this.refresh(); }catch (Exception exception){ exception.printStackTrace(); } }
// 根据bean对象的名称获取bean对象 @Override public Object getBean(String name) throws Exception {
// 判断对象容器中是否包含指定bean对象,若包含则返回,否则创建 Object object = singletonObject.get(name); if(object != null) return object;
// 获取BeanDefinition对象 BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry(); BeanDefinition beanDefinition = registry.getBeanDefinition(name);
// 获取bean信息中的className String className = beanDefinition.getClassName();
// 通过反射获取对象 Class<?> clazz = Class.forName(className); Object instance = clazz.newInstance();
// 进行依赖注入操作 MultiplePropertyValues multiplePropertyValues = beanDefinition.getMultiplePropertyValues(); for (PropertyValue propertyValue : multiplePropertyValues) {
// 获取name属性值 String propertyValueName = propertyValue.getName();
// 获取value值 String value = propertyValue.getValue();
// 获取ref值 String ref = propertyValue.getRef(); if(ref != null && !"".equals(ref)){
// 获取依赖的对象 Object bean = getBean(ref);
// 拼接方法名 String setterMethodByField = StringUtils.getSetterMethodByField(propertyValueName);
// 获取所有方法 Method[] methods = clazz.getMethods(); for (Method method : methods) { if(method.getName().equals(setterMethodByField))
// 执行setter方法 method.invoke(instance,bean); } } if(value != null && !"".equals(value)){
// 拼接方法名 String methodName = StringUtils.getSetterMethodByField(propertyValueName);
// 获取method对象 Method method = clazz.getMethod(methodName, String.class); method.invoke(instance, value); } }
// 在返回instance对象之前,将该对象存储到map容器中 singletonObject.put(name,instance); return instance; } @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); }
}
7.4.4.5 测试
将前文回顾Spring框架项目中的pom文件的spring-context依赖换为上述新建项目依赖,如图
运行后如图
7.4.5 总结
7.4.5.1 使用到的设计模式
- 工厂模式:工厂模式+ 配置文件
- 单例模式。Spring IOC管理的bean都是单例的,此处单例不是通过构造器进行单例构建,且框架对每个bean只创建一个对象。
- 模板方法模式。AbstractApplicationContext类中的finishInitialization方法调用getBean方法,因为getBean实现和环境有关。
- 迭代器模式。其中MultiplePropertyValyes类使用了迭代器模式,因为此类存储并管理PropertyValue对象,也属于一个容器。
- 还使用了很多设计模式,如AOP使用到了代理模式,选择JDK代理或CGLIB代理使用了策略模式,还有适配器模式,装饰者模式,观察者模式等。
7.4.5.2 符合大部分设计原则
7.4.5.3 整个设计和Spring设计还有一定出入
Spring框架底层是很复杂的,进行了很深入的封装,并对外提供了很好的扩展性,自定义Spring IOC容器有两目的:
- 了解Spring底层对对象的大体管理机制
- 了解设计模式在具体开发中的使用
- 以后学习Spring源码,通过该案例实现,可以降低Spring学习入门成本