Learning code: DI dependency injection of hand written Spring

Posted by Kingskin on Sat, 11 Jan 2020 18:06:09 +0100

I wrote the IOC implementation of spring before, but now I write the implementation of DI dependency injection

Paste class diagram

Because junit package will be used when DI is implemented for testing, import dependency in pom.xml

  <!--SpringDI-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13-beta-3</version>
            <scope>compile</scope>
        </dependency>

Let's start with a few comments

package com.spring.DI.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutowired {
}
package com.spring.DI.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Before di injection, a factory needs to be created. At runtime, objects are taken from the factory to assign values to properties. Therefore, some preparatory work should be done first
 * Create several annotations to use
 */
//Indicate where notes are used
@Target(ElementType.TYPE)
//When does the defined annotation take effect
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponent {
    /**
     * Define the scope attribute for this annotation
     * @return
     */
    public String scope() default "";
}
package com.spring.DI.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyValue {

    /**
     * Define value attribute
     * @return
     */
    public String value();

}

Create entity classes again (to save space, get and set methods are omitted)

package com.spring.DI.pojo;

import com.spring.DI.annotation.MyComponent;
import com.spring.DI.annotation.MyValue;


/**
 * The attribute value of entity class is temporarily written in annotation mode as a test (it will not be used in practice),
 * This entity is temporarily a singleton class (the scope property is defaulted to singleton class if not specified)
 */
@MyComponent(scope = "prototype")
public class User {

    @MyValue(value = "1")
    private Integer id;
    @MyValue(value = "Wax gourd")
    private String name;
    @MyValue(value = "123456")
    private String password;

    public User() {
        System.out.println("Nonparametric construction method execution");
    }

    public void login() {
        System.out.println("User login: id = " + id + ", name" + name + ", password= " + password);
    }

}

Next, create the UserService class, and inject the User with the dependency in the Service class;

package com.spring.DI.service;

import com.spring.DI.annotation.MyAutowired;
import com.spring.DI.annotation.MyComponent;
import com.spring.DI.pojo.User;

@MyComponent
public class UserService {

    @MyAutowired
    User user1;

    @MyAutowired
    User user2;

    public void userLogin(){
        System.out.println("User 1:"+user1);
        user1.login();

        System.out.println("User 2:"+user2);
        user2.login();
    }
}

Create annotation factory class

package com.spring.DI.applicationContext;

import com.spring.DI.annotation.MyAutowired;
import com.spring.DI.annotation.MyComponent;
import com.spring.DI.annotation.MyValue;

import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Annotation factory class
 */
public class AnnotationConfigApplicationContext {

    //This map container is used to store class definition objects
    private Map<String, Class<?>> beanDefinationFactory = new ConcurrentHashMap<String, Class<?>>();
    //This map container is used to store singleton objects
    private Map<String, Object> singletonBeanFactory = new ConcurrentHashMap<String, Object>();
    
    /**
     * The parameter type specifies the package name to be scanned and loaded. This factory can accept multiple package paths
     */
    public AnnotationConfigApplicationContext(String... packageNames) {
        //Traverse all package paths specified by scan
        for (String packageName : packageNames) {
            System.out.println("Start scanning package:" + packageName);
            //Method of scanning package
            scanPkg(packageName);
        }
        /**
         * DI dependency injection
         * Get the object marked with autowritten in the class definition object container inside the method, and inject
         */
        dependencyInjection();
    }
}
 

