The original address: www.xilidou.com/2018/01/08/…

Spring, the de facto standard for J2ee development, is a framework that every Java developer needs to know. But the IoC and Aop features of Spring are somewhat difficult to understand for novice Java developers. So I thought I’d write a series of articles about these features. This gives you further insight into the Spring framework.

After reading this article, you will know:

  • What are dependency injection and inversion of control
  • What is Ioc for
  • How is Spring’s Ioc implemented
  • Develop a simple Ioc framework along the lines of Spring

What is IoC?

Wikipedia says:

Inversion of Control, or IoC, is a design principle used in object-oriented programming to reduce coupling between computer code. One of the most common is called Dependency Injection (DI). With inversion of control, when an object is created, it is passed a reference to the object on which it depends by an external entity that regulates all objects in the system. In other words, dependencies are injected into objects.

What is Ioc for?

After reading the above explanation, you must not understand what IoC is, because it is the first time to see the above words also feel confused.

However, from the above description, we can get a general idea that the purpose of using IoC is for decoupling. In other words, IoC is a method of decoupling.

We know that Java is an object-oriented language, in Java Everything is Object, our program is made up of several objects. As our projects get bigger and more developers work with us, we will have more and more classes, and the number of references between classes will grow exponentially. As shown below:

Such a project would be a disaster if we introduced the IoC framework. The framework maintains the life cycle of classes and references between classes. Our system would look like this:

At this point we found that the IoC framework took care of the dimension of the relationships between our classes, and injected the classes into the required classes. That is, the consumer of the class is only responsible for its use, not its maintenance. Leave professional things to professional frameworks. Greatly reduce development complexity.

Use an analogy to understand the problem. Ioc framework is the real estate agent in our daily life. First of all, the agent will collect the houses in the market and establish contact with the landlords of each house. When we need to rent a house, we don’t need to look around for all kinds of rental information. We directly find a housing agency, the agency will provide the corresponding housing information according to your needs. Greatly improve the efficiency of renting, reduce the frequency of communication between you and various landlords.

How is Spring’s IoC implemented

The most direct way to learn about the Spring framework is to read the Spring source code. But Spring has a high level of code abstraction and handles a high level of detail. It’s not easy for most people to understand. After reading the source code of Spirng, I made a summary based on my understanding. Spirng IoC mainly consists of the following steps.

1. Initialize the IoC container. 2. Read the configuration file. 3. Convert the configuration file to a container-aware data structure (called BeanDefinition in Spring) 4. Use data structures to instantiate corresponding objects in turn. 5. Inject dependencies between objectsCopy the code

Implement an IoC framework yourself

For convenience, we refer to the IoC implementation of Spirng and remove all logic that is irrelevant to the core principles. A minimalist framework for implementing IoC. The project uses JSON as the configuration file. Use Maven to manage jar package dependencies.

In this framework our objects are singletons and do not support Spirng’s multiple scopes. The framework is implemented using cglib and Java reflection. I also used Lombok in the project to simplify the code.

Now let’s write the IoC framework.

First let’s look at the basic structure of the framework:

At a macro level, the framework consists of three packages that define our framework’s data structures in package beans. Core is the core logic of our framework. Utils are some general utility classes. Let’s go through them one by one:

1. Beans define the data structure of the framework

BeanDefinition is the core data structure of our project. Used to describe the objects that we need the IoC framework to manage.

@Data
@ToString
public class BeanDefinition {

    private String name;

    private String className;

    private String interfaceName;

    private List<ConstructorArg> constructorArgs;

    private List<PropertyArg> propertyArgs;

}
Copy the code

Contains the name of the object, the name of the class. If it is the implementation of the interface, then the interface that the object implements. And the list of arguments passed to constructors, constructorArgs, and the list of arguments to inject, propertyArgs.

2. Take a look at the objects in our tool class package:

ClassUtils handles the loading of Java classes as follows:

public class ClassUtils {
    public static ClassLoader getDefultClassLoader(a){
        return Thread.currentThread().getContextClassLoader();
    }
    public static Class loadClass(String className){
        try {
            return getDefultClassLoader().loadClass(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null; }}Copy the code

We just wrote a method that gets the Class of the object using the className parameter.

BeanUtils handles instantiation of objects. Here we use the cglib toolkit as follows:

public class BeanUtils {
    public static <T> T instanceByCglib(Class<T> clz,Constructor ctr,Object[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clz);
        enhancer.setCallback(NoOp.INSTANCE);
        if(ctr == null) {return (T) enhancer.create();
        }else {
            return(T) enhancer.create(ctr.getParameterTypes(),args); }}}Copy the code

ReflectionUtils implements dependency injection of objects using Java’s reflection principle:

public class ReflectionUtils {

    public static void injectField(Field field,Object obj,Object value) throws IllegalAccessException {
        if(field ! =null) {
            field.setAccessible(true); field.set(obj, value); }}}Copy the code

InjectField (Field Field,Object obj,Object Value) This method sets the Field of obj to value.

JsonUtils is used to parse our JSON configuration file. The code is quite long and has little to do with our IoC principle. Interested students can download the code from Github to have a look.

With these tools in hand, we can begin to complete the core code of the Ioc framework.

3. Core logic

My IoC framework currently supports only one type of ByName injection. So our BeanFactory has only one method:

public interface BeanFactory {
    Object getBean(String name) throws Exception;
}
Copy the code

Then we implement this method:

public class BeanFactoryImpl implements BeanFactory{

    private static final ConcurrentHashMap<String,Object> beanMap = new ConcurrentHashMap<>();

    private static final ConcurrentHashMap<String,BeanDefinition> beanDefineMap= new ConcurrentHashMap<>();

    private static final Set<String> beanNameSet = Collections.synchronizedSet(new HashSet<>());

    @Override
    public Object getBean(String name) throws Exception {
        // Find if the object has already been instantiated
        Object bean = beanMap.get(name);
        if(bean ! =null) {return bean;
        }
        // If there is no instantiation, you need to call createBean to create the object
        bean =  createBean(beanDefineMap.get(name));
        
        if(bean ! =null) {

            // After the object is successfully created, inject the parameters required by the object
            populatebean(bean);
            
            // Save the object to Map for future use.
            beanMap.put(name,bean;
        }

        // End return
        return bean;
    }

    protected void registerBean(String name, BeanDefinition bd){
        beanDefineMap.put(name,bd);
        beanNameSet.add(name);
    }

    private Object createBean(BeanDefinition beanDefinition) throws Exception {
        String beanName = beanDefinition.getClassName();
        Class clz = ClassUtils.loadClass(beanName);
        if(clz == null) {
            throw new Exception("can not find bean by beanName");
        }
        List<ConstructorArg> constructorArgs = beanDefinition.getConstructorArgs();
        if(constructorArgs ! =null && !constructorArgs.isEmpty()){
            List<Object> objects = new ArrayList<>();
            for (ConstructorArg constructorArg : constructorArgs) {
                objects.add(getBean(constructorArg.getRef()));
            }
            return BeanUtils.instanceByCglib(clz,clz.getConstructor(),objects.toArray());
        }else {
            return BeanUtils.instanceByCglib(clz,null.null); }}private void populatebean(Object bean) throws Exception {
        Field[] fields = bean.getClass().getSuperclass().getDeclaredFields();
        if(fields ! =null && fields.length > 0) {
            for (Field field : fields) {
                String beanName = field.getName();
                beanName = StringUtils.uncapitalize(beanName);
                if (beanNameSet.contains(field.getName())) {
                    Object fieldBean = getBean(beanName);
                    if(fieldBean ! =null) {
                        ReflectionUtils.injectField(field,bean,fieldBean);
                    }
                }
            }
        }
    }
}
Copy the code

First we look at the implementation of the BeanFactory. We have two hashMaps, beanMap and beanDefineMap. BeanDefineMap stores a mapping between the name of an object and its corresponding data structure. BeanMap is used to hold beanName and instantiated objects.

Container initialization, invoked BeanFactoryImpl. RegisterBean method. Store the BeanDefination data structure of the object.

When we call the getBean() method. I’m going to go to the beanMap and see if there’s any instantiated objects. If not, it goes to beanDefineMap to find the BeanDefination for this object. Use DeanDefination to instantiate an object.

Once the object is successfully instantiated, we also need to inject the parameters and call the populateBean () method. In populateBean this method, the inside of the scanned object Field, if the object in the Field is our IoC container management object, that is called ReflectionUtils we achieve above injectField to inject objects.

With everything in place, our client completed the entire IoC process. Finally, the object is placed in the beanMap for future use.

So we know that BeanFactory is where objects are managed and generated.

4. Container

The container is an extension to the BeanFactory that manages the BeanFactory. Our IoC framework uses Json as a configuration file, so we named our container JsonApplicationContext. After, of course, you are willing to implement XML as the container configuration file, you can write a XmlApplicationContext, if can call AnnotationApplcationContext annotation-based container. I’ll leave the implementation to you.

Let’s look at the code for ApplicationContext:

public class JsonApplicationContext extends BeanFactoryImpl{
    private String fileName;
    public JsonApplicationContext(String fileName) {
        this.fileName = fileName;
    }
    public void init(a){
        loadFile();
    }
    private void loadFile(a){
        InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
        List<BeanDefinition> beanDefinitions = JsonUtils.readValue(is,new TypeReference<List<BeanDefinition>>(){});
        if(beanDefinitions ! =null && !beanDefinitions.isEmpty()) {
            for(BeanDefinition beanDefinition : beanDefinitions) { registerBean(beanDefinition.getName(), beanDefinition); }}}}Copy the code

The purpose of this container is to read configuration files. Convert the configuration file to BeanDefination that the container can understand. Then use the registerBean method. Register this object.

At this point, a simple version of the IoC framework is complete.

5. Use of frames

Let’s write a test class to see how our framework works:

First we have three objects

public class Hand {
    public void waveHand(a){
        System.out.println("Wave your hand."); }}public class Mouth {
    public void speak(a){
        System.out.println("say hello world"); }}public class Robot {
    // Need to inject hand and mouth
    private Hand hand;
    private Mouth mouth;

    public void show(a){ hand.waveHand(); mouth.speak(); }}Copy the code

We need to inject hand and mouth into our Robot.

Configuration file:

[{"name":"robot"."className":"com.xilidou.framework.ioc.entity.Robot"
  },
  {
    "name":"hand"."className":"com.xilidou.framework.ioc.entity.Hand"
  },
  {
    "name":"mouth"."className":"com.xilidou.framework.ioc.entity.Mouth"}]Copy the code

Write a test class at this time:

public class Test {
    public static void main(String[] args) throws Exception {
        JsonApplicationContext applicationContext = new JsonApplicationContext("application.json");
        applicationContext.init();
        Robot aiRobot = (Robot) applicationContext.getBean("robot"); aiRobot.show(); }}Copy the code

Output after run:

Say Hello world Process finished with exit code 0Copy the code

You can see that we have successfully injected hand and mouth into my aiRobot.

So far, we have completed the Ioc framework development.

conclusion

By the end of this article, I’m sure you’ve also implemented a simple IoC framework.

Although reading source code is the ultimate means to understand the framework. But the Spring framework is a production framework, and in order to be generic and stable, the source code must be highly abstract and handle a lot of detail. So Spring’s source code is still quite difficult to read. Hopefully, this article will help you understand the implementation of Spring Ioc.

The next article should be “Hands-off Frameworks — Implementing AOP”.

Making address: https://github.com/diaozxin007/xilidou-framework