A simplified version of the Spring framework

function

  • Support for Singleton-type beans, including initialization, property injection, and dependency bean injection.
  • Configuration can be read from XML.
  • You can write AOP in an Aspectj fashion, supporting interfaces and class proxies.

instructions

If you’re lucky enough to see it

  • 1, this article is simple Spring framework reference Github, code comments refer to Github.
  • 2, the ownership of the original author, here I just copy, reference notes restore process. If you don’t understand, you can watch the author’s video.
  • 3, let’s work together and study together. If you are interested, you can also take a look at my Github. Upload Spring source code. Big guy can take a look.
  • 4. I hope you have a copy of Spring source code before reading this article. Look for the interface and class you want. Deepen the impression.
  • 5, this article is only for their own future review, if not, please understand.

Tiny-spring was developed to learn from Spring and can be considered a lite version of Spring. Spring’s code is large, complex and hard to read. I tried to build a simplified version of Spring step by step by referring to the implementation of Spring from a functional perspective. Some people compare programmers to painters, who have a basic skill called copying. Tiny-spring is a version of a program that can be copied — programming for your own needs and referencing famous projects.

Part I: IoC container

There’s no time to waste. Yeah.

1. Sequence diagram – Loading process of ordinary bean

2. Load the main related classes of the resource

Load the main related classes of the bean definition from the XML

4. Assemble the bean’s main related classes

Implement the related classes of the ApplicationContext interface

Step1 – basic container

IoC has two basic roles: the container (BeanFactory) and the Bean itself. The Bean object is wrapped with a BeanDefinition to hold some additional meta information. Test code:

1. Initialize the BeanFactory
BeanFactory beanFactory = new BeanFactory();

// 2. Inject beans
BeanDefinition beanDefinition = new BeanDefinition(new HelloWorldService());
beanFactory.registerBeanDefinition("helloWorldService", beanDefinition);

// 3. Get the bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();

Copy the code

1. First, let’s look at the overall code structure. The key point here is the BeanFactory interface. We should program for interfaces.

/** * Created by guo on 3/1/2018. * Created by guo on 3/1/2018
public interface BeanFactory {
    Object getBean(String name) throws Exception;
}

/** * Created by guo on 3/1/2018. * Abstract bean factory */
public abstract class AbstractBeanFactory implements BeanFactory {
}

``

2AbstractBeanFactory Abstract class AbstractBeanFactory abstract class Is it any different from an interface? When to use interfaces and when to use abstract classes? -1If you have methods that you want to implement by default, use abstract classes. -2If you want to implement multiple inheritance, then you must use interfaces. Since Java does not support multiple inheritance, subclasses cannot inherit multiple classes, but can implement multiple interfaces3If the basic functionality is changing, then you need to use abstract classes. If you keep changing basic functionality and using an interface, you need to change all the classes that implement that interface.3With bean factory, you have to define the bean. ` BeanDefinition ` came. Bean definition Java/** * Created by guo on 3/1/2018. * Created by guo on 3/1/2018. * Created by Guo on 3/1/2018. * /
public class BeanDefinition {

  private Object bean;

  // Class information for the class
  private Class beanClass;

  / / the name of the class
  private String beanClassName;

  // Save all attributes,
  private PropertyValues  propertyValues  = new PropertyValues();

  public BeanDefinition(a) {}public BeanDefinition(a) {}

    / / setters and gettes slightly
    public void setBeanClassName(String beanClassName) {
        this.beanClassName = beanClassName;
        try {
            // Load the class and return a class object
            // There is already an instance of the class, but there is no reference.
            this.beanClass = Class.forName(beanClassName);
        } catch(ClassNotFoundException e) { e.printStackTrace(); }}}Copy the code

With a bean definition you have to create it, initialize it, register it, validate it, or it won’t work at all. To Chou Chou. Let’s look at a concrete implementation of AbstractBeanFactory abstract class.

/** * Created by guo on 3/1/2018. * Abstract bean factory */
public abstract class AbstractBeanFactory implements BeanFactory {

    // The bean project maintains a dictionary of classes, class names +class objects
    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();

    private final List<String> beanDefinitionNames = new ArrayList<String>();

    private List<BeanPostProcessor> beanPostProcessors = new ArrayList<BeanPostProcessor>();

