详解23种设计模式(基于Java)—— 综合练习之自定义Spring IoC(五 / 五)

x33g5p2x  于2021-11-12 转载在 Java  
字(25.8k)|赞(0)|评价(0)|浏览(257)

本文章笔记整理来自黑马视频https://www.bilibili.com/video/BV1Np4y1z7BU,相关资料可在评论区获取。

5.自定义Spring框架

下面将根据前面所学的设计模式的相关知识,来实现Spring中的核心功能IoC中的一小部分。

5.1.Spring介绍

(1)有关Spring的介绍可以查看Spring——入门介绍这篇文章,此处就不再赘述。
(2)简单使用
① 在IDEA中创建一个Maven工程,并在pom.xml中引入Spring的相关依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itheima</groupId>
    <artifactId>design_pattern_spring_demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>
    </dependencies>
</project>

② 数据访问层,定义UserDao接口及其子实现类
UserDao.java

public interface UserDao {
	public void add();
}

UserDaoImpl.java

public class UserDaoImpl implements UserDao { 
	public void add() { 
		System.out.println("userDaoImpl ...."); 
	} 
}

③ 业务逻辑层,定义UserService接口及其子实现类
UserService.java

public interface UserService { 
	public void add(); 
}

UserServiceImpl.java

public class UserServiceImpl implements UserService {
    
    //声明一个UserDao类型的变量
    private UserDao userDao;
    
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    
    @Override
    public void add() {
        System.out.println("UserService...");
        userDao.add();
    }
}

④ 定义UserController类,使用main方法模拟controller层
UserController.java

public class UserController {
    public static void main(String[] args) throws Exception {
        //1.创建spring容器对象
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");      
        //2.从容器对象中获取userService对象
        UserService userService = applicationContext.getBean("userService", UserService.class);
        //3.调用userService中的方法进行业务逻辑处理
        userService.add();
    }
}

⑤ 编写配置文件,在类路径下编写一个名为ApplicationContext.xml的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>

    <bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>

</beans>

⑥ 测试,即运行UserController.java中的main方法,得到的结果如下:

UserService...
userDaoImpl ....

(3)通过上面代码及结果可以看出:
① userService对象是从applicationContext容器对象获取到的,也就是userService对象交由spring进行管理。
② 上面结果可以看到调用了UserDao对象中的add方法,也就是说UserDao子实现类对象也交由spring管理了。
③ UserService中的userDao变量我们并没有进行赋值,但是可以正常使用,说明spring已经将
UserDao对象赋值给了userDao变量。
上面三点体现了Spring框架的IOC(Inversion of Control)和DI(Dependency Injection, DI)

5.2.Spring核心功能结构

(1)Spring大约有20个模块,由1300多个不同的文件构成。这些模块可以分为:核心容器、AOP和设备支持、数据访问与集成、Web组件、通信报文和集成测试等,Spring框架的总体架构图如下:

(2)核心容器由 beans、core、context 和 expression(Spring Expression Language,SpEL)4个模块组成。
① spring-beans和spring-core模块是Spring框架的核心模块,包含了控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)。BeanFactory使用控制反转对应用程序的配置和依赖性规范与实际的应用程序代码进行了分离。BeanFactory属于延时加载,也就是说在实例化容器对象后并不会自动实例化Bean,只有当Bean被使用时,BeanFactory才会对该 Bean 进行实例化与依赖关系的装配。
② spring-context模块构架于核心模块之上,扩展了BeanFactory,为它添加了Bean生命周期控制、框架事件体系及资源加载透明化等功能。此外,该模块还提供了许多企业级支持,如邮件访问、任务调度等,ApplicationContext 是该模块的核心接口,其超类是BeanFactory。与BeanFactory不同,ApplicationContext实例化后会自动对所有的单实例Bean进行实例化与依赖关系的装配,使之处于待用状态。
③ spring-context-support模块是对Spring IoC容器及 IoC 子容器的扩展支持。
④ spring-context-indexer模块是Spring的类管理组件和Classpath扫描组件。
⑤ spring-expression模块是统一表达式语言(EL)的扩展模块,可以查询、管理运行中的对象,同时也可以方便地调用对象方法,以及操作数组、集合等。它的语法类似于传统EL,但提供了额外的功能,最出色的要数函数调用和简单字符串的模板函数。EL的特性是基于Spring产品的需求而设计的,可以非常方便地同Spring IoC进行交互。

