IOC Design Ideas

1. What is IOC (Control Inversion)?

Controlling reversal means that the right to create an instance is vested in the framework. We don't need to care about how to create an object in the program and then use it. We just need to configure some information about the object in advance and then use it directly in the program under the framework without new. Because the framework automatically assigns us what we need.

This is the reversal of control. It's amazing, isn't it? How does it really work? In fact, it is not difficult for us to think about it carefully. It's all about implementing a container that automatically creates objects before running our program and then injects them into our program so that we have instances in our program and can run properly.

2. Implement a simple IOC container

So how do I implement this simple IOC container?

Let's start with the following questions:

  • How do we ensure that our programs are container-based?
  • How can containers automatically create objects? (Extension)
  • How can we get containers to create exactly what we need?
  • How can containers do this with some objects that we only need to create and others that need to be created on a case-by-case basis? (Extension)

Start design below 2.1

Let's start by designing a container (a class) named ApplicationContext

First of all, we need to know the role of this class. First, the necessary function of this class is to load the generated objects as a container. Second, this class needs the ability to create the objects we want based on the configuration.

So we decided to declare a Map-type object for this class to load the object and use its construction method to generate the object

/**
 * IOC Containers, which automatically create objects and object instance stores.
 * Allows us to use instantiated objects directly
 */
public class ApplicationContext {

    /**
     * Store singleton object
     */
    private final Map<String,Object> singleObjContext;

    public ApplicationContext(List<ObjectConfig> objectConfigs){
    }

    /**
     * Gets the specified name object from the container
     * @param beanName Object Name
     * @return
     */
    public Object getObject(String beanName){
        return singleObjContext.get(beanName);
    }
}

Okay, the basic moulds below are built, so here's how to make the container create objects correctly:
We know that there are several elements needed to create an object:
We need to know the type of this object
We need to know the properties of this object and be able to assign values to them
We need to declare a name for this object
As you can see from the above, we can design such a configuration class (omitting the get set method, supplementing itself): ObjectConfig

public class ObjectConfig {

    /**
     * Storage object name
     */
    private String beanName;
    /**
     * Storage object type
     */
    private Class<?> cl;
    /**
     * Stores object property names and assigned values
     */
    private Map<String,Object> diMap;
    /**
     * Is the field type injected by the label basic or reference type
     */
    private Map<String,String> valueType;

    public ObjectConfig(){
        diMap = new HashMap<>();
        valueType = new HashMap<>();
    }
}

Now that we have all these, we can write the most critical method based on this:
Mainly used for reflection characteristics, students who do not know can first understand reflection. This method has some drawbacks. Students who want to improve can improve it, but this is just to demonstrate the learning topic, so there is no too much flexibility.