    /** * create an instance object of the Class when obtaining the bean. * At this point an instance of the Class will be created from the Class object * *@param name
     * @return
     * @throws Exception
     */
    @Override
    public Object getBean(String name) throws Exception {
        BeanDefinition beanDefinition = beanDefinitionMap.get(name);
        if (beanDefinition == null) {
            throw new IllegalArgumentException("no bean named " + name + "is defined");
        }
        Object bean = beanDefinition.getBean();
        if (bean == null) {
            //1. Create object, do nothing else
            bean = doCreateBean(beanDefinition);
            //2
            bean = initializeBean(bean,name);
            //3. This is the bean after initialization, not the same as the bean created initially.
            beanDefinition.setBean(bean);
        }
        returnbean; }}Copy the code

5. Let’s look at the specific doCreateBean, initializeBean, and registerBeanDefinition


  /** * Initializes the bean, BeanPostProcessor initializes the pre and post processors. * /
  protected Object initializeBean(Object bean, String name) throws Exception {
      for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
          bean = beanPostProcessor.postProcessBeforeInitialization(bean, name);
      }

      for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
          bean = beanPostProcessor.postProcessAfterInitialization(bean, name);
      }
      return bean;
  }

  /** * Create an instance of the bean */
  protected Object createBeanInstance(BeanDefinition beanDefinition) throws Exception {
      return beanDefinition.getBeanClass().newInstance();
  }

  /** * Registers the bean, storing the class name and definition in memory (map objects) */
  public void registerBeanDefinition(String name, BeanDefinition beanDefinition) throws Exception {
      beanDefinitionMap.put(name, beanDefinition);
      // Save a copy for preparation
      beanDefinitionNames.add(name);
  }

  /** * Creates the bean and sets the bean's reference */
  protected Object doCreateBean(BeanDefinition beanDefinition) throws Exception {
      // An instance object of the bean is created here
      Object bean = createBeanInstance(beanDefinition);

      // Set the bean instance object to beanDefinition
      beanDefinition.setBean(bean);
      // Set the instance object referenced by the bean
      applyPropertyValues(bean, beanDefinition);

      return bean;
  }

Copy the code

Irrelevant methods will not be posted temporarily, describe a general process. Look in the source code with these classes and interfaces. Please ignore the second and third steps. Skip to step 4. Remember in Spring source code, this is much simpler, but the basic function has


Step2 – put the bean creation into the factory

The beans in step1 are initialized and then set. In practice, we want the container to manage bean creation. We then put the bean’s initialization into the BeanFactory. In order to guarantee the scalability, we use the Extract method of Interface will be replaced the BeanFactory Interface, and the use of AbstractBeanFactory and AutowireCapableBeanFactory as its implementation.” “AutowireCapable” means” auto-assembles “in preparation for the injection of properties later.

1. Initialize the BeanFactory
BeanFactory beanFactory = new AutowireCapableBeanFactory();

// 2. Inject beans
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setBeanClassName("us.codecraft.tinyioc.HelloWorldService");
beanFactory.registerBeanDefinition("helloWorldService", beanDefinition);

// 3. Get the bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();
Copy the code

Step3 – inject properties into the bean

In this step, we want to inject properties into the bean. We chose to save the property injection information to the PropertyValue object and to the BeanDefinition. This allows us to inject bean properties according to the PropertyValue when we initialize the bean. Spring itself uses setters for injection, but for code brevity, we’ll use fields for injection.

1. Initialize the BeanFactory
BeanFactory beanFactory = new AutowireCapableBeanFactory();

/ / 2. Bean definitions
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setBeanClassName("us.codecraft.tinyioc.HelloWorldService");

// 3. Set the properties
PropertyValues propertyValues = new PropertyValues();
propertyValues.addPropertyValue(new PropertyValue("text"."Hello World!"));
beanDefinition.setPropertyValues(propertyValues);

// 4. Generate bean
beanFactory.registerBeanDefinition("helloWorldService", beanDefinition);

// 5. Get the bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();
Copy the code

Step4 – read the XML configuration to initialize the bean

You must have an IO stream, a resource (xxx.xml), and a reader. Let’s take a look at the important interfaces and implementation classes.