5.3.bean概述

(1)什么是bean?
① 先来看看Spring官方文档中对bean的解释:

In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are 
called beans.A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container.

② 翻译过来就是:

在Spring中,构成应用程序主干并由Spring IoC容器管理的对象称为bean。bean是由SpringIOC容器实例化、组装和管理的对象。

(2)Spring 就是面向 Bean 的编程(BOP, Bean Oriented Programming),Bean 在 Spring 中处于核心地位。Bean对于Spring的意义就像Object对于OOP的意义一样,Spring中没有Bean也就没有Spring存在的意义。Spring IoC容器通过配置文件或者注解的方式来管理bean对象之间的依赖关系。
(3)Spring中的bean用于对一个类进行封装,如下面的配置:

<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"> 
	<property name="userDao" ref="userDao"></property>
</bean> 
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>

(4)bean的重要性:

spring 将bean对象交由一个叫IOC容器进行管理。
bean对象之间的依赖关系在配置文件中体现,并由spring完成。

5.4.Spring IoC相关接口分析

5.4.1.BeanFactory

(1)Spring中Bean的创建是典型的工厂模式,这一系列的Bean工厂,即 IoC 容器,为开发者管理对象之间的依赖关系提供了很多便利和基础服务,在Spring中有许多 IoC 容器的实现供用户选择,其相互关系如下图所示。

(2)其中,BeanFactory作为最顶层的一个接口,定义了IoC容器的基本功能规范,BeanFactory有三个重要的子接口:ListableBeanFactoryHierarchicalBeanFactoryAutowireCapableBeanFactory。但是从类图中我们可以发现最终的默认实现类是DefaultListableBeanFactory,它实现了所有的接口。

(3)定义这么多层次的接口的目的在于:区分在Spring内部操作过程中对象的传递和转化,对对象的数据访问所做的限制。 例如:

ListableBeanFactory表示这些Bean可列表化
HierarchicalBeanFactory表示这些Bean 是有继承关系的,也就是每个Bean可能有父Bean
AutowireCapableBeanFactory表示接口定义Bean的自动装配规则

(4)这三个接口共同定义了Bean的集合、Bean之间的关系及Bean行为。最基本的IoC容器接口是BeanFactory,来看一下它的源码:
BeanFactory.java

package org.springframework.beans.factory;

import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;

public interface BeanFactory {

	String FACTORY_BEAN_PREFIX = "&";
	
	//根据bean的名称获取IOC容器中的的bean对象
	Object getBean(String name) throws BeansException;

	//根据bean的名称获取IOC容器中的的bean对象,并指定获取到的bean对象的类型,这样使用时就不需要进行类型强转
	<T> T getBean(String name, Class<T> requiredType) throws BeansException;

	Object getBean(String name, Object... args) throws BeansException;

	<T> T getBean(Class<T> requiredType) throws BeansException;

	<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

	<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);

	<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
	
	//判断容器中是否包含指定名称的bean对象
	boolean containsBean(String name);
	
	//根据bean的名称判断是否是单例
	boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

	boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

	boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

	boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

	@Nullable
	Class<?> getType(String name) throws NoSuchBeanDefinitionException;

	@Nullable
	Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;

	String[] getAliases(String name);
}

在BeanFactory里只对IoC容器的基本行为做了定义,根本不关心你的Bean是如何定义及怎样加载的。正如我们只关心能从工厂里得到什么产品,不关心工厂是怎么生产这些产品的。

(5)BeanFactory有一个很重要的子接口,就是ApplicationContext接口,该接口主要来规范容器中的bean对象是非延时加载,即在创建容器对象的时候就对象bean进行初始化,并存储到一个容器中。

要知道工厂是如何产生对象的,我们需要看具体的IoC容器实现,Spring提供了许多IoC容器实现,例如:

ClasspathXmlApplicationContext根据类路径加载xml配置文件,并创建 IoC 容器对象
FileSystemXmlApplicationContext根据系统路径加载xml配置文件,并创建 IoC 容器对象
AnnotationConfigApplicationContext加载注解类配置,并创建 IoC 容器

5.4.2.BeanDefinition

(1)Spring IoC容器管理我们定义的各种Bean对象及其相互关系,而Bean对象在Spring实现中是以BeanDefinition来描述的,如下面配置文件:

<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean> 
<!--bean标签还有很多属性: scope、init-method、destory-method等。-->

(2)其继承体系如下图所示:

5.4.3.BeanDefinitionReader

(1)Bean的解析过程非常复杂,功能被分得很细,因为这里需要被扩展的地方很多,必须保证足够的灵活性,以应对可能的变化。Bean的解析主要就是对Spring配置文件的解析。该过程主要通过BeanDefinitionReader来完成,Spring中BeanDefinitionReader的类结构图如下所示:

(2)看看BeanDefinitionReader接口定义的功能来理解它具体的作用:
BeanDefinitionReader.java

package org.springframework.beans.factory.support;

import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.lang.Nullable;

public interface BeanDefinitionReader {
	//获取BeanDefinitionRegistry注册器对象
	BeanDefinitionRegistry getRegistry();

	@Nullable
	ResourceLoader getResourceLoader();

	@Nullable
	ClassLoader getBeanClassLoader();
	
	BeanNameGenerator getBeanNameGenerator();

	//下面的loadBeanDefinitions都是从指定的资源中加载bean定义,
	int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;

	int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;

	int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;

	int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
}

5.4.4.BeanDefinitionRegistry

(1)BeanDefinitionReader用来解析bean定义,并封装BeanDefinition对象,而我们定义的配置文件中定义了很多bean标签,所以就有一个问题,解析的BeanDefinition对象存储到哪儿?答案就是BeanDefinition的注册中心,而该注册中心顶层接口就是BeanDefinitionRegistry。
BeanDefinitionRegistry.java

package org.springframework.beans.factory.support;

import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.core.AliasRegistry;

public interface BeanDefinitionRegistry extends AliasRegistry {
	
	//往注册表中注册bean
	void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException;
	
	//从注册表中删除指定名称的bean
	void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

	//获取注册表中指定名称的bean
	BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

	//判断注册表中是否已经注册了指定名称的bean
	boolean containsBeanDefinition(String beanName);

	//获取注册表中所有的bean的名称
	String[] getBeanDefinitionNames();

	int getBeanDefinitionCount();

	boolean isBeanNameInUse(String beanName);
}

(2)其继承结构图如下:

从上面类图可以看到 BeanDefinitionRegistry 接口的子实现类主要有以下两个:
① DefaultListableBeanFactory
在该类中定义了如下代码,就是用来注册bean

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

② SimpleBeanDefinitionRegistry
在该类中定义了如下代码,就是用来注册bean

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(64);

5.4.5.创建容器

(1)ClassPathXmlApplicationContext对Bean配置资源的载入是从 refresh() 方法开始的。
(2)refresh() 方法是一个模板方法,规定了 IoC 容器的启动流程,有些逻辑要交给其子类实现。它对Bean配置资源进行载入,ClassPathXmlApplicationContext通过调用其父类AbstractApplicationContext的 refresh() 方法启动整个IoC容器对Bean定义的载入过程。

5.5.自定义Spring IoC

先创建一个Maven工程,自定义完成后的完整的项目结构如下图所示:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itheima</groupId>
    <artifactId>customized_spring</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
    	<!--用于解析XML文件-->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
    </dependencies>
</project>

5.5.1.定义bean相关的pojo类

(1)PropertyValue类
用于封装bean的属性,体现到上面的配置文件就是封装bean标签的子标签property标签数据。
PropertyValue.java

package com.itheima.framework.beans;

/* * 用来封装bean标签下的property标签的属性 * name属性 * ref属性 * value属性: 给基本数据类型及String类型数据赋的值 * */
public class PropertyValue {
    private String name;
    private String ref;
    private String 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;
    }
    
    public PropertyValue() {
    }
    
    public PropertyValue(String name, String ref, String value) {
        this.name = name;
        this.ref = ref;
        this.value = value;
    }
}

(2)MutablePropertyValues类
一个bean标签可以有多个property子标签,所以再定义一个MutablePropertyValues类,用来存储并管理多个PropertyValue对象。
MutablePropertyValues.java

package com.itheima.framework.beans;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

//用户存储和管理多个PropertyValue对象(迭代器模式)
public class MutablePropertyValues implements Iterable<PropertyValue>{
    
    //定义list集合对象,用来存储PropertyValue对象
    private final List<PropertyValue> propertyValueList;
    
    public MutablePropertyValues() {
        this.propertyValueList = new ArrayList<>();
    }
    
