Design Pattern_Custom Spring Framework (IOC)

Design Pattern_Custom Spring Framework (IOC)

The notes are organized from the detailed explanation of Java design patterns by dark horse programmers, 23 Java design patterns (diagram + framework source code analysis + actual combat)

Review of Spring usage

Before customizing the spring framework, review the use of the Spring framework to analyze the core of Spring and simulate the core functions.

  • data access layer. Define the UserDao interface and its sub-implementation classes

    public interface UserDao {
          
          
        public void add();
    }
    
    public class UserDaoImpl implements UserDao {
          
          
    
        public void add() {
          
          
            System.out.println("UserDao ...");
        }
    }
    
  • Business logic layer. Define the UserService interface and its sub-implementation classes

    public interface UserService {
          
          
        public void add();
    }
    
    public class UserServiceImpl implements UserService {
          
          
    
        private UserDao userDao;
    
        public void setUserDao(UserDao userDao) {
          
          
            this.userDao = userDao;
        }
    
        public void add() {
          
          
            System.out.println("UserService ...");
            userDao.add();
        }
    }
    
  • Define the UserController class and use the main method to simulate the controller layer

    public class UserController {
          
          
        public static void main(String[] args) {
          
          
            // 1.创建spring容器对象
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
            // 2.从IOC容器中获取UserService对象
            UserService userService = applicationContext.getBean("userService", UserService.class);
            // 3.调用UserService对象的add方法 进行业务处理
            userService.add();
        }
    }
    
  • Write configuration files. Write a configuration file named applicationContext.xml under the class path

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

    The result of running the code is as follows:

    image-20230110120208942

From the above code and results, we can see that:

  • The userService object is obtained from the applicationContext container object, that is, the userService object is managed by Spring.
  • From the above results, we can see that the add method in the UserDao object is called, which means that the UserDao sub-implementation class object is also managed by Spring.
  • We have not assigned a value to the userDao variable in UserService, but it can be used normally, indicating that Spring has assigned the UserDao object to the userDao variable.

The above three points reflect the IOC (Inversion of Control) and DI (Dependency Injection, DI) of the Spring framework

Spring core function structure

Spring has about 20 modules made up of more than 1300 different files. These modules can be divided into:

Core container, AOP and device support, data access and integration, Web components, communication messages and integration testing, etc., the following is the overall architecture diagram of the Spring framework:

The core container consists of four modules: beans, core, context and expression (Spring Expression Language, SpEL).

  • The spring-beans and spring-core modules are the core modules of the Spring framework, including Inversion of Control (IOC) and Dependency Injection (DI). The BeanFactory uses inversion of control to separate the application's configuration and dependency specification from the actual application code. The BeanFactory belongs to delayed loading, that is to say, the Bean will not be instantiated automatically after the container object is instantiated. Only when the Bean is used, the BeanFactory will instantiate the Bean and assemble the dependencies.
  • The spring-context module is built on top of the core module, which extends BeanFactory and adds functions such as Bean life cycle control, framework event system and resource loading transparency. In addition, this module also provides many enterprise-level supports, such as mail access, remote access, task scheduling, etc. ApplicationContext is the core interface of this module, and its superclass is BeanFactory. Different from BeanFactory, after ApplicationContext is instantiated, it will automatically instantiate and assemble dependencies for all single-instance beans, making them in a standby state.
  • The spring-context-support module is an extended support for Spring IoC container and IoC sub-container.
  • The spring-context-indexer module is Spring's class management component and Classpath scanning component.
  • The spring-expression module is an extension module of the Unified Expression Language (EL), which can query and manage running objects, and can also conveniently call object methods, and operate arrays, collections, etc. Its syntax is similar to traditional EL, but provides additional features, most notably function calls and template functions for simple strings. The features of EL are designed based on the requirements of Spring products, and can interact with Spring IoC very conveniently.

bean overview

Spring is Bean Oriented BeanProgramming (BOP, Bean Oriented Programming), and Bean is at the core of Spring. The meaning of Bean to Spring is the same as that of Object to OOP. Without Bean in Spring, there is no meaning of Spring. The Spring IoC container manages the dependencies between bean objects through configuration files or annotations.

Bean in Spring is used to encapsulate a class. Such as the following configuration:

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

