【Java设计模式】二十五、自定义Spring IoC

文章目录

  • 1、IoC类的定义
    • [1.1 定义bean相关的pojo类PropertyValue](#1.1 定义bean相关的pojo类PropertyValue)
    • [1.2 定义MutablePropertyValues类](#1.2 定义MutablePropertyValues类)
    • [1.3 定义BeanDefinition类](#1.3 定义BeanDefinition类)
  • 2、定义注册表相关类
    • [2.1 BeanDefinitionRegistry接口](#2.1 BeanDefinitionRegistry接口)
    • [2.2 SimpleBeanDefinitionRegistry类](#2.2 SimpleBeanDefinitionRegistry类)
  • 3、定义解析器相关类
    • [3.1 BeanDefinitionReader接口](#3.1 BeanDefinitionReader接口)
    • [3.2 XmlBeanDefinitionReader类](#3.2 XmlBeanDefinitionReader类)
  • 4、IOC容器相关类
    • [4.1 BeanFactory接口](#4.1 BeanFactory接口)
    • [4.2 ApplicationContext接口](#4.2 ApplicationContext接口)
    • [4.3 AbstractApplicationContext类](#4.3 AbstractApplicationContext类)
    • [4.4 ClassPathXmlApplicationContext类](#4.4 ClassPathXmlApplicationContext类)
  • 5、测试自定义的IoC
  • [6、自定义Spring IOC总结](#6、自定义Spring IOC总结)

自定义Spring框架的IOC,对下面的配置文件进行解析,并对涉及到的对象进行管理

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="userService" class="com.plat.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>
    <bean id="userDao" class="com.plat.dao.impl.UserDaoImpl"></bean>
</beans>

1、IoC类的定义

1.1 定义bean相关的pojo类PropertyValue

根据上面xml的特点,定义PropertyValue类,用于封装bean标签下的property标签的各个属性。

  • property.name即对象的属性名
  • property.ref即引用参考对象
  • property.value即对象属性为基本类型或String时的赋的值
java 复制代码
public class PropertyValue {

  private String name;  //对象的属性名
  private String ref;  //引用参考对象
  private String value;  //对象属性为基本类型或String时的赋的值


  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;
  }
}

1.2 定义MutablePropertyValues类

一个bean标签可以有多个property子标签,一个property子标签对应一个PropertyValue对象,所以再定义一个MutablePropertyValues类,用来存储并管理多个PropertyValue对象。这里实现Iterable接口,用到了迭代器模式。

java 复制代码
public class MutablePropertyValues implements Iterable<PropertyValue> {
	
	//里面存一个个PropertyValue对象(property子标签)。复习:这里用final修饰,list对象本身只能赋值一次,但其里面的元素(对象的属性)是可变的
    private final List<PropertyValue> propertyValueList;
	
	//被final修饰了,必须在其构造方法结束之前对其赋值,因此加有参和无参构造
	
	//无参构造
    public MutablePropertyValues() {
        this.propertyValueList = new ArrayList<PropertyValue>();
    }
	
	//有参构造
    public MutablePropertyValues(List<PropertyValue> propertyValueList) {
        this.propertyValueList = (propertyValueList != null ? propertyValueList : new ArrayList<PropertyValue>());
    }

	/**
	 * 获取所有的Property对象
	*/
    public PropertyValue[] getPropertyValues() {
    	//toArray返回的是Object数组,传个new PropertyValue[0]可让其返回PropertyValue数组,当然不这么写,强转也行
        return this.propertyValueList.toArray(new PropertyValue[0]);
    }

	/**
	 * 根据PropertyValue的name属性值获取PropertyValue对象
	*/
    public PropertyValue getPropertyValue(String propertyName) {
        for (PropertyValue pv : this.propertyValueList) {
            if (pv.getName().equals(propertyName)) {
                return pv;
            }
        }
        return null;
    }
	
	/**
	 * 接口方法重写
	 * 需要返回迭代器对象
	 * 直接把List对象的迭代器扔出去
	*/
    @Override
    public Iterator<PropertyValue> iterator() {
        return propertyValueList.iterator();
    }


	/**
	 * 判断集合是否为空
	*/
    public boolean isEmpty() {
        return this.propertyValueList.isEmpty();
    }

	/**
	 * 添加一个PropertyValue对象
	 * MutablePropertyValues 的返回值类型,用于链式编程
	*/
    public MutablePropertyValues addPropertyValue(PropertyValue pv) {
        for (int i = 0; i < this.propertyValueList.size(); i++) {
            PropertyValue currentPv = this.propertyValueList.get(i);
            if (currentPv.getName().equals(pv.getName())) {
            	//判断传入的PropertyValue对象是否已在集合中,若是,则覆盖(set方法替换指定索引的对象)
                this.propertyValueList.set(i, new PropertyValue(pv.getName(),pv.getRef(), pv.getValue()));
                //也可this.propertyValueList.set(i, pv);  
                return this;
            }
        }
        //无重复
        this.propertyValueList.add(pv);
        return this;
    }

	/**
	 * 判断集合是否包含某个name属性为形参值的PropertyValue对象
	*/
    public boolean contains(String propertyName) {
        return getPropertyValue(propertyName) != null;
    }
}

【补充】:上面那个list转数组的toArray返回的是一个Object[ ],想返回一个指定类型的数组,可强转,也可传个对应类型的数组,数组长度无所谓,0也行。

java 复制代码
return this.propertyValueList.toArray(new PropertyValue[0]);
return (PropertyValue[]) this.propertyValueList.toArray();

以上两种等价,第一种的相关源码:也是做了一个转型和数组copy

1.3 定义BeanDefinition类

BeanDefinition类用来封装bean信息的,主要包含id(即bean对象的名称)、class(需要交由spring管理的类的全类名)及子标签property数据。

java 复制代码
public class BeanDefinition {

    private String id;  //bean对象的名称
    
    private String className;   //全类名
    
    private MutablePropertyValues propertyValues;  //property标签

    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 void setPropertyValues(MutablePropertyValues propertyValues) {
        this.propertyValues = propertyValues;
    }

    public MutablePropertyValues getPropertyValues() {
        return propertyValues;
    }
}

2、定义注册表相关类

2.1 BeanDefinitionRegistry接口

BeanDefinitionRegistry接口定义了注册表的相关操作,定义如下功能:

  • 注册BeanDefinition对象到注册表中
  • 从注册表中删除指定名称的BeanDefinition对象
  • 根据名称从注册表中获取BeanDefinition对象
  • 判断注册表中是否包含指定名称的BeanDefinition对象
  • 获取注册表中BeanDefinition对象的个数
  • 获取注册表中所有的BeanDefinition的名称
java 复制代码
public interface BeanDefinitionRegistry {

    //注册BeanDefinition对象到注册表中
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);

    //从注册表中删除指定名称的BeanDefinition对象
    void removeBeanDefinition(String beanName) throws Exception;

    //根据名称从注册表中获取BeanDefinition对象
    BeanDefinition getBeanDefinition(String beanName) throws Exception;

    boolean containsBeanDefinition(String beanName);

    int getBeanDefinitionCount();

    String[] getBeanDefinitionNames();
    
}

2.2 SimpleBeanDefinitionRegistry类

写BeanDefinitionRegistry的子实现类,该类实现了BeanDefinitionRegistry接口,定义了Map集合作为注册表容器,用来存储BeanDefinition对象 ,选择双列集合,key为BeanDefinition的名称。

java 复制代码
public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry {
	
	//存BeanDefinition
    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() {
    	//toArray(new String[1])的形参是为了把toArray的Object[]返回类型转为String[],传的String[1],长度无所谓
    	//keySet拿到单列的key集合
        return beanDefinitionMap.keySet().toArray(new String[1]);
    }
}

3、定义解析器相关类

3.1 BeanDefinitionReader接口

BeanDefinitionReader是用来解析配置文件,封装成BeanDefinition对象,并在注册表BeanDefinitionRegistry中注册。接口只定义了两个规范,具体逻辑又子类去实现:

  • 获取注册表的功能,让外界可以通过该对象获取注册表对象。
  • 加载配置文件,并注册bean数据。
java 复制代码
public interface BeanDefinitionReader {

	//获取注册表对象
    BeanDefinitionRegistry getRegistry();
	//加载配置文件并在注册表中进行注册
    void loadBeanDefinitions(String configLocation) throws Exception;
    
}

3.2 XmlBeanDefinitionReader类

根据不同的配置文件,写BeanDefinitionReader的实现类,如针对properties文件的PropertiesXmlBeanDefinitionReader类,这里写XmlBeanDefinitionReader类是专门用来解析xml配置文件的。该类实现BeanDefinitionReader接口并实现接口中的两个方法

java 复制代码
public class XmlBeanDefinitionReader implements BeanDefinitionReader {
	
	//声明注册表对象
    private BeanDefinitionRegistry registry;

	//无参构造中对注册表对象进行赋值(赋子类)
    public XmlBeanDefinitionReader() {
        this.registry = new SimpleBeanDefinitionRegistry();
    }

    @Override
    public BeanDefinitionRegistry getRegistry() {
        return registry;
    }

	/**
	 * 形参为类路径下的xml配置文件的路径
	*/
    @Override
    public void loadBeanDefinitions(String configLocation) throws Exception {
		//通过当前类的类加载器获取输入流对象
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(configLocation);
        //dom4j的SAXReader对象
        SAXReader reader = new SAXReader();
        //SAXReader对象的read方法需要传入一个输入流,返回Document对象
        Document document = reader.read(is);
        //获取根标签对象,根据xml的写法,这里即beans标签
        Element rootElement = document.getRootElement();
        //解析beans标签
        parseBean(rootElement);
    }

	/**
	 * 解析beans标签
	*/
    private void parseBean(Element rootElement) {
		//获取根标签beans下的所有子标签对象(所有bean标签)
        List<Element> elements = rootElement.elements();
        //每个Element对象即一个个bean标签
        for (Element element : elements) {
            String id = element.attributeValue("id");   //获取bean标签的id属性
            String className = element.attributeValue("class"); //获取bean标签的class属性
            //将id属性和全类名属性封装到BeanDefinition对象中
            BeanDefinition beanDefinition = new BeanDefinition();  
            beanDefinition.setId(id); 
            beanDefinition.setClassName(className);
            //获取bean标签的所有property子标签
            List<Element> list = element.elements("property"); 
            //创建管理PropertyValues对象(一个property标签一个PropertyValues对象)的MutablePropertyValues对象
            MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
            //一个bean标签下可以有多个property标签
            for (Element element1 : list) {
            	//拿到bean标签下的一个个property标签的属性,封装到一个个PropertyValues对象中
                String name = element1.attributeValue("name");
                String ref = element1.attributeValue("ref");
                String value = element1.attributeValue("value");
                PropertyValue propertyValue = new PropertyValue(name,ref,value);
                //一个个PropertyValues对象最终交给mutablePropertyValues
                mutablePropertyValues.addPropertyValue(propertyValue);
            }
            //beanDefinition对象封装彻底完成
            beanDefinition.setPropertyValues(mutablePropertyValues);
			//将beanDefinition对象注册到注册表中存起来
            registry.registerBeanDefinition(id,beanDefinition);
        }
      
    }
}

到此,解析xml,封装成BeanDefiniton,注册到BeanDefinitonRegistry中存起来的事儿完成了。这就是BeanDefinitionReader干的事儿 。注意,上面用dom4j来对xml文件进行解析,jar包坐标:

xml 复制代码
<dependency>
	<groupId>dom4j</groupId>
	<artifactId>dom4j</artifactId>
	<version>1.6.1</version>
</dependency>

以及通过当前类的类加载器获取输入流对象

java 复制代码
InputStream is = this.getClass().getClassLoader().getResourceAsStream(configLocation);

4、IOC容器相关类

4.1 BeanFactory接口

在该接口中,定义IOC容器的统一规范,即获取bean对象。

java 复制代码
public interface BeanFactory {
	//根据bean对象的名称获取bean对象
    Object getBean(String name) throws Exception;
	//根据bean对象的名称获取bean对象,并进行类型转换
    <T> T getBean(String name, Class<? extends T> clazz) throws Exception;
}

4.2 ApplicationContext接口

BeanFactory的子接口,该接口所有的子实现类对bean对象的创建都是非延时的(立即加载),所以在该接口中定义 refresh() 方法,该方法主要完成以下两个功能:

  • 加载配置文件
  • 根据注册表中的BeanDefinition对象封装的数据进行bean对象的创建
java 复制代码
/**
 * 非延时加载
 */
public interface ApplicationContext extends BeanFactory {
	//进行配置文件加载并进行对象创建
    void refresh() throws IllegalStateException, Exception;
}

4.3 AbstractApplicationContext类

  • 作为ApplicationContext接口的子类,所以该类也是非延时加载,所以需要在该类中定义一个Map集合,作为bean对象存储的容器。

  • 声明BeanDefinitionReader类型的变量,用来进行xml配置文件的解析,符合单一职责原则。

而BeanDefinitionReader类型属性的对象创建,则交由子类实现,因为只有子类明确到底创建BeanDefinitionReader哪儿子实现类对象。

java 复制代码
public abstract class AbstractApplicationContext implements ApplicationContext {

	//声明解析器对象,protected修饰,更加方便子类访问
    protected BeanDefinitionReader beanDefinitionReader;
    
    //用来存储bean对象的容器(Map)   key存储的是bean的id值,value存储的是bean对象
    protected Map<String, Object> singletonObjects = new HashMap<String, Object>();

    //存储xml配置文件的路径
    protected String configLocation;
    
	/**
	 * 定义refresh方法
	 */
    public void refresh() throws IllegalStateException, Exception {

        //加载封装注册BeanDefinition
        beanDefinitionReader.loadBeanDefinitions(configLocation);

        //初始化bean(创建Bean对象)
        finishBeanInitialization();
    }

    //bean的初始化
    private void finishBeanInitialization() throws Exception {
    	//铜鼓解析器获取注册表对象
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
        //获取BeanDefinition的名称
        String[] beanNames = registry.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(beanName);
            //进行Bean的初始化
            getBean(beanName);
        }
    }
}

注意:该类finishBeanInitialization()方法中调用getBean()目前是BeanFactory接口的一给抽象方法,以后子类去实现,这里使用到了模板方法模式。

4.4 ClassPathXmlApplicationContext类

该类主要是加载类路径下的配置文件,并进行bean对象的创建,主要完成以下功能:

  • 在构造方法中,创建BeanDefinitionReader对象(实现类)
  • 在构造方法中,调用refresh()方法,用于进行配置文件加载、创建bean对象并存储到容器中
  • 重写父接口中的getBean()方法,并实现依赖注入操作,注入这里依赖注入的实现思路!
java 复制代码
//Ioc容器具体的子实现类,用于加载类路径下的xml的配置文件,创建对应的Bean
public class ClassPathXmlApplicationContext extends AbstractApplicationContext{
	
	//有参构造方法,形参为xml路径
    public ClassPathXmlApplicationContext(String configLocation) {
        this.configLocation = configLocation;
        //构建XmlBeanDefinitionReader对象
        beanDefinitionReader = new XmlBeanDefinitionReader();
        try {
            this.refresh();
        } catch (Exception e) {
        }
    }

    /**
     * 实现getBean方法,根据bean的id属性值获取bean对象
     */
    @Override
    public Object getBean(String name) throws Exception {

        //父类中定义的Map集合,用来存储Bean对象
        //先看Map里有没,有则直接返回
        Object obj = singletonObjects.get(name);
        if(obj != null) {
            return obj;
        }
		//根据解析器获取注册表对象
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
        //根据注册表中获取到封装Bean信息的BeanDefinition对象
        BeanDefinition beanDefinition = registry.getBeanDefinition(name);
        if(beanDefinition == null) {
            return null;
        }
        //获取全类名
        String className = beanDefinition.getClassName();
        Class<?> clazz = Class.forName(className);
        //反射创建对象
        Object beanObj = clazz.newInstance();
        //解析bean标签的property子标签,给反射得到的对象赋值(做依赖注入)
        MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
        //遍历所有的property标签
        for (PropertyValue propertyValue : propertyValues) {
        	//获取属性的名称、值、引用参考
            String propertyName = propertyValue.getName();
            String value = propertyValue.getValue();
            String ref = propertyValue.getRef();
            //根据spring的xml语法,bean标签下的property子标签,value和ref属性肯定只有其中一个
            if(ref != null && !"".equals(ref)) {
				//获取依赖的Bean的对象,getBean方法递归
                Object bean = getBean(ref);
                //根据属性名获取set方法的名称
                String methodName = getSetterMethodNameByFieldName(propertyName);
                //反射获取Bean定义中全类名类的所有的方法对象
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if(method.getName().equals(methodName)) {
                    	//循环判断拿到set方法对象,执行这个set方法,给要返回的beanObj对象的引用属性,调用set方法赋值,赋依赖的bean对象
                    	//到此,完成了依赖注入
                        method.invoke(beanObj,bean);
                    }
                }
            }
			//如果属性是基本类型,即value有值
            if(value != null && !"".equals(value)) {
            	//拿到属性的set方法名称
                String methodName = getSetterMethodNameByFieldName(propertyName);
                //反射拿到set方法对象
                Method method = clazz.getMethod(methodName, String.class);
                //调用set方法,beanObj对象调用set方法,赋值为value
                method.invoke(beanObj,value);
            }
        }
        //将创建的对象,放到Map中,下次可直接获取,不用再反射(单例)
        singletonObjects.put(name,beanObj);
        return beanObj;
    }

    @Override
    public <T> T getBean(String name, Class<? extends T> clazz) throws Exception {

        Object bean = getBean(name);
        //相比前面的getBean,多一个转型
        if(bean != null) {
            return clazz.cast(bean);
        }
        return null;
    }

	//根据属性名获取set方法的名称 name ==> setName,及set + 首字母截取并大写 + 拼装首字母以外的全部
	public static String getSetterMethodNameByFieldName(String fieldName) {
		return "set" + fieldName.substring(0,1).toUpperCase() + fieldName.substring(1);
	}
}

5、测试自定义的IoC

测试上面定义的IoC容器,能否完成xml的解析和读取,并创建出Bean。首先将上面自定义IoC的模块安装到本地仓库:

新建个干净的Maven工程,引入上面的自定义Ioc模块:

xml 复制代码
<dependency>
	<groupId>com.plat</groupId>
	<artifactId>myIoc_spring</artifactId>
	<version>1.0.0</version>
</dependency>

xml中定义Bean:

xml 复制代码
<bean id="userService" class="com.plat.service.impl.UserServiceImpl">
    <property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="com.plat.dao.impl.UserDaoImpl"></bean>

准备测试类:

java 复制代码
@Setter
public class UserServiceImpl implements UserService {

	private UserDao ueserDao;   //声明个UserDao类型的对象

	public UserServiceImpl(){
		System.out.println("userService被创建了");
	}
	public void add(){
		System.out.println("userService ...");
		userDao.add();
	}
}
java 复制代码
public class UserDaoImpl implements UserDao {
	
	public UserDaoImpl(){
		System.out.println("UserDao被创建了");
	}

	public void add(){
		System.out.println("userDao ...");
	}
}

测试:ApplicationContext和ClassPathXmlApplicationContext类均为上面自己定义的:

java 复制代码
public class MyApplication {

	public static void main(String[] args){
		//创建Spring容器对象
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicaitonContext.xml");
		//从容器中获取Bean
		UserService bean = applicationContext.getBean("userService", UserService.class);
		bean.add();

	}
}

运行,看到Bean和依赖注入的Dao对象都成功了,且Debug可以看到依旧是非延时加载Bean。

6、自定义Spring IOC总结

自定义IoC使用到的设计模式:

  • 工厂模式:使用了工厂模式 + 配置文件(xml配置,
  • 单例模式:Spring IOC管理的bean对象都是单例的,此处的单例不是通过构造器进行单例的控制的,而是spring框架对每一个bean只创建了一个对象
  • 模板方法模式:AbstractApplicationContext类中的finishBeanInitialization()方法调用了子类的getBean()方法,因为getBean()的实现和环境息息相关
  • 迭代器模式:对于MutablePropertyValues类定义使用到了迭代器模式,因为此类存储并管理PropertyValue对象,也属于一个容器,所以给该容器提供一个遍历方式

spring框架其实使用到了很多设计模式,如AOP使用到了代理模式,选择JDK代理或者CGLIB代理使用到了策略模式,还有适配器模式,装饰者模式,观察者模式等。

相关推荐
.生产的驴4 分钟前
SpringBoot 消息队列RabbitMQ 消息确认机制确保消息发送成功和失败 生产者确认
java·javascript·spring boot·后端·rabbitmq·负载均衡·java-rabbitmq
.生产的驴4 分钟前
SpringBoot 消息队列RabbitMQ在代码中声明 交换机 与 队列使用注解创建
java·spring boot·分布式·servlet·kafka·rabbitmq·java-rabbitmq
idealzouhu18 分钟前
Java 并发编程 —— AQS 抽象队列同步器
java·开发语言
听封22 分钟前
Thymeleaf 的创建
java·spring boot·spring·maven
写bug写bug28 分钟前
6 种服务限流的实现方式
java·后端·微服务
楠枬39 分钟前
双指针算法
java·算法·leetcode
奔驰的小野码44 分钟前
java通过org.eclipse.milo实现OPCUA客户端进行连接和订阅
java·开发语言
huapiaoy1 小时前
Spring mvc
java·spring·mvc
风控牛1 小时前
【chromedriver编译-绕过selenium机器人检测】
java·python·selenium·测试工具·安全·机器人·行为验证