    public MutablePropertyValues(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 getPropertyValue(String propertyName) {
        //遍历集合对象
        for (PropertyValue propertyValue : propertyValueList) {
            if (propertyValue.getName().equals(propertyName)) {
                return propertyValue;
            }
        }
        return null;
    }
    
    //判断集合是否为空
    public boolean isEmpty(){
        return propertyValueList.isEmpty();
    }
    
    //添加PropertyValue对象
    public MutablePropertyValues addPropertyValue(PropertyValue pv) {
        //判断集合中存储的PropertyValue对象是否和传递进行的重复了,如果重复了,则进行覆盖
        for (int i = 0; i < propertyValueList.size(); i++) {
            //获取集合中每一个PropertyValue对象
            PropertyValue currentPv = propertyValueList.get(i);
            if(currentPv.getName().equals(pv.getName())) {
                propertyValueList.set(i,pv);
                //目的是实现链式编程
                return this;
            }
        }
        this.propertyValueList.add(pv);
        //目的是实现链式编程
        return this;
    }
    
    //判断是否有指定name属性值的对象
    public boolean contains(String propertyName){
        return getPropertyValue(propertyName)!=null;
    }
    
    //获取迭代器对象
    @Override
    public Iterator<PropertyValue> iterator() {
        return propertyValueList.iterator();
    }
}

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

package com.itheima.framework.beans;

/* * 用来封装bean标签数据 id属性 class属性 property子标签的数据 * */
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;
    }
}

5.5.2.定义注册表相关类

(1) BeanDefinitionRegistry接口
BeanDefinitionRegistry接口定义了注册表的相关操作,定义如下功能:
① 注册BeanDefinition对象到注册表中
② 从注册表中删除指定名称的BeanDefinition对象
③ 根据名称从注册表中获取BeanDefinition对象
④ 判断注册表中是否包含指定名称的BeanDefinition对象
⑤ 获取注册表中BeanDefinition对象的个数
⑥ 获取注册表中所有的BeanDefinition的名称
BeanDefinitionRegistry.java

package com.itheima.framework.beans.factory.support;

import com.itheima.framework.beans.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();
}

(2) SimpleBeanDefinitionRegistry类
该类实现了BeanDefinitionRegistry接口,定义了Map集合作为注册表容器。
SimpleBeanDefinitionRegistry.java

package com.itheima.framework.beans.factory.support;

import com.itheima.framework.beans.BeanDefinition;

import java.util.HashMap;
import java.util.Map;

//注册表接口的子实现类
public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry {

    //定义一个容器,用来存储BeanDefinition对象
    private Map<String,BeanDefinition> beanDefinitionMap = new HashMap<String, BeanDefinition>();

    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
        beanDefinitionMap.put(beanName,beanDefinition);
    }

    public void removeBeanDefinition(String beanName) throws Exception {
        beanDefinitionMap.remove(beanName);
    }

    public BeanDefinition getBeanDefinition(String beanName) throws Exception {
        return beanDefinitionMap.get(beanName);
    }

    public boolean containsBeanDefinition(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

    public int getBeanDefinitionCount() {
        return beanDefinitionMap.size();
    }

    public String[] getBeanDefinitionNames() {
        return beanDefinitionMap.keySet().toArray(new String[0]);
    }
}

5.5.3.定义解析器相关类

(1)BeanDefinitionReader接口
BeanDefinitionReader是用来解析配置文件并在注册表中注册bean的信息。定义了两个规范:
① 获取注册表的功能,让外界可以通过该对象获取注册表对象。
② 加载配置文件,并注册bean数据。
BeanDefinitionReader.java

package com.itheima.framework.beans.factory.support;

//用来解析配置文件的,而该接口只是定义了规范
public interface BeanDefinitionReader {
    //获取注册表对象
    BeanDefinitionRegistry getRegistry();
    //加载配置文件并在注册表中进行注册
    void loadBeanDefinitions(String configLocation) throws Exception;
}

(2)XmlBeanDefinitionReader类
XmlBeanDefinitionReader类是专门用来解析xml配置文件的。该类实现BeanDefinitionReader接口并实现接口中的两个功能。
XmlBeanDefinitionReader.java

package com.itheima.framework.beans.factory.xml;

import com.itheima.framework.beans.BeanDefinition;
import com.itheima.framework.beans.MutablePropertyValues;
import com.itheima.framework.beans.PropertyValue;
import com.itheima.framework.beans.factory.support.BeanDefinitionReader;
import com.itheima.framework.beans.factory.support.BeanDefinitionRegistry;
import com.itheima.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;

