directory

  • Principles and manual implementation of IOC in the Spring series
  • Spring series DI principle and manual implementation
  • Spring series of AOP principles and manual implementation
  • Spring series handwritten annotations and configuration file parsing
  • Write a SpringMVC in the Spring series

Introduction:

Spring is a layered JavaSE/EE full-stack lightweight open source framework. It is one of the frameworks that almost all Java workers must master, as well as its excellent design ideas and the art of code implementation. Learn Spring, in addition to use in our project, also need to study its source, but the realization of the Spring cover a lot of knowledge, plus the number of classes of these is also very much, when we read the source code may be interspersed with reading among dozens of classes, is likely to be inadvertently leads to confusion. In view of this, I first on the Spring of several important modules for a simple manual implementation, one is familiar with the principle of these modules, but also imitate the Spring structure to read the source code to lay a foundation.

IOC(Inversion of Control)

Inversion of Control means that the object we created by the client code will be controlled by the IOC container. The object will be created, initialized, and managed by the IOC.

The benefits of the IOC

  1. Decoupling: The emergence of IOC solves the coupling between classes. In the Servlet era of Web development, if a Servlet needs to rely on some implementation of another class, then we need to create and initialize the dependent class in the current class. If other classes also depend on this class, they also need to create and initialize. With the IOC in charge, you only need to apply to the IOC when needed, without repeated creation and initialization. Of course, IOC also allows you to recreate a new object each time.
  2. Easy to work with AOP: AOP is also a very frequently used feature that can be easily worked with through IOC.

Design patterns designed in IOC

Factory mode. The IOC container is responsible for creating management class instance objects, applying to IOC when needed, and obtaining them from IOC. So IOC containers are also called bean factories.

The factory pattern is a relatively straightforward design pattern, which will not be introduced here, if you need to look at this: factory pattern.

Manual implementation of IOC

Bean definition

The main function of IOC is to manage beans, including creation, initialization, management, and ecstasy. The first question we faced was how do we enable IOC to create a Bean? What do we need to provide in order to create the Bean?

How to create a Bean

There are two ways to create an object instance of a class without manually creating it through the new keyword:

  1. Reflection: An instance of a class can be created by reflection:clazz.getClass().newInstance();.
  2. Factory mode: Factory mode allows us to create instances without touching instance classes.
public class PersonFactory{
    public Person getPerson() {returnnew Person(); }}Copy the code
What do we need to provide in order to create the Bean

The answer can be easily obtained by analyzing the above two methods.

For reflection we simply provide the Class object of the instance.

All we need to provide for the factory method is the factoryName and methodName that created the class;

What else do YOU need to do besides create the bean

The IOC container manages the entire life cycle of beans, requiring initialization in addition to creation, and destruction of beans when they are not needed (such as freeing resources). So we also need to provide initialization and destruction.

With the basic analysis needed to create a bean here, look at the class diagram:

Bean plant

The Bean definition is resolved, but where to put the Bean definition and the created Bean instance, we need a unified place to store these things so that we can easily access them when we need them.

We define a Bean factory to store beans, when needed to understand the Bean factory can be taken, the Bean factory provides only a method to get beans, because the Bean type is variable, so the return value positioning Object.

Register Bean definitions

Now that we have the bean definition to create the bean, and the bean factory to store and manage the bean, we need to think about how to connect the two classes. We also need another interface that allows us to register and retrieve the bean definition. Here we use beanName to distinguish between different beans.

Code implementation

Here we have everything we need to implement a simple IOC container. Take a look at the basic class diagram:

Basic code implementation:

DefaultBeanDefinition:

public class DefaultBeanDefinition implements BeanDefinition{ private Class<? > clazz; private String beanFactoryName; private String createBeanMethodName; private String staticCreateBeanMethodName; private String beanInitMethodName; private String beanDestoryMethodName; private boolean isSingleton; // setter public voidsetSingleton(boolean singleton) { isSingleton = singleton; } @Override public Class<? >getBeanClass() {
        return this.clazz;
    }

    @Override
    public String getBeanFactory() {
        return this.beanFactoryName;
    }

    @Override
    public String getCreateBeanMethod() {
        return this.createBeanMethodName;
    }

    @Override
    public String getStaticCreateBeanMethod() {
        return this.staticCreateBeanMethodName;
    }

    @Override
    public String getBeanInitMethodName() {
        return this.beanInitMethodName;
    }

    @Override
    public String getBeanDestoryMethodName() {
        return this.beanDestoryMethodName;
    }

    @Override
    public String getScope() {
        returnthis.isSingleton? BeanDefinition.SINGLETION :BeanDefinition.PROTOTYPE; } @Override public booleanisSingleton() {
        return this.isSingleton;
    }

    @Override
    public boolean isPrototype() {
        return !this.isSingleton;
    }
}

Copy the code

DefaultBeanFactory:

public class DefaultBeanFactory implements BeanFactory, BeanDefinitionRegistry, Closeable {