/** * Resource is Spring's internal interface for locating resources */
public interface Resource {
    InputStream getInputStream(a) throws Exception; } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- loading resources -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -public class ResourceLoader {

    // Get resources
    public Resource getResource(String location){
        URL resource = this.getClass().getClassLoader().getResource(location);
        return newUrlResource(resource); }} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /public class UrlResource implements Resource {

   private final URL url;

   public UrlResource(URL url) {
       this.url = url;
   }

   @Override
   // Load the input stream according to the URL
   public InputStream getInputStream(a) throws IOException{
       URLConnection urlConnection = url.openConnection();
       urlConnection.connect();
       returnurlConnection.getInputStream(); }} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- test -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --public class ResourceLoaderTest {

	@Test
	public void test(a) throws IOException {
		ResourceLoader resourceLoader = new ResourceLoader();
        Resource resource = resourceLoader.getResource("tinyioc.xml"); InputStream inputStream = resource.getInputStream(); Assert.assertNotNull(inputStream); }}Copy the code

2. Let’s look at the more important interfaces and implementation classes

public interface BeanDefinitionReader {

    void loadBeanDefinitions(String location) throws Exception; } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- important implementation -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --** * Created by guo on 3/1/2018. ** Created by Guo on 3/1/2018
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {

    / / bean collection
    private Map<String,BeanDefinition> registry;

    // Resource loader
    private ResourceLoader resourceLoader;

    protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
        this.registry = new HashMap<String, BeanDefinition>();
        this.resourceLoader = resourceLoader;
    }
      / / setter. getter
}
Copy the code

3. The final implementation comes


public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
        super(resourceLoader);
    }

    @Override
    public void loadBeanDefinitions(String location) throws Exception {
        InputStream inputStream = getResourceLoader().getResource(location).getInputStream();
        doLoadBeanDefinitions(inputStream);

    }

Copy the code

4. I’ve pulled out the method for ease of understanding. This is mainly about parsing and registering

protected void doLoadBeanDefinitions(InputStream inputStream) throws Exception {
    / / XML parsing
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    DocumentBuilder docBuilder = factory.newDocumentBuilder();
    Document doc = docBuilder.parse(inputStream);
    / / parsing bean
    registerBeanDefinitions(doc);
    inputStream.close();
}

public void registerBeanDefinitions(Document doc) {
    Element root = doc.getDocumentElement();
    parseBeanDefinitions(root);
}

Copy the code

The real analysis is here.



protected void parseBeanDefinitions(Element root) {
    NodeList nl = root.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        if (node instanceofElement) { Element ele = (Element) node; processBeanDefinition(ele); }}}protected void processBeanDefinition(Element ele) {
    // Get the id and classname
    String name = ele.getAttribute("id");
    String className = ele.getAttribute("class");
    BeanDefinition beanDefinition = new BeanDefinition();
    // Handle attributes
    processProperty(ele, beanDefinition);
    / / Class registration
    beanDefinition.setBeanClassName(className);
    getRegistry().put(name, beanDefinition);
}

// Add bean attributes, and ref references
private void processProperty(Element ele, BeanDefinition beanDefinition) {
    NodeList propertyNode = ele.getElementsByTagName("property");
    for (int i = 0; i < propertyNode.getLength(); i++) {
        Node node = propertyNode.item(i);
        if (node instanceof Element) {
            Element propertyEle = (Element) node;
            String name = propertyEle.getAttribute("name");
            String value = propertyEle.getAttribute("value");
            if(value ! =null && value.length() > 0) {
                beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
            } else {
                String ref = propertyEle.getAttribute("ref");
                if (ref == null || ref.length() == 0) {
                    throw new IllegalArgumentException("Configuration problem: <property> element for property '"
                            + name + "' must specify a ref or value");
                }
                // Bean references to other objects are placed directly in its own properties
                BeanReference beanReference = new BeanReference(ref);
                beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference));
            }
        }
    }
}
}
Copy the code

6. Here is the test code

@Test
public void test(a) throws Exception {
    XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
    xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");
    Map<String, BeanDefinition> registry = xmlBeanDefinitionReader.getRegistry();
    Assert.assertTrue(registry.size() > 0);
}
Copy the code

Such a big chunk of initialization code is annoying. The BeanDefinition here is just some configuration, so let’s initialize it in XML. We define the BeanDefinitionReader initialization bean, which has an implementation called XmlBeanDefinitionReader.

1. Read the configuration
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");

// 2. Initialize BeanFactory and register the bean
BeanFactory beanFactory = new AutowireCapableBeanFactory();
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {
        beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
}

// 3. Get the bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();
Copy the code

Step5 – inject beans into beans

With XML configuration, it seems that Spring is one step closer to what we all know! But there’s a big problem: we can’t handle dependencies between beans, we can’t inject beans into beans, so it can’t be called a full IoC container! How do you do that? We define a BeanReference to indicate that the property is a reference to another bean. This is initialized when the XML is read, and when the bean is initialized, it is parsed and injected into the real bean.