//针对xml配置文件进行解析的类
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 {
        //使用dom4j进行xml配置文件的解析
        SAXReader reader = new SAXReader();
        //获取类路径下的配置文件
        InputStream is = XmlBeanDefinitionReader.class.getClassLoader().getResourceAsStream(configLocation);
        Document document = reader.read(is);
        //根据Document对象获取根标签对象 (beans)
        Element rootElement = document.getRootElement();
        //获取根标签下所有的bean标签对象
        List<Element> beanElements = rootElement.elements("bean");
        for (Element beanElement : beanElements) {
            //获取id属性
            String id = beanElement.attributeValue("id");
            //获取class属性
            String className = beanElement.attributeValue("class");
            //将id属性和class属性封装到BeanDefinition对象中
            //(1)创建BeanDefinition
            BeanDefinition beanDefinition = new BeanDefinition();
            beanDefinition.setId(id);
            beanDefinition.setClassName(className);
    
            //创建MutablePropertyValues对象
            MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
            
            //(2)获取bean标签下所有的property标签对象
            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);
            }
            //将mutablePropertyValues对象封装到BeanDefinition对象中
            beanDefinition.setPropertyValues(mutablePropertyValues);
    
            //将beanDefinition对象注册到注册表中
            registry.registerBeanDefinition(id,beanDefinition);
        }
    }
}

5.5.4.IOC容器相关类

(1) BeanFactory接口
在该接口中定义IOC容器的统一规范即获取bean对象。
BeanFactory.java

package com.itheima.framework.beans.factory;

//IoC容器父接口
public interface BeanFactory {
    
    Object getBean(String name) throws Exception;
    
    <T> T getBean(String name, Class<? extends T> clazz) throws Exception;
}

(2) ApplicationContext接口
该接口的所以的子实现类对bean对象的创建都是非延时的,所以在该接口中定义 refresh() 方法,该方法主要完成以下两个功能:
① 加载配置文件。
② 根据注册表中的BeanDefinition对象封装的数据进行bean对象的创建。
ApplicationContext.java

package com.itheima.framework.context;

import com.itheima.framework.beans.factory.BeanFactory;

//定义非延时加载功能
public interface ApplicationContext extends BeanFactory {
    void refresh() throws Exception;
}

(3) AbstractApplicationContext类
① 作为ApplicationContext接口的子类,所以该类也是非延时加载,所以需要在该类中定义一个Map集合,作为bean对象存储的容器。
② 声明BeanDefinitionReader类型的变量,用来进行xml配置文件的解析,符合单一职责原则。BeanDefinitionReader类型的对象创建交由子类实现,因为只有子类明确到底创建BeanDefinitionReader哪儿个子实现类对象。

package com.itheima.framework.context.support;

import com.itheima.framework.beans.factory.support.BeanDefinitionReader;
import com.itheima.framework.beans.factory.support.BeanDefinitionRegistry;
import com.itheima.framework.context.ApplicationContext;

import java.util.HashMap;
import java.util.Map;

//ApplicationContext接口的子实现类,用于立即加载
public abstract class AbstractApplicationContext implements ApplicationContext {
    
    //声明解析器变量
    protected BeanDefinitionReader beanDefinitionReader;
    
    //定义用于存储bean对象的map容器
    protected Map<String, Object> singletonObjects = new HashMap<String, Object>();
    
    //声明配置文件路径的变量
    protected String configLocation;
    
    @Override
    public void refresh() throws Exception {
        //加载BeanDefinition对象
        beanDefinitionReader.loadBeanDefinitions(configLocation);
        //初始化bean
        finishBeanInitialization();
    }
    
    //bean的初始化
    private void finishBeanInitialization() throws Exception {
        //获取注册表对象
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
        
        //获取BeanDefinition对象
        String[] beanNames = registry.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            //进行bean的初始化
            getBean(beanName);
        }
    }
}

注意:该类finishBeanInitialization()方法中调用getBean()方法使用到了模板方法模式。

(4) ClassPathXmlApplicationContext类
该类主要是加载类路径下的配置文件,并进行bean对象的创建,主要完成以下功能:
① 在构造方法中,创建BeanDefinitionReader对象。
② 在构造方法中,调用refresh()方法,用于进行配置文件加载、创建bean对象并存储到容器中。
③ 重写父接口中的getBean()方法,并实现依赖注入操作。
StringUtils.java