Why are beans so important?

  • Spring hands over the bean object to a container called IOC for management.
  • Dependencies between bean objects are reflected in configuration files and are done by Spring.

Spring IOC related interface analysis

BeanFactory analysis

The creation of beans in Spring is a typical factory pattern. This series of bean factories, namely IoC containers, provide developers with a lot of convenience and basic services for managing dependencies between objects. There are many implementations of IoC containers in Spring for The user selects, and the relationship between them is shown in the figure below.

Among them, BeanFactory, as the top-level interface, defines the basic functional specifications of the IoC container. BeanFactory has three important sub-interfaces:

  • ListableBeanFactory
  • HierarchicalBeanFactory
  • AutowireCapableBeanFactory

But from the class diagram, we can find that the final default implementation class is DefaultListableBeanFactory, which implements all interfaces.

So why define so many levels of interfaces?

Each interface has its usage occasions, mainly to distinguish object transfer and conversion in the Spring internal operation process, and the restrictions on object data access. For example,

  • The ListableBeanFactory interface indicates that these beans can be listized (bean objects can be stored in the form of a list).
  • HierarchicalBeanFactory indicates that these beans have an inheritance relationship, that is, each bean may have a parent bean.
  • The AutowireCapableBeanFactory interface defines the autowiring rules for a Bean.

These three interfaces jointly define the collection of beans, the relationship between beans and the behavior of beans. The most basic IoC container interface is BeanFactory, take a look at its source code:

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;
	String[] getAliases(String name);
}

In BeanFactory, only the basic behavior of the IoC container is defined, and it doesn't care how your Bean is defined and loaded. Just as we only care about what products we can get from the factory, we don't care how the factory produces these products.

BeanFactory has a very important sub-interface, which is the ApplicationContext interface. This interface is mainly used to regulate the non-delayed loading of bean objects in the container, that is, when the container object is created, the object bean is initialized and stored in a container.

To know how the factory generates objects, we need to look at the specific IoC container implementation. Spring provides many IoC container implementations, such as:

  • ClasspathXmlApplicationContext: Load the xml configuration file according to the classpath and create an IOC container object.
  • FileSystemXmlApplicationContext: Load the xml configuration file according to the system path and create an IOC container object.
  • AnnotationConfigApplicationContext: Load annotation class configuration and create IOC container.

BeanDefinition analysis

The Spring IoC container manages the various Bean objects we define and their interrelationships, and the Bean objects are described by BeanDefinition in the Spring implementation, as shown in the following configuration file

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

bean标签还有很多属性:
	scope、init-method、destory-method等。

Its inheritance system is shown in the following figure:

BeanDefinitionReader parsing

The bean parsing process is very complicated, and the functions are divided into fine pieces, because there are many places that need to be extended, and sufficient flexibility must be ensured to cope with possible changes. The analysis of beans is mainly the analysis of Spring configuration files. This parsing process is mainly done through BeanDefinitionReader. Look at the class structure diagram of BeanDefinitionReader in Spring, as shown in the following figure:

The above figure is only a part of the structure. We can see that the sub-implementation classes of BeanDefinitionReader include Propertiesand Xml. PropertiesBeanDefinitionReader is a configuration file specially parsed .properties, but most of us use XmlBeanDefinitionReader to parse .xmlconfiguration files of .

image-20230110153834330

Look at the functions defined by the BeanDefinitionReader interface to understand its specific role:

public interface BeanDefinitionReader {
    
    

	// 获取BeanDefinitionRegistry注册器对象
	BeanDefinitionRegistry getRegistry();

	@Nullable       

	@Nullable
	ClassLoader getBeanClassLoader();

	BeanNameGenerator getBeanNameGenerator();

	/*
	 * 下面的loadBeanDefinitions都是加载bean定义,从指定的资源(配置文件)中,封装成BeanDefinition对象
	 */
	int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
	int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
	int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
	int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
}

BeanDefinitionRegistry parsing

BeanDefinitionReader is used to parse the bean definition and encapsulate the BeanDefinition object, and the configuration file we defined defines many bean tags, so there is a question, where are the parsed BeanDefinition objects stored? The answer is the registration center of BeanDefinition, and the top-level interface of the registration center is BeanDefinitionRegistry.

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

The inheritance structure diagram is as follows:

