继续整理记录这段时间来的收获,详细代码可在我的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学习入门成本