In the last article, the author analyzed the IOC implementation of Spring and analyzed the source code of Spring. It can be seen that the source code is extremely complex. This is because spring designers need to consider the expansibility, robustness, performance and other elements of the framework, so the design is very complex. The author also said in the end to achieve a simple IOC, let us have a deeper understanding of IOC, therefore, there is this article.
Of course, we are modeled after Spring’s IOC, so the code naming and design is basically modeled after Spring.
We’ll write a simple IOC in a few steps, first designing the components, then the interfaces, and then focusing on implementation.
1. Design components.
Do we remember the most important components in Spring? The BeanFactory container, the basic data structure of the BeanDefinition Bean, and of course the resource loader that loads the Bean. Probably the most important ones in the end are these components. The container is used to store the initialized Bean. BeanDefinition is the basic data structure of the Bean, such as Bean name, Bean property PropertyValue, Bean method, lazy loading or not, dependency relationship, etc. The resource loader is simple, just a class that reads an XML configuration file, reads each tag and parses it.
2. Design interfaces
First of all, we need a BeanFactory, which is a Bean container. The container interface has at least two simplest methods, one is to get the Bean and one is to register the Bean.
/** * A beanFactory is required to define the behavior of the IOC container such as fetching beans by name, such as registering beans, taking the bean name as an argument, and bean definition **@author stateis0
* @version 1.0.0
* @Date2017/11/30 * /
public interface BeanFactory {
/** * Gets the bean object ** from the container based on the bean name@paramName Bean name *@returnThe bean instance *@throwsThe Exception Exception * /
Object getBean(String name) throws Exception;
/** * Register the bean to the container **@paramName Bean name *@paramBean Bean instance *@throwsThe Exception Exception * /
void registerBeanDefinition(String name, BeanDefinition bean) throws Exception;
}
Copy the code
Get the Bean object based on the Bean’s name, with two registration parameters: the Bean name and the BeanDefinition object.
After defining the Bean’s most basic container, we also need a simple BeanDefinition interface. For convenience, but since we don’t need to consider extensions, we can design it directly as a class. What elements and methods do beanDefinitions need? You need a Bean object, a Class object, a ClassName string, and a collection of elements called PropertyValues. And that makes up a very basic BeanDefinition class. So what are the methods needed? It’s basically a get set method for these properties. Let’s look at the details of this class:
package cn.thinkinjava.myspring;
/** * the definition of bean **@author stateis0
*/
public class BeanDefinition {
/** * bean */
private Object bean;
/** * the bean's CLass object */
private Class beanClass;
/** * The class fully qualified name of the bean */
private String ClassName;
/** * class attribute collection */
private PropertyValues propertyValues = new PropertyValues();
/** * get the bean object */
public Object getBean(a) {
return this.bean;
}
/** * sets the bean's object */
public void setBean(Object bean) {
this.bean = bean;
}
/** * gets the bean's Class object */
public Class getBeanclass(a) {
return this.beanClass;
}
/** * Generates Class objects */ by setting Class name reflection
public void setClassname(String name) {
this.ClassName = name;
try {
this.beanClass = Class.forName(name);
} catch(ClassNotFoundException e) { e.printStackTrace(); }}/** * gets the bean's property set */
public PropertyValues getPropertyValues(a) {
return this.propertyValues;
}
/** * Sets the bean's properties */
public void setPropertyValues(PropertyValues pv) {
this.propertyValues = pv; }}Copy the code
Now that we have our basic BeanDefinition data structure, we also need an action class that reads from XML and parses into BeanDefinition. First we define a BeanDefinitionReader interface, which is just an identifier, Concrete by the abstract class to implement a basic method and define some basic properties, such as a registry container to store when reading, also need a delegate a ResourceLoader ResourceLoader, for loading XML files, and we need to set the constructor must contain a ResourceLoader, There are also get set methods.
package cn.thinkinjava.myspring;
import cn.thinkinjava.myspring.io.ResourceLoader;
import java.util.HashMap;
import java.util.Map;
/** * Abstract bean definition reads class **@author stateis0
*/
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {
/** * Register the bean container */
private Map<String, BeanDefinition> registry;
/** * resource loader */
private ResourceLoader resourceLoader;
/** * The constructor must have a resource loader, and the default plug-in creates a map container **@paramResourceLoader resourceLoader */
protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
this.registry = new HashMap<>();
this.resourceLoader = resourceLoader;
}
/** * get the container */
public Map<String, BeanDefinition> getRegistry(a) {
return registry;
}
/** * get the resource loader */
public ResourceLoader getResourceLoader(a) {
returnresourceLoader; }}Copy the code
With these abstract classes and interfaces, we can basically form a prototype, BeanDefinitionReader for reading configuration files from XML, generating BeanDefinition instances, storing them in a BeanFactory container, and once initialized, You can call the getBean method to get the successfully initialized Bean. Form a perfect closed loop.
3. How to achieve it
We’ve just gone through the process of reading a configuration file from XML, parsing it into a BeanDefinition, and finally putting it into a container. Basically, three steps. So let’s design the first step.
1. Read the configuration file from XML and parse it into BeanDefinition
We just designed a read BeanDefinition BeanDefinitionReader interface and an implementation of its abstract classes AbstractBeanDefinitionReader, abstract defines some simple method, There is a delegate class —–ResourceLoader, which we haven’t created yet, which is a ResourceLoader that loads resources based on a given path. We can use the default Java class library java.net.URL to achieve this, define two classes, one is a class that wraps the URL ResourceUrl, and one is a resource loading class that relies on ResourceUrl.
ResourceUrl code implementation
/** * resource URL */
public class ResourceUrl implements Resource {
/** * class library URL */
private final URL url;
/** * requires a library URL */
public ResourceUrl(URL url) {
this.url = url;
}
/** * gets the input stream */ from the URL
@Override
public InputStream getInputstream(a) throws Exception {
URLConnection urlConnection = url.openConnection();
urlConnection.connect();
returnurlConnection.getInputStream(); }}Copy the code
ResourceLoader implementation
/** * resource URL */
public class ResourceUrl implements Resource {
/** * class library URL */
private final URL url;
/** * requires a library URL */
public ResourceUrl(URL url) {
this.url = url;
}
/** * gets the input stream */ from the URL
@Override
public InputStream getInputstream(a) throws Exception {
URLConnection urlConnection = url.openConnection();
urlConnection.connect();
returnurlConnection.getInputStream(); }}Copy the code
Of course, you also need an interface, which defines only an abstract method
package cn.thinkinjava.myspring.io;
import java.io.InputStream;
/** * Resource definition **@author stateis0
*/
public interface Resource {
/** * get the input stream */
InputStream getInputstream(a) throws Exception;
}
Copy the code
Ok, AbstractBeanDefinitionReader elements required has had, however, obviously cannot implement this method reads BeanDefinition task. So we need a class to inherit an abstract class, to achieve specific methods, now that we are read the XML configuration files, then we will define a AbstractBeanDefinitionReader XmlBeanDefinitionReader inheritance, Implement some of the required methods, such as readrXML to read THE XML, such as registering parsed elements into Registry’s Map, and some of the parsing details. Let’s just look at the code.
The XmlBeanDefinitionReader implementation reads configuration files and parses them into beans
package cn.thinkinjava.myspring.xml;
import cn.thinkinjava.myspring.AbstractBeanDefinitionReader;
import cn.thinkinjava.myspring.BeanDefinition;
import cn.thinkinjava.myspring.BeanReference;
import cn.thinkinjava.myspring.PropertyValue;
import cn.thinkinjava.myspring.io.ResourceLoader;
import java.io.InputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/** * Parse the XML file **@author stateis0
*/
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
/** * constructor, which must contain a resource loader **@paramResourceLoader resourceLoader */
public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
super(resourceLoader);
}
public void readerXML(String location) throws Exception {
// Create a resource loader
ResourceLoader resourceloader = new ResourceLoader();
// Get the input stream from the resource loader
InputStream inputstream = resourceloader.getResource(location).getInputstream();
// Get the document Builder factory instance
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// Factory creates document builder
DocumentBuilder docBuilder = factory.newDocumentBuilder();
// The document builder parse stream returns the document object
Document doc = docBuilder.parse(inputstream);
// Parses the given document object and registers it in the bean container
registerBeanDefinitions(doc);
/ / close the flow
inputstream.close();
}
/** * parses the given document object and registers it in the bean container **@paramDoc document object */
private void registerBeanDefinitions(Document doc) {
// Reads the root element of the document
Element root = doc.getDocumentElement();
// Parse the root node of the element and all children of the root node and add them to the registry container
parseBeanDefinitions(root);
}
/** * Parses the root node of the element and all its children and adds them to the registry container **@paramRoot XML file root node */
private void parseBeanDefinitions(Element root) {
// Reads all the children of the root element
NodeList nl = root.getChildNodes();
// Iterate over the child elements
for (int i = 0; i < nl.getLength(); i++) {
// Gets the node at the given position of the root element
Node node = nl.item(i);
// Type judgment
if (node instanceof Element) {
// Force a parent element
Element ele = (Element) node;
// Parse to a given node, including name, class, property, name, value, refprocessBeanDefinition(ele); }}}/** * resolves to the given node, including name, class, property, name, value, ref **@paramEle XML parsing element */
private void processBeanDefinition(Element ele) {
// Gets the name attribute of the given element
String name = ele.getAttribute("name");
// Gets the class attribute of the given element
String className = ele.getAttribute("class");
// Create a bean definition object
BeanDefinition beanDefinition = new BeanDefinition();
// Set the fully qualified class name of the bean definition object
beanDefinition.setClassname(className);
// Inject a member variable from the configuration file into the bean
addPropertyValues(ele, beanDefinition);
// Add the bean name and bean definition to the registry container
getRegistry().put(name, beanDefinition);
}
/** * Add attribute elements from the configuration file to the bean definition instance **@paramElements of ele *@paramThe BeanDefinition bean defines the object */
private void addPropertyValues(Element ele, BeanDefinition beandefinition) {
// Gets the set of property attributes for the given element
NodeList propertyNode = ele.getElementsByTagName("property");
// loop set
for (int i = 0; i < propertyNode.getLength(); i++) {
// Gets the node at a given position in the collection
Node node = propertyNode.item(i);
// Type judgment
if (node instanceof Element) {
// Force nodes down to child elements
Element propertyEle = (Element) node;
// The element object gets the name attribute
String name = propertyEle.getAttribute("name");
// The element object gets the value of the attribute
String value = propertyEle.getAttribute("value");
// Check that value is not null
if(value ! =null && value.length() > 0) {
// Add the member variable to the given bean definition instance
beandefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
} else {
// If null, get the attribute ref
String ref = propertyEle.getAttribute("ref");
if (ref == null || ref.length() == 0) {
// If the attribute ref is empty, an exception is thrown
throw new IllegalArgumentException(
"Configuration problem: <property> element for property '"
+ name + "' must specify a ref or value");
}
// If not empty, create an instance of "bean reference" with the construction parameter name and the instance temporarily empty
BeanReference beanRef = new BeanReference(name);
// Add member variables to the given "bean definition"
beandefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanRef));
}
}
}
}
}
Copy the code
The code comments are very detailed, and the class methods are as follows:
- Public void readerXML(String location) Public method of parsing XML, given a String argument of a location.
- Private void registerBeanDefinitions(Document Doc) Gives a Document object and parses it.
- Private void parseBeanDefinitions(Element root) Given a root Element, loop through all the children of the root Element.
- Private void processBeanDefinition(Element ele) Gives a child Element, parses the Element, and creates a BeanDefinition object with the parsed data. And register with BeanDefinitionReader’s Map container, which holds all beans at parse time.
- Private void addPropertyValues(Element ele, BeanDefinition BeanDefinition) The property element in the element is parsed and injected into the BeanDefinition instance.
It takes five steps to parse the XML file. The ultimate goal is to put parsed files into BeanDefinitionReader’s Map.
Ok, so now that we’re done reading and parsing from the XML file, when do we put it into the BeanFactory container? Just we just in the AbstractBeanDefinitionReader registered in the container. So how do we build a Bean that actually works based on the BeanFactory design? Because those beans are just information about some beans. There are no beans for our real business needs.
2. Initialize the Bean we need (not the Bean definition) and implement dependency injection
We know that Bean definition can’t work, just some Bean information, just like a person, BeanDefinition is like your file in the public security bureau, but you are not in the public security Bureau, but as long as the public security Bureau can find you with your file. That’s the relationship.
Let’s create an abstract class AbstractBeanFactory based on the design of BeanFactory.
package cn.thinkinjava.myspring.factory;
import cn.thinkinjava.myspring.BeanDefinition;
import java.util.HashMap;
/** * An abstract class that implements bean methods and contains a map for storing bean names and bean definitions **@author stateis0
*/
public abstract class AbstractBeanFactory implements BeanFactory {
/** * container */
private HashMap<String, BeanDefinition> map = new HashMap<>();
/** * Gets the bean by its name, if not, then throws an exception if so, then gets the bean instance */ from the bean definition object
@Override
public Object getBean(String name) throws Exception {
BeanDefinition beandefinition = map.get(name);
if (beandefinition == null) {
throw new IllegalArgumentException("No bean named " + name + " is defined");
}
Object bean = beandefinition.getBean();
if (bean == null) {
bean = doCreate(beandefinition);
}
return bean;
}
/** * Registers the abstract method implementation of the bean definition, which is a template method that calls the subclass method doCreate, */
@Override
public void registerBeanDefinition(String name, BeanDefinition beandefinition) throws Exception {
Object bean = doCreate(beandefinition);
beandefinition.setBean(bean);
map.put(name, beandefinition);
}
/** * reduce a bean */
abstract Object doCreate(BeanDefinition beandefinition) throws Exception;
}
Copy the code
This class implements the two basic methods of the interface, getBean and registerBeanDefinition. We also design an abstract method for these two methods to call, deferring the concrete logic creation logic to subclasses. What is this design pattern? Template mode. The main thing is to look at the doCreate method, which is to create the bean concrete method, so we still need a subclass, what is it called? AutowireBeanFactory, which automatically injects beans, is the job of our standard Bean factory. What about the code?
package cn.thinkinjava.myspring.factory;
import cn.thinkinjava.myspring.BeanDefinition;
import cn.thinkinjava.myspring.PropertyValue;
import cn.thinkinjava.myspring.BeanReference;
import java.lang.reflect.Field;
/ * * * automatic injection and recursive injection (spring's standard implementation class DefaultListableBeanFactory has 1810 rows) * *@author stateis0
*/
public class AutowireBeanFactory extends AbstractBeanFactory {
/** * Creates an instance based on the bean definition, stores the instance as a key and the bean definition as a value, and calls the addPropertyValue method to inject */ for the properties of the given bean
@Override
protected Object doCreate(BeanDefinition beandefinition) throws Exception {
Object bean = beandefinition.getBeanclass().newInstance();
addPropertyValue(bean, beandefinition);
return bean;
}
/** * Inject instances for properties in the given bean, given a bean definition and a bean instance. * /
protected void addPropertyValue(Object bean, BeanDefinition beandefinition) throws Exception {
// Loop over the set of properties for the given bean
for (PropertyValue pv : beandefinition.getPropertyValues().getPropertyValues()) {
// Gets the property object in the given bean based on the given property name
Field declaredField = bean.getClass().getDeclaredField(pv.getname());
// Set the access permission for the property
declaredField.setAccessible(true);
// Get the object in the defined property
Object value = pv.getvalue();
// Determine if this object is a BeanReference object
if (value instanceof BeanReference) {
// Convert the property object to a BeanReference object
BeanReference beanReference = (BeanReference) value;
// Call the AbstractBeanFactory getBean method of the superclass to get the instance by the name of the bean reference, in this case recursively
value = getBean(beanReference.getName());
}
// Reflect the properties of the injected beandeclaredField.set(bean, value); }}}Copy the code
As you can see, the doCreate method uses reflection to create an object, and it also needs to inject properties into the object. If the property is of type REF, then it is a dependency and needs to call getBean recursively to find the Bean (because the property of the last Bean must be of a primitive type). This completes a fetching of the instantiated Bean and also implements class dependency injection.
4. To summarize
With this code, we have implemented a simple IOC dependency injection function and gained a better understanding of IOC so that we will no longer be overwhelmed by Spring initialization problems. Look directly at the source code can solve. Ha ha
The code is posted on Github at: a simple IOC for your own implementation, including dependency injection
Good luck!!