One, foreword

Using XML configuration, following the spring IOC source code implementation, handwritten a very simple IOC container, easy to start spring source code.

Ii. Introduction of the overall process

The overall process of Spring IOC can be roughly divided into the following steps:

Before writing, you need to understand the following functional components:

3. Specific steps

3.1 Construction Project

  • Set up a Maven project to introduce dom4j’s dependencies with the following POM file

    <dependencies>
      <! -- https://mvnrepository.com/artifact/dom4j/dom4j -->
      <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
      </dependency>
    </dependencies>
    Copy the code

    Dom4j’s dependencies are primarily used for parsing XML configuration files

  • Create a UserDao interface and implement class UserDaoImpl as follows:

    public interface UserDao {
    	void sayHello(a);
    }
    
    
    public class UserDaoImpl implements UserDao {
    	@Override
    	public void sayHello(a) {
    		System.out.println("hello world"); }}Copy the code
  • Write a UserService interface and implementation class UserServiceImpl as follows:

    public interface UserService {
    	void sayHello(a);
    }
    
    
    public class UserServiceImpl implements UserService {
      // This relies on the UserDao
    	private UserDao userDao;
      // The set method must be used to assign the value to the userDao
    	public void setUserDao(UserDao userDao) {
    		this.userDao = userDao;
    	}
    
    	@Override
    	public void sayHello(a) { userDao.sayHello(); }}Copy the code
  • Create a new application.xml file in the Resources directory with the following contents

    
            
    <beans>
        <bean id="userService" class="com.liuqiuyi.spring.business.impl.UserServiceImpl">
            <property name="userDao" ref="userDao"></property>
        </bean>
        <bean id="userDao" class="com.liuqiuyi.spring.business.impl.UserDaoImpl"></bean>
    </beans>
    Copy the code

    In this XML, we put two beans, userService and userDao, into the IOC container. Where userService relies on the userDao, which corresponds to the Java code above

    This is written the same as the SPRING XML configuration

3.2 Defining an XML attribute receiver object

To encapsulate attributes in an XML file as Java beans, you need to use three classes:

  • PropertyValue class: Used to encapsulate the contents of the Property tag in an XML file
  • MutablePropertyValues class: A bean label can contain more than one property label, using MutablePropertyValues to encapsulate multiple property labels
  • BeanDefinition class: Used to encapsulate the contents of the bean label

The code for the three classes is as follows:

  • PropertyValue class
/** * encapsulates the property tag * in the configuration file@author liuqiuyi
 * @date2021/3/10 * /
public class PropertyValue {
	private String name;
	private String ref;

	public PropertyValue(a) {}public PropertyValue(String name, String ref) {
		this.name = name;
		this.ref = ref;
	}
	// The get/set method is omitted
}
Copy the code
  • MutablePropertyValues class
/** * encapsulates multiple property objects and implements the iterator interface **@author liuqiuyi
 * @date2021/3/10 22:30 * /
public class MutablePropertyValues implements 可迭代<PropertyValue>{
	private final List<PropertyValue> propertyValueList;

	public MutablePropertyValues(a) {
		propertyValueList = new ArrayList<>();
	}

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

	/**
	 * 添加 PropertyValue
	 *
	 * @author liuqiuyi
	 * @date2021/3/10 agony * /
	public MutablePropertyValues addPropertyValue(PropertyValue propertyValue) {
		if (null == propertyValue) {
			return this;
		}
		If yes, replace it. If no, add it directly
		for (int i = 0; i < propertyValueList.size(); i++) {
			PropertyValue value = propertyValueList.get(i);
			if (value.getName().equals(propertyValue.getName())) {
				// If so, replace
				propertyValueList.set(i, propertyValue);
				return this;
			}
		}
		propertyValueList.add(propertyValue);
		return this;
	}

	/** * returns the list iterator */
	@Override
	public Iterator<PropertyValue> iterator(a) {
		returnpropertyValueList.iterator(); }}Copy the code
  • BeanDefinition class