From the above class diagram, we can see that the sub-implementation classes of the BeanDefinitionRegistry interface mainly include the following:

  • SimpleBeanDefinitionRegistry

    It is a simple BeanDefinition registration center, because the parsed BeanDefinition object is stored in the BeanDefinition registration center, so it must be a container. This class requires more attention.

    The following code is defined in this class, which is used to register beans:

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

    This class not only implements the BeanFactory interface, but also implements the BeanDefinitionRegistry interface, so this class is both a container and a registry.

    The following code is defined in this class, which is used to register beans:

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

    This class indirectly implements the ApplicationContext interface, so it is both a container and a registry.

create container

Let's look at the source code of the container class ClassPathXmlApplicationContext to briefly analyze the process of creating a container:

public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {
    
    
    
    // 无参构造方法
    public ClassPathXmlApplicationContext() {
    
    
    }

    // 再传入一个ApplicationContext对象,即父容器
    public ClassPathXmlApplicationContext(ApplicationContext parent) {
    
    
       super(parent);
    }

    /*
     * 我们创建ClassPathXmlApplicationContext时,一般都传递的是一个字符串,就是使用的这个构造方法
     * configLocation:就是类路径下面的配置文件的路径
     * 它底层又套娃的另一个构造方法
     */
    public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
    
    
       this(new String[] {
    
    configLocation}, true, null);
    }
    	||
        \/
    /*
     * @param
     * String[] configLocations:配置文件位置
     * boolean refresh:上面的构造方法传入 refresh值固定为true
     * ApplicationContext parent:上面的构造方法传入 父容器固定为null
     */
    public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {
    
    

		super(parent);
		setConfigLocations(configLocations);
        // 判断refresh是否为true 在此逻辑中必定满足 进入if
		if (refresh) {
    
    
            // 真正的核心就是这个 refresh 方法
			refresh();
		}
	}

refresh()

    // 父类AbstractApplicationContext中的方法
	@Override
    public void refresh() throws BeansException, IllegalStateException {
    
    
       synchronized (this.startupShutdownMonitor) {
    
    
          StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

          // Prepare this context for refreshing. 准备此容器以进行刷新。
          prepareRefresh(); // #1 准备刷新上下文环境

          // Tell the subclass to refresh the internal bean factory. 告诉子类刷新内部bean工厂。
          ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // #2 初始化BeanFactory 并加载bean definitions信息

          // Prepare the bean factory for use in this context. 准备用于此容器的bean工厂。
          prepareBeanFactory(beanFactory); // #3 对beanFacotry进行配置

          try {
    
    
             // Allows post-processing of the bean factory in context subclasses. 允许在容器子类中对bean工厂进行后处理。
             postProcessBeanFactory(beanFactory); // #4 提供给子类扩展的预留方法

             StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
             // Invoke factory processors registered as beans in the context. 在容器中调用注册为bean的工厂处理器。
             invokeBeanFactoryPostProcessors(beanFactory); // #5 激活BeanFactoryPostProcessors

             // Register bean processors that intercept bean creation. 注册拦截bean创建的bean处理器。
             registerBeanPostProcessors(beanFactory);  // #6 注册BeanPostProcessors
             beanPostProcess.end();

             // Initialize message source for this context. 初始化此容器的消息源。
             initMessageSource(); // #7 初始化MessageSource

             // Initialize event multicaster for this context. 为此容器初始化事件多播。
             initApplicationEventMulticaster(); // #8 初始化事件广播器

             // Initialize other special beans in specific context subclasses. 初始化特定容器子类中的其他特殊bean。
             onRefresh(); // #9 提供给子类初始化其他的Bean

             // Check for listener beans and register them. 检查侦听器bean并注册它们。
             registerListeners(); // #10 注册事件监听器

             // Instantiate all remaining (non-lazy-init) singletons. 实例化所有剩余的(非惰性初始化)单例。
             finishBeanFactoryInitialization(beanFactory); // #11 构造热加载单例bean

             // Last step: publish corresponding event. 最后一步:发布相应的事件。
             finishRefresh(); // #12 完成刷新过程,通知生命周期处理器
          }

          catch (BeansException ex) {
    
    
             if (logger.isWarnEnabled()) {
    
    
                logger.warn("Exception encountered during context initialization - " +
                      "cancelling refresh attempt: " + ex);
             }

             // Destroy already created singletons to avoid dangling resources. 销毁已创建的单实例以避免悬空资源。
             destroyBeans(); // #13 出错了,销毁bean

             // Reset 'active' flag. 重置“激活”标志。
             cancelRefresh(ex); // #14 出错了,修改active标识

             // Propagate exception to caller. 向调用方传播异常。
             throw ex;
          }

          finally {
    
    
             // Reset common introspection caches in Spring's core, since we  重置Spring核心中的常见内省缓存,因为我们
             // might not ever need metadata for singleton beans anymore...   可能不再需要单例bean的元数据...
             resetCommonCaches();
             contextRefresh.end();
          }
       }
    }