In the construction method of factory class, multiple package paths can be received, and each package path can be scanned circularly. The scanPkg method of scanning package is as follows:

 /**
     * In the construction method of the factory class, multiple package paths can be accepted, and each package path can be scanned circularly,
     * This method is used to scan the development package and find the class files in the package
     * For standard (annotation defined on class) class files, reflective loading creates class definition objects and puts them into containers
     *
     * @param pkg
     */
    private void scanPkg(final String pkg) {
        //Replace package path, convert package structure to directory structure
        String pkgDir = pkg.replaceAll("\\.", "/");
        //Get the location of the directory structure in the classpath (where the url encapsulates the path of the specific resource)
        URL url = getClass().getClassLoader().getResource(pkgDir);
        //Based on this path resource (url), build a file object
        File file = new File(url.getFile());
        //Get the specified standards (files ending in. class) in this directory, that is, filter them once
        File[] fs = file.listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                //Get file name
                String fName = file.getName();
                //Determine whether the file is a directory or a file. If it is a directory, further recursion is required
                if (file.isDirectory()) {
                    scanPkg(pkg + "." + fName);
                } else {
                    //Determine whether the file suffix is. class
                    if (fName.endsWith(".class")) {
                        return true;
                    }
                }
                return false;
            }
        });
        //After filtering, traverse all class files
        for (File f : fs) {
            //Get file name User.class
            String fName = f.getName();
            //Get removed. File after class User
            fName = fName.substring(0, fName.lastIndexOf("."));
            //The first letter of a name (class name usually starts in uppercase) is converted to lowercase (stored in the factory as a key) user
            String beanId = String.valueOf(fName.charAt(0)).toLowerCase() + fName.substring(1);
            //Building a class full name (package. Class name) changing the name is where the class is located
            String pkgCls = pkg + "." + fName;
            try {
                //Building class objects by reflection
                Class<?> c = Class.forName(pkgCls);
                //Determine whether there is mycoponent annotation on this class object
                if (c.isAnnotationPresent(MyComponent.class)) {
                    //Store class objects in the map container
                    beanDefinationFactory.put(beanId, c);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

Start injection

 /**
     * After scanning all packages, inject the required attributes
     * This method is used for dependency injection of properties
     * Get all class objects from the factory, if the properties in the class have myautowritten annotation
     * First, get the object from the factory according to the property name, or get the object according to the object type
     * Finally, the object is used to inject attributes
     */
    private void dependencyInjection() {
        //Get all class definition objects in the container
        Collection<Class<?>> classes = beanDefinationFactory.values();
        //Traverse every object
        for (Class<?> cls : classes) {
            //Get the full name of the class object (package name. Class name)
            String clsName = cls.getName();
            //Get the class name. The object address saved by reflection points to the package path. So the class name finds the last point
            clsName = clsName.substring(clsName.lastIndexOf(".") + 1);
            //Converts the first letter of a class name (usually starting with uppercase) to lowercase
            String beanId = String.valueOf(clsName.charAt(0)).toLowerCase() + clsName.substring(1);
            //Get all the properties in the class
            Field[] fields = cls.getDeclaredFields();
            //Traverse each property
            for (Field field : fields) {
                //If there is myautowritten annotation on this attribute, perform the injection operation
                if (field.isAnnotationPresent(MyAutowired.class)) {
                    try {
                        //Get property name user1
                        String fieldName = field.getName();
                        System.out.println("Property name:" + fieldName);
                        //bean object defined for property injection (this object is taken from the container)
                        //This object is used to receive the reflection object generated by the User class
                        Object fieldBean = null;
                        //First, take the object from the container according to the property name, and assign it to the fieldBean object if it is not null
                        if (beanDefinationFactory.get(fieldName) != null) {
                            fieldBean = getBean(fieldName, field.getType());
                        } else {  //Otherwise, the object is taken out of the container and injected according to the type of the property
                            //Get the type of property (package name + class name)
                            String type = field.getType().getName();
                            //Truncate the last class name
                            type = type.substring(type.lastIndexOf(".") + 1);
                            //j converts the first letter of a class name (usually starting with uppercase) to lowercase
                            String fieldBeanId = String.valueOf(type.charAt(0)).toLowerCase() + type.substring(1);
                            System.out.println("Attribute types ID: " + fieldBeanId);
                            //According to the converted type beanId, get the object from the container and assign it to the fieldBean object
                            fieldBean = getBean(fieldBeanId, field.getType());
                        }
                        System.out.println("Value to inject for attribute:" + fieldBean);
                        //If the fieldBean object is not empty, inject the attribute
                        if (fieldBean != null) {
                            //Get instance object of object defined by this class get instance object of userService
                            Object clsBean = getBean(beanId, cls);
                            //Set this property to access
                            field.setAccessible(true);
                            //Inject value for this property inject user object into userService instance object
                            //field.set(object,value) is used to reset the new attribute value, that is
                            //The clsBean object resets the value to fieldBean, that is
                            //The User service is set to User. In this case, the User object is injected into the User service
                            field.set(clsBean, fieldBean);
                            System.out.println("Injection succeeded!");
                        } else {
                            System.out.println("Injection failure");
                        }
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

During injection, the getBean method was called:

/**
 * The getBean method is invoked in the dependencyInjection method.
 * Get the container Object according to the id of the passed bean value, which is of type Object
 *
 * @param beanId user
 * @return
 */
public Object getBean(String beanId) {
    //Get class objects based on the passed beanid
    Class<?> cls = beanDefinationFactory.get(beanId);
    //Get the annotation of its definition according to the class object
    MyComponent annotation = cls.getAnnotation(MyComponent.class);
    //Get the scope property value of the annotation
    String scope = annotation.scope();
    try {
        //If the scope value is singleton, create singleton object
        if ("singleton".equals(scope) || "".equals(scope)) {
            if (singletonBeanFactory.get(beanId) == null) {
                //Determine whether the singleton container has an instance of this class. If not, create it
                Object instance = cls.newInstance();
                //Assign a value to a member property of an object
                setFieldValues(cls, instance);
                //Save in single instance container
                singletonBeanFactory.put(beanId, instance);
            }
            //Get the object in the singleton container according to the beanId and return
            return singletonBeanFactory.get(beanId);
        }
        //If scope = prototype, the creation returns multiple objects
        if ("prototype".equals(scope)) {
            Object instance = cls.newInstance();
            setFieldValues(cls, instance);
            return instance;
        }
        //At present, only single instance and multiple instances are supported to create objects
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    //Returns null if an exception is encountered
    return null;
}
/**
     * This is an overloaded method. According to the class object passed in, it performs a forced internal rotation to return the class object type passed in
     *
     * @param beanId
     * @param c
     * @return
     */
    public <T> T getBean(String beanId, Class<?> c) {
        return (T) getBean(beanId);
    }

In the getBean method, you need to assign a value to the member property of the object and call the setFieldValues method

  /**
     * Get the object from the factory container in the getBean method, and call the setFieldValues method to assign values to the object's properties
     * This method is used to assign values to the properties of an object
     * Internally, the annotation value on the member property is obtained, and then converted to a type, and then reflected as an object
     *
     * @param cls      Class definition object cls is the class path obtained by reflection,
     * @param obj The instance object obj to be assigned is an instance object created by the reflection path
     */
    private void setFieldValues(Class<?> cls, Object obj) {
        //Get all member properties in the class
        Field[] fields = cls.getDeclaredFields();
        //Traverse all properties
        for (Field field : fields) {
            //If this property is decorated with Myvalue, operate on it
            if (field.isAnnotationPresent(MyValue.class)) {
                //Get property name
                String fieldName = field.getName();
                //Get value in annotation
                String value = field.getAnnotation(MyValue.class).value();
                //Get the type of property definition
                String type = field.getType().getName();
                //Change the property name to start with an uppercase letter: for example, change ID to ID
                fieldName = String.valueOf(fieldName.charAt(0)).toUpperCase() + fieldName.substring(1);
                //Define set method name
                String setterName = "set" + fieldName;
                try {
                    //Returns a Method object that reflects the declared Method of the Class or interface represented by this Class object
                    //That is, get all the methods in cls class
                    Method method = cls.getDeclaredMethod(setterName, field.getType());
                    //If an attribute type is judged, if the type is inconsistent, the set method is called to assign the attribute to the transformation type.
                    if ("java.lang.Integer".equals(type) || "int".equals(type)) {
                        int intValue = Integer.valueOf(value);
                        //Inject the method into the reflection object, because the invoke method returns an object, and the parameter must be
                        //Packing type, so type conversion
                        method.invoke(obj, intValue);
                    }else if ("java.lang.String".equals(type)){
                        method.invoke(obj,value);
                    }
                    //As a test, only Integer and String types are judged. Other types are the same
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }

            }
        }

    }

Finally release resources

 /**
     * Destroy method for releasing resources
     */
    public void close(){
        beanDefinationFactory.clear();
        beanDefinationFactory=null;
        singletonBeanFactory.clear();
        singletonBeanFactory=null;
    }

Now that the DI implementation is completed, start the test

package com.spring.DI.springTest;

import com.spring.DI.annotation.MyComponent;
import com.spring.DI.applicationContext.AnnotationConfigApplicationContext;
import com.spring.DI.service.UserService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

@MyComponent
public class TestSpringDi {
    //Create annotationconfiguapplicationcontext factory
    AnnotationConfigApplicationContext ctx;
    //Create UserService object
    UserService userService;
    /**
     * Initialization method
     */
    @Before
    public void init() {
        //Instance factory class, passing in pojo/service/test three package paths for scanning
        ctx = new AnnotationConfigApplicationContext
                ("com.spring.DI.pojo",
                        "com.spring.DI.service", "com.spring.DI.springTest");

        userService = ctx.getBean("userService", UserService.class);
    }

    @Test
    public void userLogin() {
        userService.userLogin();
    }

    /**
     * Destruction method
     */
    @After
    public void close() {
        ctx.close();
    }

}

Operation result

Published 4 original articles, won praise 0, visited 12
Private letter follow

Topics: Java Spring Attribute Junit