package com.itheima.framework.utils;

public class StringUtils {
    private StringUtils() {
    }

    // userDao ==> setUserDao
    public static String getSetterMethodByFieldName(String fieldName) {
        String methodName = "set" + fieldName.substring(0,1).toUpperCase() + fieldName.substring(1);
        return methodName;
    }
}

ClassPathXmlApplicationContext.java

package com.itheima.framework.context.support;

import com.itheima.framework.beans.BeanDefinition;
import com.itheima.framework.beans.MutablePropertyValues;
import com.itheima.framework.beans.PropertyValue;
import com.itheima.framework.beans.factory.support.BeanDefinitionRegistry;
import com.itheima.framework.beans.factory.xml.XmlBeanDefinitionReader;
import com.itheima.framework.utils.StringUtils;

import java.lang.reflect.Method;

//IOC容器具体的子实现类
public class ClassPathXmlApplicationContext extends AbstractApplicationContext {
    
    public ClassPathXmlApplicationContext(String configLocation) {
        this.configLocation = configLocation;
        //构建解析器对象
        beanDefinitionReader = new XmlBeanDefinitionReader();
        try{
            this.refresh();
        } catch (Exception e) {
        
        }
    }
    
    //根据bean对象的名称获取bean对象
    public Object getBean(String name) throws Exception {
        //判断对象容器中是否包含指定名称的bean对象,如果包含,直接返回即可,如果不包含,需要自行创建
        Object obj = singletonObjects.get(name);
        if (obj != null) {
            return obj;
        }
        
        //获取BeanDefinition对象
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
        BeanDefinition beanDefinition = registry.getBeanDefinition(name);
        //获取bean信息中的className
        String className = beanDefinition.getClassName();
        //通过反射创建对象
        Class<?> clazz = Class.forName(className);
        Object beanObj = clazz.newInstance();
        
        //进行依赖注入操作
        MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
        for (PropertyValue propertyValue : propertyValues) {
            //获取name属性值
            String propertyName = propertyValue.getName();
            //获取value属性
            String value = propertyValue.getValue();
            //获取ref属性
            String ref = propertyValue.getRef();
            if(ref != null && !"".equals(ref)) {
                //获取依赖的bean对象
                Object bean = getBean(ref);
                //拼接方法名
                String methodName = StringUtils.getSetterMethodByFieldName(propertyName);
                //获取所有的方法对象
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if (methodName.equals(method.getName())) {
                        //执行该setter方法
                        method.invoke(beanObj,bean);
                    }
                }
            }
            
            if(value != null && !"".equals(value)) {
                //拼接方法名
                String methodName = StringUtils.getSetterMethodByFieldName(propertyName);
                //获取method对象
                Method method = clazz.getMethod(methodName, String.class);
                method.invoke(beanObj,value);
            }
        }
        
        //在返回beanObj对象之前,将该对象存储到map容器中
        singletonObjects.put(name,beanObj);
        return beanObj;
    }
    
    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);
    }
}

5.5.5.测试

(1)上述代码编写完成后,先将该项目安装到本地仓库

(2)在5.1中的已有项目中进行测试
① 修改pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itheima</groupId>
    <artifactId>design_pattern_spring_demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!--<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.0.RELEASE</version> </dependency>-->
        <!--使用自定义的Spring框架-->
        <dependency>
            <groupId>com.itheima</groupId>
            <artifactId>customized_spring</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

② 修改UserController.java中相关类的包

package com.itheima.controller;

import com.itheima.framework.context.ApplicationContext;
import com.itheima.framework.context.support.ClassPathXmlApplicationContext;
import com.itheima.service.UserService;

public class UserController {
    public static void main(String[] args) throws Exception {
        //1.创建spring容器对象
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        //2.从容器对象中获取userService对象
        UserService userService = applicationContext.getBean("userService", UserService.class);
        //3.调用userService中的方法进行业务逻辑处理
        userService.add();
    }
}

结果与使用Spring框架的一样

UserService...
userDaoImpl ....

至此,一个简单的自定义的Spring IoC就实现了。

5.6.总结

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

(2)Spring框架其实使用到了很多设计模式,如AOP使用到了代理模式,选择JDK代理或者CGLIB代理使用到了策略模式,还有适配器模式,装饰者模式,观察者模式等。有关Spring中用到了哪些设计模式的问题,可以查看面试官:说说Spring用到了哪些设计模式?这篇文章。

相关文章