The refresh() method does a lot of things, briefly summarized: What the refresh method does is to load the configuration file and initialize the Bean object, and then store the Bean object in the container.

The Spring startup we usually say is actually calling AbstractApplicationContext#refresh to complete the initialization and startup process of the spring context. The whole process of spring context initialization from the beginning to the end and startup is in the refresh method. The refresh method just started to do some spring context preparations, that is, spring context initialization, such as: creating BeanFactory, registering BeanFactoryPostProcessor, etc., and only after these preparations are completed can the spring context be started.

Summarize:

The loading of Bean configuration resources by ClassPathXmlApplicationContext starts from the refresh() method. The refresh() method is a template method, which specifies the startup process of the IoC container, and some logic should be handed over to its subclasses for implementation. It loads the Bean configuration resource, and ClassPathXmlApplicationContext starts the process of loading the Bean definition by the entire IoC container by calling the refresh() method of its parent class AbstractApplicationContext.

Custom Spring IOC

Now we need to analyze the following configuration files, and customize the IOC of the Spring framework to manage the related objects.

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <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>
</beans>

Overall structure

image-20230110223722425

Define bean-related pojo classes

PropertyValue class

It is used to encapsulate the properties of the bean, reflected in the above configuration file is the sub-label property label data of the package bean label.

/**
 * 用来封装bean标签下的property标签的属性
 * name属性
 * ref属性
 * value属性:给基本数据类型及String类型数据赋的值
 */
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;
  	}
}

MutablePropertyValues类

A bean tag can have multiple property sub-tags, so define a MutablePropertyValues ​​class to store and manage multiple PropertyValue objects.

used 迭代器模式.

/**
 * 用来存储和管理多个PropertyValue对象
 */
public class MutablePropertyValues implements Iterable<PropertyValue> {
    
    

    // 定义list集合对象,用来存储PropertyValue对象
    private final List<PropertyValue> propertyValueList;

    public MutablePropertyValues() {
    
    
        this.propertyValueList = new ArrayList<PropertyValue>();
    }

    public MutablePropertyValues(List<PropertyValue> propertyValueList) {
    
    
        this.propertyValueList = (propertyValueList != null ? propertyValueList : new ArrayList<PropertyValue>());
    }

    // 获取所有的PropertyValue对象,返回以数组的形式
    public PropertyValue[] getPropertyValues() {
    
    
        // 将集合转换为数组并返回
        return this.propertyValueList.toArray(new PropertyValue[0]);
    }

    // 根据name属性值获取PropertyValue对象
    public PropertyValue getPropertyValue(String propertyName) {
    
    
        // 遍历集合对象
        for (PropertyValue pv : this.propertyValueList) {
    
    
            if (pv.getName().equals(propertyName)) {
    
    
                return pv;
            }
        }
        return null;
    }

    // 获取迭代器对象
    @Override
    public Iterator<PropertyValue> iterator() {
    
    
        return propertyValueList.iterator();
    }

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

    // 添加PropertyValue对象
    public MutablePropertyValues addPropertyValue(PropertyValue pv) {
    
    
        // 判断集合中存储的PropertyValue对象是否和传递进行的重复了,如果重复了,进行覆盖
        for (int i = 0; i < this.propertyValueList.size(); i++) {
    
    
            // 获取集合中每一个PropertyValue对象
            PropertyValue currentPv = this.propertyValueList.get(i);
            if (currentPv.getName().equals(pv.getName())) {
    
    
                this.propertyValueList.set(i, new PropertyValue(pv.getName(), pv.getRef(), pv.getValue()));
                return this; // 目的就是实现链式编程
            }
        }
        this.propertyValueList.add(pv);
        return this; // 目的就是实现链式编程
    }