for (PropertyValue propertyValue : mbd.getPropertyValues().getPropertyValues()) {
    Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName());
    declaredField.setAccessible(true);
    Object value = propertyValue.getValue();
    if (value instanceof BeanReference) {
        BeanReference beanReference = (BeanReference) value;
        value = getBean(beanReference.getName());
    }
    declaredField.set(bean, value);
}
Copy the code

In order to solve the problem of loop dependency, we can use lazy-init to execute createBean on getBean. If the bean corresponding to this property is not found when the bean is injected, it will be created first! Because it is always created first and injected later, there is no problem with deadlock creation of two looping dependent beans.

1. Read the configuration
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");

// 2. Initialize BeanFactory and register the bean
AbstractBeanFactory beanFactory = new AutowireCapableBeanFactory();
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {
    beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
}

// 3. Initialize bean
beanFactory.preInstantiateSingletons();

// 4. Get the bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();
Copy the code

6. Step6 – ApplicationContext

Regardless, let’s first look at the important interfaces and the important implementations

/** * inherits beanFactory */
public interface ApplicationContext extends BeanFactory {} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --public abstract class AbstractApplicationContext implements ApplicationContext {
	protected AbstractBeanFactory beanFactory;

	public AbstractApplicationContext(AbstractBeanFactory beanFactory) {
		this.beanFactory = beanFactory;
	}

	public void refresh(a) throws Exception {
		/ / load bean
		loadBeanDefinitions(beanFactory);
		// What should I do before I register
		registerBeanPostProcessors(beanFactory);
		onRefresh();
	}

	// Call the BeanFactory factory to get the instance object of the bean
	@Override
	public Object getBean(String name) throws Exception {
		returnbeanFactory.getBean(name); }}Copy the code

2. For convenience, the method is put here

protected abstract void loadBeanDefinitions(AbstractBeanFactory beanFactory) throws Exception;

protected void registerBeanPostProcessors(AbstractBeanFactory beanFactory) throws Exception {
  List beanPostProcessors = beanFactory.getBeansForType(BeanPostProcessor.class);
  for(Object beanPostProcessor : beanPostProcessors) { beanFactory.addBeanPostProcessor((BeanPostProcessor) beanPostProcessor); }}protected void onRefresh(a) throws Exception{
      beanFactory.preInstantiateSingletons();
  }
Copy the code

Familiar dongdong appeared

public class ClassPathXmlApplicationContext extends AbstractApplicationContext {

	private String configLocation;

	public ClassPathXmlApplicationContext(String configLocation) throws Exception {
		this(configLocation, new AutowireCapableBeanFactory());     // BeanFactory can automatically assemble content
	}

	public ClassPathXmlApplicationContext(String configLocation, AbstractBeanFactory beanFactory) throws Exception {
		super(beanFactory);
		this.configLocation = configLocation;
		// Initialize all directly
		refresh();
	}

	@Override
	protected void loadBeanDefinitions(AbstractBeanFactory beanFactory) throws Exception {
		// Locate the bean, then load the bean
		XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
		xmlBeanDefinitionReader.loadBeanDefinitions(configLocation);
		// Register the bean, where the bean has been loaded into the virtual machine, but the object has not been instantiated.
		for(Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) { beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue()); }}Copy the code

Test your code

@Test
public void test(a) throws Exception {
    // Encapsulate the beanFactory to make the call more convenient. Register, all initialized.
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
    HelloWorldService helloWorldService = (com.guo.codecraft.tinyioc.HelloWorldService) applicationContext.getBean("helloWorldService");
    helloWorldService.helloWorld();
}

@Test
public void testPostBeanProcessor(a) throws Exception {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc-postbeanprocessor.xml");
    HelloWorldService helloWorldService = (com.guo.codecraft.tinyioc.HelloWorldService) applicationContext.getBean("helloWorldService");
    helloWorldService.helloWorld();
}
Copy the code

Now the BeanFactory is fully functional, but it’s a little tricky to use. So we introduce the familiar ApplicationContext interface and in AbstractApplicationContext refresh () method in the bean’s initialization.

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
helloWorldService.helloWorld();
Copy the code

Is that familiar? At this point, the IoC part of our Tiny-Spring can be said to be complete. The class, method names, and functions of this section correspond to the corresponding Spring components. With just over 400 lines of code, you already have basic IoC functionality!