public ApplicationContext(List<ObjectConfig> objectConfigs){
    singleObjContext = new HashMap<>();
    for (ObjectConfig objectConfig : objectConfigs) {
        try {
            String beanName = objectConfig.getBeanName();
            Class<?> className = objectConfig.getCl();
            Map<String, Object> diMap = objectConfig.getDiMap();
            Object obj = className.getConstructor().newInstance();
            Field[] fields = className.getDeclaredFields();
            AccessibleObject.setAccessible(fields, true);
            //Traversal Injection
            for (Field field : fields) {
                if (diMap.containsKey(field.getName())) {
                    Class<?> cl = diMap.get(field.getName()).getClass();
                    //If the field type is the parent or equal type of the passed parameter type
                    if(field.getType().isAssignableFrom(cl)||field.getType().equals(cl)){
                         field.set(obj,diMap.get(field.getName()));
                    }else{
                        throw new Exception("Type transfer error!!");
                    }
                }
            }
            singleObjContext.put(beanName, obj);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

After that, we can do a test:
First we write a Person class, then we configure it and use containers to create Person objects.

public class Person {
    private String name;
    private Integer age;
    private String sex;
    private Integer height;
    private Integer weight;
}

public class Test {
    public static void main(String[] args) {
        ObjectConfig objectConfig = new ObjectConfig();
        objectConfig.setBeanName("person");
        objectConfig.setCl(Person.class);
        Map<String ,Object> diMap = objectConfig.getDiMap();
        diMap.put("name","Herdsmen");
        diMap.put("age",12);
        diMap.put("sex","male");
        diMap.put("height",170);
        diMap.put("weight",110);
        List<ObjectConfig> objectConfigList = new ArrayList<>();
        objectConfigList.add(objectConfig);
        ApplicationContext applicationContext = new ApplicationContext(objectConfigList);
        Person person = (Person) applicationContext.getObject("person");
        System.out.println(person.toString());
    }
}

As you can see from the test code above, there is no new Person () from the beginning to the end, and we bring Person objects through containers.
Let's see if we can successfully get a person object:

As we can see, configuratively, we do get Person objects from containers.

We can conclude that IOC containers are actually a tool for automatically getting the objects we want through some configuration.

3. What is the difference between programming with and without IOC containers?

From the previous studies, we have implemented a simple IOC container. Let's compare the differences between programming with and without containers using common objects in containers:

public class Test {
    public static void main(String[] args) {
        new Test().IOCCoding();
        new Test().NormalCoding();
    }

    public void IOCCoding(){
        ObjectConfig objectConfig = new ObjectConfig();
        objectConfig.setBeanName("person");
        objectConfig.setCl(Person.class);
        Map<String ,Object> diMap = objectConfig.getDiMap();
        diMap.put("name","Herdsmen");
        diMap.put("age",12);
        diMap.put("sex","male");
        diMap.put("height",170);
        diMap.put("weight",110);
        List<ObjectConfig> objectConfigList = new ArrayList<>();
        objectConfigList.add(objectConfig);

        ApplicationContext applicationContext = new ApplicationContext(objectConfigList);

        Person person = (Person) applicationContext.getObject("person");
        System.out.println(person.toString());
    }

    public void NormalCoding(){
        Person person = new Person();
        person.setName("Siro");
        person.setAge(12);
        person.setSex("male");
        person.setHeight(170);
        person.setWeight(110);
        System.out.println(person);
    }

}

The results are the same, but intuitively, will it feel like more code and more complex to use containers?!

If you think so, you do not yet understand the power of containers!
Let's parse the part of the code above that uses containers:

In practice, there are two lines of code that use containers to program:

ApplicationContext applicationContext = new ApplicationContext(objectConfigList);
Person person = (Person) applicationContext.getObject("person");

We just need to configure the information somewhere, and the container will automatically read and create objects for us. We just need to get them.

Even on Spring, we don't even have to worry about getting it. All of it can be done by configuring it. We just need to use it!!
Let's learn how Spring supports IOC.

4. Support for IOC in Spring

In Spring, we can learn from the following notes:

  • @Component comment: Acts on a class to declare that the class will load objects when the container is initialized.
  • The @Service comment: acts like @Component, but it has a special meaning and is generally used to decorate business classes.
  • The @Repository comment: acts like @Component, but it has a special meaning and is generally used to modify database operation classes.
  • The @Controller comment: acts like @Component, but it has a special meaning and is generally used to decorate the controller class.
  • @Autowired annotation: Inject annotation to implement property injection, similar to @Inject, @Resource
  • @Configuration annotation: used to mark a class as a configuration class
  • @ComponentScan annotation: Used to configure classes to allow containers to automatically scan configuration class flat packages or subpackages for automatic assembly.
  • @Qualifier: Used to resolve ambiguity injection.
  • @Scope: Used to set the scope of a bean, that is, to control whether a bean is created only or on demand.

Okay, here's an example of how we implemented Section 3 with Spring:
First, we'll create a new configuration class in the outermost directory:

Then, create a new Person class and decorate it with annotations:

Next, create a component class: ChangePerson

Finally, we initialize the container on the test method and get what we want, and we don't use new throughout the process

Person object successfully obtained:

Tags: Java programming language

Posted by manlio on Thu, 12 May 2022 19:31:50 +0300