    // 判断是否有指定name属性值的对象
    public boolean contains(String propertyName) {
    
    
        return getPropertyValue(propertyName) != null;
    }
}

In fact, there is indeed a PropertyValue class in Spring, but there is also an interface: PropertyValues, which inherits the Iterable interface, rewrites some methods of the iterator, and the MutablePropertyValues ​​class implements the PropertyValues ​​interface.

public interface PropertyValues extends Iterable<PropertyValue> {
     
     
}

public class MutablePropertyValues implements PropertyValues, Serializable {
     
     

	private final List<PropertyValue> propertyValueList;
}

BeanDefinition class

The BeanDefinition class is used to encapsulate bean information, mainly including id (ie the name of the bean object), class (the full class name of the class to be managed by Spring) and sub-label property data.

/**
 * 用来封装bean标签数据
 * id属性
 * class属性
 * property子标签的数据
 */
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 void setPropertyValues(MutablePropertyValues propertyValues) {
    
    
        this.propertyValues = propertyValues;
    }

    public MutablePropertyValues getPropertyValues() {
    
    
        return propertyValues;
    }
}

Define registry related classes

BeanDefinitionRegistry interface

The BeanDefinitionRegistry interface defines the related operations of the registry, and defines the following functions:

  • Register the BeanDefinition object into the registry
  • Deletes the BeanDefinition object with the specified name from the registry
  • Get a BeanDefinition object from the registry by name
  • Determine whether the BeanDefinition object with the specified name is contained in the registry
  • Get the number of BeanDefinition objects in the registry
  • Get the names of all BeanDefinitions in the registry
// 注册表对象
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();
}

SimpleBeanDefinitionRegistry class

This class implements the BeanDefinitionRegistry interface and defines the Map collection as the registry container.

// 注册表接口子实现类
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() {
    
    
        return beanDefinitionMap.keySet().toArray(new String[0]);
    }
}

Define parser related classes

BeanDefinitionReader interface