/** * used to encapsulate information about bean tags in XML **@author liuqiuyi
 * @date2021/3/10 if * /
public class BeanDefinition {
	private String id;
	private String className;
	private MutablePropertyValues mutablePropertyValues;

	// The get/set method is omitted
}
Copy the code

The relationship between the three classes is as follows:

3.3 Create a registry class that receives beanDefinitions

Before we read the XML file, we need to think about, when we read the XML file, we encapsulate BeanDefinition objects, where do we store those objects? I can’t read it every time I call it, so here’s the BeanDefinitionRegistry.

  • BeanDefinitionRegistry is an interface that defines some abstract methods for manipulating BeanDefinitions
  • SimpleBeanDefinitionRegistry is concrete implementation class, which implements the concrete operating method of BeanDefinition this class can be understood as a registry, storage encapsulated BeanDefinition object

The code implementation is as follows:

  • BeanDefinitionRegistry interface
/** * Bean registry, used to store resolved BeanDefinitions **@author liuqiuyi
 * @date2021/3/10 22:52 * /
public interface BeanDefinitionRegistry {
	/** * Removes the BeanDefinition object ** with the specified name from the registry@paramBeanName bean name *@paramBeanDefinition Bean registers the object *@author liuqiuyi
	 * @date2021/3/10 so * /
	void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);

	/** * Removes the BeanDefinition object ** with the specified name from the registry@paramBeanName bean name *@throwsException cannot find the BeanDefinition object *@author liuqiuyi
	 * @date2021/3/10 22:54 * /
	void removeBeanDefinition(String beanName) throws Exception;

	/** * Gets the BeanDefinition object ** * from the registry by name@paramBeanName bean name *@returnBeanDefinition object *@throwsException cannot find the BeanDefinition object *@author liuqiuyi
	 * @date2021/3/10 22:54 * /
	BeanDefinition getBeanDefinition(String beanName) throws Exception;

	/** * If BeanDefinition exists **@paramBeanName bean name *@returnExists, true- exists,false- does not exist *@author liuqiuyi
	 * @date2021/3/10 22:55 * /
	boolean containsBeanDefinition(String beanName);

	/** * Get the number of BeanDefinitions **@returnNumber of Beandefinitions *@author liuqiuyi
	 * @date2021/3/10 after * /
	int getBeanDefinitionCount(a);

	/** * Get all the names of BeanDefinition **@returnBeanDefinition array *@author liuqiuyi
	 * @date2021/3/10 space of * /
	String[] getBeanDefinitionNames();
}
Copy the code
  • SimpleBeanDefinitionRegistry class
/** * singleton BeanDefinition register class **@author liuqiuyi
 * @date2021/3/10 23:08 * /
public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry {
	/** * A Map to store beanDefinition, regardless of concurrency */
	Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

	/** * Removes the BeanDefinition object ** with the specified name from the registry@paramBeanName bean name *@paramBeanDefinition Bean registers the object *@author liuqiuyi
	 * @date2021/3/10 so * /
	@Override
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
		beanDefinitionMap.put(beanName, beanDefinition);
	}

	/** * Removes the BeanDefinition object ** with the specified name from the registry@paramBeanName bean name *@throwsException cannot find the BeanDefinition object *@author liuqiuyi
	 * @date2021/3/10 22:54 * /
	@Override
	public void removeBeanDefinition(String beanName) throws Exception {
		beanDefinitionMap.remove(beanName);
	}

	/** * Gets the BeanDefinition object ** from the registry by name@paramBeanName bean name *@returnBeanDefinition object *@throwsException cannot find the BeanDefinition object *@author liuqiuyi
	 * @date2021/3/10 22:54 * /
	@Override
	public BeanDefinition getBeanDefinition(String beanName) throws Exception {
		return beanDefinitionMap.get(beanName);
	}

	/** * If BeanDefinition exists **@paramBeanName bean name *@returnExists, true- exists,false- does not exist *@author liuqiuyi
	 * @date2021/3/10 22:55 * /
	@Override
	public boolean containsBeanDefinition(String beanName) {
		return beanDefinitionMap.containsKey(beanName);
	}

	/** * Get the number of BeanDefinitions **@returnNumber of Beandefinitions *@author liuqiuyi
	 * @date2021/3/10 after * /
	@Override
	public int getBeanDefinitionCount(a) {
		return beanDefinitionMap.size();
	}

	/** * Get all the names of BeanDefinition **@returnBeanDefinition array *@author liuqiuyi
	 * @date2021/3/10 space of * /
	@Override
	public String[] getBeanDefinitionNames() {
		return beanDefinitionMap.keySet().toArray(new String[0]); }}Copy the code

3.4 Reading XML Files

This step starts reading the tag content defined in the XML file and encapsulating it into a BeanDefinition object. It mainly involves the methods of XML parsing:

  • BeanDefinitionReader interface: Defines abstract methods for loading BeanDefinitions
  • XmlBeanDefinitionReader class: Reads content from XML and parses it into a concrete BeanDefinition object

The specific code is as follows:

  • BeanDefinitionReader interface
/** * Parses the XML configuration file and encapsulates it in BeanDefinition **@author liuqiuyi
 * @date2021/3/11 22:55 * /
public interface BeanDefinitionReader {
	/** * Get the registry object **@returnRegistry object *@author liuqiuyi
	 * @date2021/3/11 after * /
	BeanDefinitionRegistry getRegistry(a);

	/** * Load the configuration file and register in the registry *@paramConfigLocation Specifies the file address * to read@throws Exception
	 * @author liuqiuyi
	 * @date2021/3/11 after * /
	void loadBeanDefinitions(String configLocation) throws Exception;
}
Copy the code
  • XmlBeanDefinitionReader class
/** * Read BeanDefinition * from XML@author liuqiuyi
 * @date2021/3/11 space of * /
public class XmlBeanDefinitionReader implements BeanDefinitionReader{
	// Depending on BeanDefinitionRegistry objects, cache the parsed BeanDefinition objects in BeanDefinitionRegistry
	private BeanDefinitionRegistry beanDefinitionRegistry;

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

	/** * Get the registry object **@returnRegistry object *@author liuqiuyi
	 * @date2021/3/11 after * /
	@Override
	public BeanDefinitionRegistry getRegistry(a) {
		return beanDefinitionRegistry;
	}

	/** * Load the configuration file and register in the registry **@paramConfigLocation Specifies the file address * to read@throws Exception
	 * @author liuqiuyi
	 * @date2021/3/11 after * /
	@Override
	public void loadBeanDefinitions(String configLocation) throws Exception {
		if (null == configLocation || "".equals(configLocation.trim())) {
			return;
		}
		InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(configLocation);

		// Read the contents of the XML file
		SAXReader saxReader = new SAXReader();
		Document document = saxReader.read(inputStream);
    // Get the root tag
		Element rootElement = document.getRootElement();

		List<Element> elements = rootElement.elements();
    // Loop through all tags
		for (Element element : elements) {
      // Retrieve the id and class attributes
			String id = element.attributeValue("id");
			String clazz = element.attributeValue("class");

			BeanDefinition beanDefinition = new BeanDefinition();
			beanDefinition.setId(id);
			beanDefinition.setClassName(clazz);

      // Retrieve the property tag
			List<Element> propertyElements = element.elements("property");
			MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
			for (Element propertyElement : propertyElements) {
        // Retrieve the name attribute and ref attribute from the property tag
				String name = propertyElement.attributeValue("name");
				String ref = propertyElement.attributeValue("ref");
				
				mutablePropertyValues.addPropertyValue(new PropertyValue(name, ref));
			}
			beanDefinition.setMutablePropertyValues(mutablePropertyValues);
			// Place the beanDefinition object in the beanDefinitionRegistry objectbeanDefinitionRegistry.registerBeanDefinition(id, beanDefinition); }}}Copy the code

3.5 Create bean objects and perform dependency injection

The implementation is modeled after the structure in Spring

  • BeanFactory interface: The top level interface to IOC in Spring, which defines abstract methods for manipulating the IOC container
  • ApplicationContext interface: Inherits the BeanFactory interface and defines a container initialization method refresh()
  • AbstractApplicationContext abstract class: implements the ApplicationContext interface, rewrite the refresh () method, and defined the initialization of the container
  • ClassPathXmlApplicationContext classes: inherited AbstractApplicationContext abstract class and override the container operation method, and provides access to the bean’s method

Specific dependencies are as follows:

Specific code implementation:

  • The BeanFactory interface

    /** * The top-level interface of the IOC container **@author liuqiuyi
     * @date 2021/3/11 23:37
     */
    public interface BeanFactory {
    	/** * Gets the bean object * based on its name@paramName The name of the bean object *@returnBean object *@throwsException could not find the bean object *@author liuqiuyi
    	 * @date2021/3/11 now * /
    	Object getBean(String name) throws Exception;
    
    	/** * Gets the bean object based on its name and casts it **@paramName The name of the bean object *@paramClazz object type *@returnT Specifies the bean *@throwsException Cannot find bean Exception *@author liuqiuyi
    	 * @date2021/3/11 now * /
    	<T> T getBean(String name, Class<? extends T> clazz) throws Exception;
    }
    Copy the code
  • ApplicationContext interface

    /** * ApplicationContext, which defines the refresh() method *@author liuqiuyi
     * @date2021/3/11 suffering justly * /
    public interface ApplicationContext extends BeanFactory{
    	/** * Config file loading and object creation **@throw Exception
    	 * @author liuqiuyi
    	 * @date 2021/3/11 23:42
    	 */
    	void refresh(a) throws Exception;
    }
    Copy the code
  • AbstractApplicationContext abstract class

    /** * Abstract ApplicationContext, which defines the initialization steps *@author liuqiuyi
     * @date2021/3/13 and * /
    public abstract class AbstractApplicationContext implements ApplicationContext{
      // it is used to store the created bean object, regardless of concurrency
    	protected final Map<String, Object> singletonObjectMap = new HashMap<>();
    	// Rely on BeanDefinitionReader objects, since XML objects need to be parsed to BeanDefinition objects first
    	protected BeanDefinitionReader beanDefinitionReader;
    
    	/** * the path to the XML file */
    	protected String configLocation;
    
    
    	/** * Config file loading and object creation **@throw IllegalStateException
    	 * @author liuqiuyi
    	 * @date 2021/3/11 23:42
    	 */
    	@Override
    	public void refresh(a) throws Exception {
    		// Load the beanDefinition object
    		beanDefinitionReader.loadBeanDefinitions(configLocation);
    		// Perform the bean initialization
    		finishBeanInitialization();
    	}
    
    	private void finishBeanInitialization(a) throws Exception {
    		// Get the BeanDefinitionRegistry object
    		BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
    		// Get all beandefinitionNames
    		String[] beanDefinitionNames = registry.getBeanDefinitionNames();
    		for (String beanDefinitionName : beanDefinitionNames) {
    			// The getBean method here uses the template method pattern, which is done in the child implementation classgetBean(beanDefinitionName); }}}Copy the code
  • ClassPathXmlApplicationContext class

    public class ClassPathXmlApplicationContext extends AbstractApplicationContext {
    
    	/** * Completes the assignment to the XML path in the constructor and executes the refresh() method */
    	public ClassPathXmlApplicationContext(String configLocation) throws Exception {
    		super.configLocation = configLocation;
    		beanDefinitionReader = new XmlBeanDefinitionReader();
    		super.refresh();
    	}
    
    	/** * Gets the bean object ** based on its name@paramName The name of the bean object *@returnBean object *@throwsException could not find the bean object *@author liuqiuyi
    	 * @date2021/3/11 now * /
    	@Override
    	public Object getBean(String name) throws Exception {
    		// Check whether the bean object exists in the container
    		Object obj = singletonObjectMap.get(name);
    		if (null! = obj) {return obj;
    		}
    		BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
    		Fetch beanDefinitionDefinition by name from BeanDefinitionRegistry
    		BeanDefinition beanDefinition = registry.getBeanDefinition(name);
    		if (null == beanDefinition) {
    			return null;
    		}
    
    		// Create bean by reflectionClass<? > clazz = Class.forName(beanDefinition.getClassName()); Object beanObj = clazz.newInstance();// Get dependency information
    		MutablePropertyValues mutablePropertyValues = beanDefinition.getMutablePropertyValues();
    		for (PropertyValue mutablePropertyValue : mutablePropertyValues) {
    			String propertyValueName = mutablePropertyValue.getName();
    			String ref = mutablePropertyValue.getRef();
    
    			if (null! = ref && !"".equals(ref.trim())) {
    				// Get the bean to be injected, similar to recursion
    				Object bean = getBean(ref);
    				// Assemble the method name of the setBean in beanObj
    				String setMethodName = buildSetMethodName(propertyValueName);
    				// Get the setBean method in beanObj and set the value
    				Method[] methods = clazz.getMethods();
    				for (Method method : methods) {
    					if(method.getName().equals(setMethodName)) { method.invoke(beanObj, bean); }}}}// Place the created objects in the IOC container
    		singletonObjectMap.put(name, beanObj);
    		return beanObj;
    	}
    
    	/** * Gets the bean object based on its name and casts it **@paramName The name of the bean object *@paramClazz object type *@returnT Specifies the bean *@throwsException Cannot find bean Exception *@author liuqiuyi
    	 * @date2021/3/11 now * /
    	@Override
    	public <T> T getBean(String name, Class<? extends T> clazz) throws Exception {
    		Object bean = getBean(name);
    		if (null == bean) {
    			return null;
    		}
    		return clazz.cast(bean);
    	}
    
    	/** * capitalize the method name and add the set string *@author liuqiuyi
    	 * @date2021/3/13 16:06 * /
    	private String buildSetMethodName(String name) {
    		if (null == name || "".equals(name.trim())) {
    			return "";
    		}
    		String upperString = name.substring(0.1).toUpperCase() + name.substring(1);
    		return "set"+ upperString; }}Copy the code

At this point, the entire IOC container is fully implemented, so let’s test it

Test custom IOC containers

Write the test method to get the UserServiceImpl class from the IOC container and call the methods in the class

/** * Custom IOC container tests **@author liuqiuyi
 * @date2021/3/13 affliction * /
public class SpringIocTest {
	public static void main(String[] args) throws Exception {
    // Create an IOC container
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
    // Get the UserServiceImpl class from the container
		UserServiceImpl userService = applicationContext.getBean("userService", UserServiceImpl.class);
    // Call a method in the classuserService.sayHello(); }}Copy the code

The results are as follows:

At this point, the implementation of a simple custom IOC container is complete.

5. Sequence diagram of IOC initialization

The overall execution process is as follows