    private Log log= LogFactory.getLog(this.getClass()); //ConcurrentHashMap To deal with concurrent environment private Map<String, BeanDefinition> bdMap = new ConcurrentHashMap<>(); private Map<String, Object> beanMap = new ConcurrentHashMap<>(); @Override public void register(BeanDefinition bd, String beanName) { Assert.assertNotNull("BeanName cannot be empty beanName", beanName);
        Assert.assertNotNull("BeanDefinition cannot be empty", bd);

        if(bdMap.containsKey(beanName)){
            log.info("[" + beanName + "] already exists");
        }

        if(! bd.validate()){ log.info("BeanDefinition is illegal.");
        }

        if(! bdMap.containsKey(beanName)){ bdMap.put(beanName, bd); } } @Override public boolean containsBeanDefinition(String beanName) {return bdMap.containsKey(beanName);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) {
        if(! bdMap.containsKey(beanName)){ log.info("[" + beanName + "] Does not exist");
        }
        return bdMap.get(beanName);
    }

    public Object doGetBean(String beanName) throws InstantiationException, IllegalAccessException {
        if(! beanMap.containsKey(beanName)){ log.info("[" + beanName + "] Does not exist");
        }

        Object instance = beanMap.get(beanName);

        if(instance ! = null){returninstance; } // Create it if it does not existif(! this.bdMap.containsKey(beanName)){ log.info("There is no such thing as: [" + beanName + "] bean definition"); } BeanDefinition bd = this.bdMap.get(beanName); Class<? > beanClass = bd.getBeanClass();if(beanClass ! = null){ instance = createBeanByConstruct(beanClass);if(instance == null){ instance = createBeanByStaticFactoryMethod(bd); }}else if(instance == null && StringUtils.isNotBlank(bd.getStaticCreateBeanMethod())){
            instance = createBeanByFactoryMethod(bd);
        }

        this.doInit(bd, instance);

        if(instance ! = null && bd.isSingleton()){ beanMap.put(beanName, instance); }return instance;
    }

    private void doInit(BeanDefinition bd, Object instance) { Class<? > beanClass = instance.getClass();if(StringUtils.isNotBlank(bd.getBeanInitMethodName())){ try { Method method = beanClass.getMethod(bd.getBeanInitMethodName(), null); method.invoke(instance, null); } catch (Exception e) { e.printStackTrace(); }}} /** * constructor creates instance * @param beanClass * @return*/ private Object createBeanByConstruct(Class<? > beanClass) { Object instance = null; try { instance = beanClass.newInstance(); } catch (Exception e) { e.printStackTrace(); }returninstance; } /** * Normal factory method to create instance * @param bd * @return*/ private Object createBeanByFactoryMethod(BeanDefinition bd) { Object instance = null; Try {// Get the factory class Object Factory =doGetBean(bd.getBeanFactory()); Method = factory.getClass().getMethod(bd.getCreateBeanMethod()); // Invoke method = method.invoke(factory, null); } catch (Exception e) { e.printStackTrace(); }returninstance; } /** * static method to create instance * @param bd * @return*/ private Object createBeanByStaticFactoryMethod(BeanDefinition bd) { Object instance = null; try { Class<? > beanClass = bd.getBeanClass(); / / access to create an instance Method Method. = beanClass getMethod (bd) getStaticCreateBeanMethod ()); instance = method.invoke(beanClass, null); } catch (Exception e) { e.printStackTrace(); }return instance;
    }

    @Override
    public Object getBean(String beanName) {
        if(! beanMap.containsKey(beanName)){ log.info("[" + beanName + "] Does not exist");
        }
        return beanMap.get(beanName);
    }

    @Override
    public void close() throws IOException {
        Set<Map.Entry<String, BeanDefinition>> entries = bdMap.entrySet();
        for(Map.Entry<String, BeanDefinition> entry: entries){ BeanDefinition value = entry.getValue(); String destoryMethodName = value.getBeanDestoryMethodName(); try { Method method = value.getBeanClass().getMethod(destoryMethodName, null); method.invoke(value.getBeanClass(), null); } catch (Exception e) { e.printStackTrace(); }}}}Copy the code

A simple test: Instance bean:

public class User {

    private String name;

    private int age;

    //getter setter

    public void init(){
        System.out.println("init...");
    }

    public void destory(){
        System.out.println("destory..."); }}Copy the code

The factory class:

public class TestFactory {
    public Object createMethod() {return new User();
    }

    public static Object staticCreateMethod() {returnnew User(); }}Copy the code

The test class:

public class MySpringTest {

    static DefaultBeanFactory factory = new DefaultBeanFactory();

    @Test
    public void test() throws IllegalAccessException, InstantiationException {
        DefaultBeanDefinition bd = new DefaultBeanDefinition();
        bd.setClazz(User.class);
        bd.setSingleton(true);
        bd.setBeanFactoryName("TestFactory");
        bd.setCreateBeanMethodName("createMethod");
        bd.setStaticCreateBeanMethodName("staticCreateMethod");

        bd.setBeanInitMethodName("init");

        factory.register(bd, "user");

        System.out.println(factory.doGetBean("user")); }}Copy the code

summary

This is a simple container, of course, we only have the basic functionality here, in fact, far from the instantiation of beans with parameters and so on. But the basic principles of IOC have already been expressed, and we just need to add new functionality to this foundation.