Why do we have to create a BeanDefinitionReader interface? When analyzing the interfaces related to Spring IoC functions, Spring will provide different subclasses for different configuration files to analyze. For example, propertiesthe PropertiesBeanDefinitionReader class is used to parse the configuration file in the format, and the XmlBeanDefinitionReader is used to parse the configuration file in the ``xml` format. In the inheritance system of the BeanDefinitionReader interface, these two classes are its sub-implementation classes.

Here, when we customize the Spring IoC function, we only xmlcreate parsing classes for configuration files in the format.

BeanDefinitionReader is used to parse configuration files and register beans in the registry. Two specifications are defined:

  • Obtain the function of the registry, so that the outside world can obtain the registry object through this object.
  • Load the configuration file and register the bean data.
// 用来解析配置文件的,而该接口只是定义了规范
public interface BeanDefinitionReader {
    
    

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

XmlBeanDefinitionReader class

The XmlBeanDefinitionReader class is dedicated to parsing xmlconfiguration files. This class implements the BeanDefinitionReader interface and implements two functions in the interface.

// 针对xml配置文件进行解析的类
public class XmlBeanDefinitionReader implements BeanDefinitionReader {
    
    

    /*
     * 声明注册表对象
     * 为什么要在成员变量位置处声明注册表对象呢?
     * XmlBeanDefinitionReader对象(即解析器)是专门用来解析XML格式的配置文件的,
     * 解析完之后,自然是会将配置文件里面的<bean>标签封装成BeanDefinition对象,这些BD对象最终都要存放到注册表中。
     */
    private BeanDefinitionRegistry registry;

    public XmlBeanDefinitionReader() {
    
    
        this.registry = new SimpleBeanDefinitionRegistry();
    }

    // 获取注册表对象
    @Override
    public BeanDefinitionRegistry getRegistry() {
    
    
        return registry;
    }

    /*
     * 加载配置文件,并在注册表中进行注册
     * @param configLocation 类路径下配置文件的路径
     */
    @Override
    public void loadBeanDefinitions(String configLocation) throws Exception {
    
    
        // 获取类路径下的配置文件。注意,这里我们只实现类路径下的配置文件的加载
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(configLocation);
        // 使用dom4j进行xml配置文件的解析(需要在pom.xml导入对应的jar包)
        SAXReader reader = new SAXReader();
        Document document = reader.read(is);
        // 根据Document对象获取根标签对象(根标签很明显就是<beans>标签)
        Element rootElement = document.getRootElement();
        /*
         * 解析bean标签
         */
        // 获取根标签下所有的<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);

            // 2.创建MutablePropertyValues对象
            MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();

            // 3.获取<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);
            }
            // 4.将MutablePropertyValues对象封装到BeanDefinition对象中
            beanDefinition.setPropertyValues(mutablePropertyValues);

            // 5.将BeanDefinition对象注册到注册表中
            registry.registerBeanDefinition(id, beanDefinition);
        }
    }
}

pom.xml

<dependency>
    <groupId>dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>1.6.1</version>
</dependency>

IOC container related classes

BeanFactory interface

To define the uniform specification of IOC container in this interface is to obtain the bean object.

// IOC容器父接口
public interface BeanFactory {
    
    
    
	// 根据bean对象的名称获取bean对象
    Object getBean(String name) throws Exception;

	// 根据bean对象的名称获取bean对象,并进行类型转换
    <T> T getBean(String name, Class<? extends T> clazz) throws Exception;
}

ApplicationContext interface

All sub-implementation classes of this interface create bean objectsnon-delayedYes, that is to say (the user) will load the configuration file when creating the container object, instantiate the bean object, and finally store it in the container.

refresh()Therefore, the method is defined in this interface , which mainly completes the following two functions:

  • Load the configuration file.
  • The bean object is created according to the data encapsulated by the BeanDefinition object in the registry.
// 定义非延时加载功能
public interface ApplicationContext extends BeanFactory {
    
    
    
	// 进行配置文件加载并进行对象创建
    void refresh() throws IllegalStateException, Exception;
}

AbstractApplicationContext类

  • As a subclass of the ApplicationContext interface, this class is also non-delayed loading, so you need to define a Map collection in this class as a container for storing bean objects.
  • Declare a variable of type BeanDefinitionReader, which is used to parse the xml configuration file, and conforms to the principle of single responsibility.
    • The object creation of BeanDefinitionReader type is implemented by the subclass, because only the subclass clearly creates the BeanDefinitionReader subclass object.
// ApplicationContext接口的子实现类,用于立即加载
public abstract class AbstractApplicationContext implements ApplicationContext {
    
    

    // 声明解析器变量。注意,这里我们只是声明解析器变量而已,具体的对象交由子类去创建。
    protected BeanDefinitionReader beanDefinitionReader;
    // 用来存储bean对象的容器 key存储的是bean的id值,value存储的是bean对象
    protected Map<String, Object> singletonObjects = new HashMap<String, Object>();

    // 存储配置文件的路径
    protected String configLocation;

    public void refresh() throws IllegalStateException, Exception {
    
    
        // 加载BeanDefinition对象。我们只需要去调用解析器里面的方法即可。
        beanDefinitionReader.loadBeanDefinitions(configLocation);
        // 初始化bean(创建bean对象)
        finishBeanInitialization();
    }
  
    /*
     * bean对象的初始化
     * 如果我们要进行bean对象的初始化,很显然,我们需要先获取BeanDefinition对象,
     * 因为BeanDefinition对象里面记录了bean的相关信息,只有拿到这些信息,你才能去创建对象。
     * 而BeanDefinition对象又是被注册在注册表里面的,所以首先我们还得先去获取对应的注册表对象!
     */
    private void finishBeanInitialization() throws Exception {
    
    
        // 获取注册表对象
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
        // 获取BeanDefinition对象
        String[] beanNames = registry.getBeanDefinitionNames();

        for (String beanName : beanNames) {
    
    
            // 进行bean的初始化
            getBean(beanName);
        }
    }
}

Note: The getBean() method is used in the finishBeanInitialization() method of this class 模板方法模式.

ClassPathXmlApplicationContext类

This class mainly loads configuration files under the class path and creates bean objects, mainly completing the following functions:

  • In the constructor, create a BeanDefinitionReader object.
  • In the construction method, call the refresh() method to load the configuration file, create the bean object and store it in the container.
  • Override the getBean() method in the parent interface and implement dependency injection (DI).
/**
 * IOC容器具体的子实现类
 * 用于加载类路径下的xml格式的配置文件
 */
public class ClassPathXmlApplicationContext extends AbstractApplicationContext {
    
    

    public ClassPathXmlApplicationContext(String configLocation) {
    
    
        this.configLocation = configLocation;
        // 构建XmlBeanDefinitionReader对象
        beanDefinitionReader = new XmlBeanDefinitionReader();
        try {
    
    
            this.refresh();
        } catch (Exception e) {
    
    
        }
    }

    // 根据bean的id属性值(名称)获取bean对象
    @Override
    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);
        if (beanDefinition == null) {
    
    
            return null;
        }
        // 获取bean信息中的className(全类名)
        String className = beanDefinition.getClassName();
        // 通过反射创建对象
        Class<?> clazz = Class.forName(className);
        // 这里的beanObj就是UserService对象 只不过现在还是个空壳
        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();
            // 对于value属性和ref属性必然只能存在一个 所以需要进行判断
            // ref一般是一个bean对象,而value只是一个普通的属性值
            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方法
                        // 往beanObj中注入bean对象
                        method.invoke(beanObj, bean);
                    }
                }
            }

            if (value != null && !"".equals(value)) {
    
    
                // 拼接方法名
                String methodName = StringUtils.getSetterMethodByFieldName(propertyName);
                // 获取method对象
                Method method = clazz.getMethod(methodName, String.class);
                // 往beanObj中注入value值
                method.invoke(beanObj, value);
            }
        }

        // 在返回beanObj对象之前,将该对象存储到map容器中
        singletonObjects.put(name, beanObj);
        return beanObj;
    }

    @Override
    public <T> T getBean(String name, Class<? extends T> clazz) throws Exception {
    
    
        Object bean = getBean(name);
        if (bean != null) {
    
    
            // 类型强转
            return clazz.cast(bean);
        }
        return null;
    }
}

StringUtils

// String工具类
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;
    }
}

The overall logic is probably:

  • First of all, we have a BeanFactory top-level parent interface that defines a unified specification;
  • Then we create an ApplicationContext interface to inherit BeanFactory, the purpose is to achievenon-lazy loadingBD object;
  • Then create an AbstractApplicationContext sub-implementation class
    • it mainly achievesnon-delayed load timefunction, which is refresh()the method, which calls the XmlBeanDefinitionReader#loadBeanDefinitions method, which can load and parse the xml file, encapsulate all bean tags into BD objects, and register them in the registry;
      • Then we call getBean()the method to initialize all bean objects, which is implemented in specific subclasses;
    • The specific BeanDefinitionReader type object is created by the subclass, because only the subclass knows what type it is;
  • Create a concrete subclass to implement ClassPathXmlApplicationContext
    • Create a specific XmlBeanDefinitionReader object in the construction method of this class and assign it to the BeanDefinitionReader of the parent class, and call refresh()the method to load the configuration file, create a bean object and store it in the container;
    • Rewrite the method in the parent interface getBean()and implement the dependency injection operation (DI), **here is the logic of creating objects through reflection. **Finally store the object in the container.

test

reference video

Summary of custom Spring IOC

The design pattern used

  • factory pattern. The way this is used 工厂模式 + 配置文件.
  • singleton pattern. The bean objects managed by Spring IOC are all singletons. The singleton here is not controlled by the constructor, but the Spring framework creates only one object for each bean.
  • template method pattern. The finishBeanInitialization() method in the AbstractApplicationContext class calls the getBean() method of the subclass, because the implementation of getBean() is closely related to the environment.
  • iterator pattern. For the MutablePropertyValues ​​class definition, the iterator mode is used, because this class stores and manages PropertyValue objects and also belongs to a container, so it provides a traversal method for the container.

The Spring framework actually uses many design patterns, such as AOP uses proxy mode, chooses JDK proxy or CGLIB proxy and uses strategy mode, as well as adapter mode, decorator mode, observer mode, etc.

Conforms to most design principles.

There is still a certain discrepancy between the whole design and Spring's design

The bottom layer of the Spring framework is very complex, it has been deeply encapsulated, and it provides good scalability to the outside world. And our custom Spring IOC has the following purposes:

  • Understand the general management mechanism of Spring's underlying objects.
  • Understand the use of design patterns in specific development.
  • Learning Spring source code in the future, through the realization of this case, can reduce the entry cost of Spring source code learning.

Guess you like

Origin blog.csdn.net/weixin_53407527/article/details/128645080