[self research framework I] self research framework IoC container and single instance mode

Posted by firedrop on Tue, 18 Jan 2022 13:54:52 +0100

Use of self-developed frame IoC container

The most basic functions of the framework

◆ resolution configuration
◆ locate and register the object, which is a mark (annotation). After locating the object, you need to register the object in the container
◆ inject the object and return the object to the user accurately when the user needs it

◆ provide general tools to facilitate users and frameworks to call according to their own needs

Implementation of IoC container

Object: generally refers to something marked, usually a class. Store the class name and class into the container as key value pairs.

What is stored is not the class instance itself, but the class object, xml attribute information or annotation of the class. They form beanFactory and are stored in the container. Then decide whether to directly create the target wrapped instance when the container is initialized, or create the target wrapped instance where the target object is used for the first time.

After implementing the container, solve the problems of IoC output and dependency injection.

1, Create annotation

First create the common annotation @ Controller (this class needs to be managed by the spring container to facilitate the management of front-end requests)

@Service (representing the implementation class of the service layer)

@Repository (database operation)

@Component (general spring container management)

With component, why do you have the above three different annotations?

It can logically divide different modules, adopt specific functional annotation primary keys for different use scenarios, and filter them according to specific tags when it is necessary to obtain, for example, the Controller class

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}

2, Extract annotated objects

And inject them into the container,

Traversal range: the class that needs to traverse the specified range

Realization idea

◆ specify the range and obtain all classes within the range
◆ traverse all classes, get the annotated classes and load them into the container

The first step is to extract the implementation and integrate it into tool classes. (scan all classes in the package according to the package of the business item passed in by the user)

Get all the following classes according to the incoming package

What needs to be done in extractPacakgeClass

◆ get the class loader
◆ obtain the loaded resource information through the class loader
◆ obtain the collection of resources in different ways according to different resource types

Gets the purpose of the project class loader

Gets the actual path of the project publication

com.imooc, we can't locate its specific path, so we must get the specific path first before we can get all the class files under the path.

Why not let users pass in absolute paths

◆ not friendly enough: the paths between different machines may be different
◆ if you hit the war package or jar package, you can't find the path at all
◆ therefore, the general practice is to obtain the data through the class loader of the project

ClassLoader

◆ find or generate the corresponding bytecode according to the name of a specified class
◆ resources required for loading Java applications (picture files, configuration files, resource directories, etc.)

Programs are executed by threads. You can get the loader to which the currently executing thread belongs.

/**
 * Get class loader
 * @return Current ClassLoader
 */
public static ClassLoader getClassLoader(){
    return Thread.currentThread().getContextClassLoader();
}

How to get the loaded resources through the class loader?

Uniform Resource Locator
Unique address of a resource
◆ by obtaining Java net. The URL instance obtains information such as protocol name, resource name and path

Filter the resource of file with lowercase protocol.

The path is an absolute path. If it is a file, the file name will also be included.

The separator for obtaining resources is divided by / but the package name is divided by Split, so you need to deal with packageName first

Judge the ur null value and record the null value in the log

When the file is found, the recursion stops, and it terminates when it is judged that it is not a directory. If it is a folder, list all files and folders (excluding subfolders) under the current folder

We only focus on folders and class files. return true filters them out. After detecting the class file, load the class instance into the collection

Recursively filter the directory under the folder and load the class instance of its class file into the collection

Why judge that file is empty? Because it is not judged that an exception will be reported in the foreach loop. That is, in the foreach loop, the null value must be judged first.

The internal class can use the attribute value of the external class. The external class has packageName, and the internal class can obtain the path and full path name of the class

Encapsulate the Class object as a general method,

Finally, ClassUtil extracts the object code marked by the annotation:

package org.simpleframework.util;

import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileFilter;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

@Slf4j
public class ClassUtil {

    public static final String FILE_PROTOCOL = "file";

    /**
     *Get the class collection under the package
     * @param packageName
     * @return A collection of all classes under the package
     */
    public static Set<Class<?>> extractPackageClass(String packageName){
        //1. Get class loader
        ClassLoader classLoader = getClassLoader();
        //2. Get the resources to be loaded through the class loader
        //The separator for obtaining resources is divided by / but the package name is divided by Split, so you need to deal with packageName first
        URL url = classLoader.getResource(packageName.replace(".", "/"));
        if(url==null){
            log.warn("unable to retrieve anything from package:" +packageName);
            return null;
        }
        //3. According to different resource types, the collection of resources is obtained in different ways
        Set<Class<?>> classSet=null;
        //Filter out the resource of file type and get the protocol name with getProtocol()
        if(url.getProtocol().equalsIgnoreCase(FILE_PROTOCOL )){
            //Create a collection instance
            classSet=new HashSet<Class<?>>();
            //Get the actual path of the resource
            File packageDirectory = new File(url.getPath());
            //Traverse directories and subdirectories to get class files
            extractClassFile(classSet,packageDirectory,packageName);
        }
        //TODO can add processing for other types of resources here. For example, after being packaged in jar, the protocol is jar
        return classSet;
    }

    /**
     *
     * @param emptyClassSet Load the collection of target classes
     * @param fileSource  File or directory
     * @param packageName  Package name
     */
    private static void extractClassFile(Set<Class<?>> emptyClassSet, File fileSource, String packageName) {
        if(!fileSource.isDirectory()){//When the file is found, the recursion stops, and it terminates when it is judged that it is not a directory.
            return;
        }
        //If it is a folder, list all files and folders (excluding subfolders) under the current folder
        File[] files = fileSource.listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                if(file.isDirectory()){//It's a folder
                    return true;
                }else {//The file needs to determine whether it is a class file
                    //Gets the absolute value path of the file
                    String absolutePath = file.getAbsolutePath();
                    if(absolutePath.endsWith(".class")){
                        //If it is a class file, it will be loaded directly
                        addToClassSet(absolutePath);
                    }
                    return false;
                }

            }
            //According to the absolute value path of the class file, obtain and generate the class object and put it into the classSet
            private void addToClassSet(String absolutePath) {
                //1. Extract the class name containing the package from the absolute value path of the class file
                //Converts the delimiter in the absolute path to
                absolutePath =absolutePath.replace(File.separator,".");
                String className = absolutePath.substring(absolutePath.indexOf(packageName));
                //Include className in Remove the class suffix
                className=className.substring(0,className.lastIndexOf("."));
                //2. Get the corresponding Class object through the reflection mechanism and add it to the classSet
                Class targetClass = loadClass(className);
                emptyClassSet.add(targetClass);
            }
        });

        if(files!=null){//First judge whether the obtained files are empty and make recursive calls during traversal
            for (File f : files) {
                //Recursive call
                extractClassFile(emptyClassSet, f, packageName);
            }
        }

    }

    /**
     * Get Class object
     * @param className=package+Class name
     * @return Class
     */
    public static Class<?> loadClass(String className){
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            log.error("load class error:",e);
            throw new RuntimeException(e);
        }
    }

    /**
     * Get class loader
     * @return Current ClassLoader
     */
    public static ClassLoader getClassLoader(){
        return Thread.currentThread().getContextClassLoader();
    }


}

test

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.8.1</version>
    <scope>test</scope>
</dependency>

Create the same directory in test

@Test
public void extractPackageClassTest(){
    Set<Class<?>> classSet = ClassUtil.extractPackageClass("com.imooc.entity");
    System.out.println(classSet);
    Assertions.assertEquals(4,classSet.size());
}

Singleton Pattern

After solving the problem of instance creation, create a container again. Because the same container needs to manage all the objects to be managed, the container needs to be implemented with a single instance.

Ensure that there is only one instance of a class and provide unified access to the outside world. The client does not need and cannot instantiate the object of this class.

Singleton thread safety:

◆ starvation mode:

Class is initialized and a unique instance is created as soon as it is loaded

package demo.pattern.singleton;

public class StarvingSingleton {
    private static final StarvingSingleton starvingSingleton=new StarvingSingleton();
    private StarvingSingleton(){}
    public static StarvingSingleton getInstance(){
        return starvingSingleton;
    }

    public static void main(String[] args) {
        System.out.println(StarvingSingleton.getInstance());
        System.out.println(StarvingSingleton.getInstance());
    }
}

The constructor is set to private to prevent external new from creating instances. Set to static to ensure uniqueness, and set to private. The outside world cannot pass the class name. Called in the same way. Set to final and cannot be changed once initialized. We can only obtain the instance object already created through the methods we provide.

◆ lazy mode:

A unique instance is created only when it is first called by the client

◆ the lazy mode with double check lock mechanism can ensure thread safety

package demo.pattern.singleton;

public class LazyDoubleCheckSingleton {
    
    //Add volatile to prevent reordering, so as to avoid the situation that the memory space has been allocated to the object before initialization
    private volatile static LazyDoubleCheckSingleton instance;

    private LazyDoubleCheckSingleton(){}

    public static LazyDoubleCheckSingleton getInstance(){
        //First test
        if(instance==null){
            //Lock
            synchronized (LazyDoubleCheckSingleton.class){
                //Second judgment
                if(instance==null){
                    //This instruction is divided into three steps, and the volatile keyword is added to make it execute in strict order without 132 cases
                    /*
                    * 1,Allocate memory space
                    * 2,Initialize object
                    * 3,Set instance to point to the memory address just allocated. At this time, the instance object! null*/
                    instance=new LazyDoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

Using reflection, you can obtain private construction methods and set their private modifier to invalid, so as to create instances, which shows that the singleton mode is not absolutely safe

Class can't defend against reflection attacks. Equip the hungry man to enumerate attacks that ignore reflection.

Define private enumerations, and define member variables and instances of enumerations. The constructor of enumeration type itself is private and cannot be created by others, so it is the same with or without the private modifier. The instance of enumeration type will be created during initialization, which is also a hungry man type.

Protect the instance by enumerating.

package demo.pattern.singleton;

public class EnumStarvingSingleton {
    private EnumStarvingSingleton(){}

    public static EnumStarvingSingleton getInstance(){
        return ContainerHolder.HOLDER.instance;
    }

    private enum ContainerHolder{
        HOLDER;
        private EnumStarvingSingleton instance;
        ContainerHolder(){
            instance=new EnumStarvingSingleton();
        }
    }
}

The test shows that the instance object obtained by method and the instance obtained by creating object through class object reflection are the same

Enumerating private functions can be lower than reflection attacks, and a more secure container can be created in this way.

Decompile: javap compile: javac

The hungry man mode equipped with enumeration can resist the attack of reflection and serialization, and 4 meet the requirements of containers

Write the created singleton to the file through serialization, and then create the singleton in reverse order through the file. There are likely to be two different instances.

The objects created through the newInstance method of class are called beans, so the Ioc container is named BeanContainer

Constructor without parameters and set modifiers.

Sets the member variable of the private enumeration. HOLDER is used to hold BeanContainer instances. Define the private member variable, define the private constructor of enumeration, and create a new instance in it.

Solve the singleton output to facilitate the outside world to obtain singleton objects.

package org.simpleframework.core;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE) //Parameterless constructor, which can contain private
public class BeanContainer {

    /**
     * Get Bean container instance
     * @return
     */
    public static BeanContainer getInstance(){
        return ContainerHolder.HOLDER.instance;
    }

    private enum ContainerHolder{//Sets the member variable of a private enumeration
        HOLDER;//Used to hold BeanContainer instances
        private BeanContainer instance;//Define private member variables
        //Enumeration, in which the new instance
        ContainerHolder{
            instance=new BeanContainer();
        }
    }
}

Implementation container

Components of container

◆ a carrier that holds Class objects and their instances
◆ loading of containers (it is necessary to define the method of configuring, acquiring and filtering target objects)

◆ operation mode of container

Not all class objects are container managed objects. Instead, select the class object specified in the configuration, that is, the object marked with annotation, and store it in the carrier for management.

1. Store class objects and corresponding instances in the map in the form of key value pairs

Implement container loading

Realization idea

◆ management and acquisition of configuration (how to manage annotations, read the target annotation at any time, and get the class marked by it)
◆ obtain Class objects within the specified range

◆ extract the Class object according to the configuration, together with instance 1 - and store it in the container

Solve the problem of configuration: create a configuration carrier to save the relevant configuration.

Use asList to convert to the corresponding list instance

//Store the Map, class object and corresponding instance of all configured marked target objects
private final Map<Class<?>,Object> beanMap=new ConcurrentHashMap<>();

/*
* Load the annotation list of the bean
* */
private static final List<Class<? extends Annotation>> BEAN_ANNOTATION
        = Arrays.asList(Component.class, Controller.class, Service.class, Repository.class);

When modified by these annotations, it needs to be managed by the bean container container

Loading beans and getting containers are separated, so there is no need to return values. Pass in the loaded range and return the beans within the range.

1. Get all class es of package

2. Judge the null value of the collection, log and return. When you get it, you can cycle through the classset and get the corresponding class object according to the defined annotation

/**
 * Scan and load all beans
 * @param packageName Package name
 */
public void loadBeans(String packageName){
    //1. Get all class es of package
    Set<Class<?>> classSet = ClassUtil.extractPackageClass(packageName);
    //2. Judge the null value of the collection, log and return. When you get it, you can cycle through the classset and get the corresponding class object according to the defined annotation
    if(classSet==null || classSet.isEmpty()){
        log.warn("extract nothing from packageName "+packageName);
        return;
    }

    for (Class<?> clazz : classSet) {
        for (Class<? extends Annotation> annotation : BEAN_ANNOTATION) {
            //The class is marked with our defined annotations
            if(clazz.isAnnotationPresent(annotation)){
                //Put the target class itself as the key and the target class instance as v into the beanMap
                beanMap.put(clazz,ClassUtil.newInstance(clazz,true));
            }
        }
    }
}

The general method of creating class object instances allows users to decide whether to generate instances with private modifier

/**
 * Gets the instantiated object of the class
 * @param clazz Class
 * @param accessible  Does it support creating instances of private class objects
 * @param <T>  class Type of
 * @return Class instantiation
 */
public static <T> T newInstance(Class<?> clazz, boolean accessible) {
    try {
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(accessible);
        return (T) constructor.newInstance();
        
    } catch (Exception e) {
        log.error("newInstance error ",e);
        throw new RuntimeException(e);
    }
}

Then extract the above logic to judge whether the collection is empty as a tool class

It also determines whether the string, array and Map are empty

package org.simpleframework.util;

import java.util.Collection;
import java.util.Map;

public class ValidationUtil {

    /**
     * Collection Is null or size 0
     * @param obj Collection collection
     * @return Is it empty
     */
    public static boolean isEmpty(Collection<?> obj){
        return obj==null || obj.isEmpty();
    }

    public static boolean isEmpty(String obj){
        return obj==null || "".equals(obj);
    }

    public static boolean isEmpty(Object[] obj){
        return obj==null || obj.length==0;
    }

    public static boolean isEmpty(Map<?,?> obj){
        return obj==null || obj.isEmpty();
    }


}

reform

if(ValidationUtil.isEmpty(classSet)){
    log.warn("extract nothing from packageName "+packageName);
    return;
}

Since container loading is a time-consuming process, in order to avoid repeated loading, we can define i a private Boolean member variable to judge whether the container has been loaded.

/*
* Has the container loaded bean s
* */
private boolean loaded=false;

public boolean isLoaded(){
    return loaded;
}

restructure

After the container is loaded, modify the value of the variable.

For thread safety (two threads modify the value of the variable), the method is defined as synchronous

/**
 * Scan load all beans
 * @param packageName Package name
 */
public synchronized void loadBeans(String packageName){
    //Determine whether the bean container has been loaded
    if(isLoaded()){
        log.warn("BeanContainer has been loaded");
        return;
    }
    //1. Get all class es of package
    Set<Class<?>> classSet = ClassUtil.extractPackageClass(packageName);
    //2. Judge the null value of the collection, log and return. When you get it, you can cycle through the classset and get the corresponding class object according to the defined annotation
    if(ValidationUtil.isEmpty(classSet)){
        log.warn("extract nothing from packageName "+packageName);
        return;
    }

    for (Class<?> clazz : classSet) {
        for (Class<? extends Annotation> annotation : BEAN_ANNOTATION) {
            //The class is marked with our defined annotations
            if(clazz.isAnnotationPresent(annotation)){
                //Put the target class itself as the key and the target class instance as v into the beanMap
                beanMap.put(clazz,ClassUtil.newInstance(clazz,true));
            }
        }
    }
    loaded=true;
}

Unit test, create the same directory

@BeforeAll: initialize only once, before all tests start. The test method is not initialized every time it is executed.

First judge whether it has been loaded.

Find out all the classes modified by the annotation under the core, create the corresponding instances of the class, and transfer them to the container.

Add the corresponding label to the previous class, and pay attention to the service label to the implementation class of the service

Define a common method to get the size of the beanmap

/*
* Get the number of Bean instances
* */
public int size(){
    return beanMap.size();
}

Execute on the entire test class

package org.simpleframework.core;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

public class BeanContainerTest {
    private static BeanContainer beanContainer;

    @BeforeAll  //@BeforeAll: initialize only once
    static void init(){
        beanContainer=BeanContainer.getInstance();
    }

    @Test
    public void loadBeansTest(){
        //First determine whether to initialize
        Assertions.assertEquals(false,beanContainer.isLoaded());
        beanContainer.loadBeans("com.imooc");
        Assertions.assertEquals(6,beanContainer.size());
        Assertions.assertEquals(true,beanContainer.isLoaded());

    }

}

Overall container loading code:

package org.simpleframework.core;

@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE) //Parameterless constructor, which can contain private
public class BeanContainer {

    //Store the Map, class object and corresponding instance of all configured marked target objects
    private final Map<Class<?>,Object> beanMap=new ConcurrentHashMap<>();

    /*
    * Load the annotation list of the bean
    * */
    private static final List<Class<? extends Annotation>> BEAN_ANNOTATION
            = Arrays.asList(Component.class, Controller.class, Service.class, Repository.class);

    /*
    * Has the container loaded bean s
    * */
    private boolean loaded=false;

    public boolean isLoaded(){
        return loaded;
    }

    /**
     * Get Bean container instance
     * @return
     */
    public static BeanContainer getInstance(){
        return ContainerHolder.HOLDER.instance;
    }
    /*
    * Get the number of Bean instances
    * */
    public int size(){
        return beanMap.size();
    }

    /**
     * Scan load all beans
     * @param packageName Package name
     */
    public synchronized void loadBeans(String packageName){
        //Determine whether the bean container has been loaded
        if(isLoaded()){
            log.warn("BeanContainer has been loaded");
            return;
        }
        //1. Get all class es of package
        Set<Class<?>> classSet = ClassUtil.extractPackageClass(packageName);
        //2. Judge the null value of the collection, log and return. When you get it, you can cycle through the classset and get the corresponding class object according to the defined annotation
        if(ValidationUtil.isEmpty(classSet)){
            log.warn("extract nothing from packageName "+packageName);
            return;
        }

        for (Class<?> clazz : classSet) {
            for (Class<? extends Annotation> annotation : BEAN_ANNOTATION) {
                //The class is marked with our defined annotations
                if(clazz.isAnnotationPresent(annotation)){
                    //Put the target class itself as the key and the target class instance as v into the beanMap
                    beanMap.put(clazz,ClassUtil.newInstance(clazz,true));
                }
            }
        }
        loaded=true;
    }

    private enum ContainerHolder{//Sets the member variable of a private enumeration
        HOLDER;//Used to hold BeanContainer instances
        private BeanContainer instance;//Define private member variables
        //Enumeration, in which the new instance
        ContainerHolder(){
            instance=new BeanContainer();
        }
    }
}

Operation mode of container

Obtain the implementation class of a service interface according to the interface

Implement the operation mode of the container
Adding, deleting, modifying and querying containers

◆ add and delete
◆ obtain the annotated Cla through annotation
◆ obtain corresponding instances according to Class

◆ obtain the corresponding subclass Class through superclass
◆ obtain all classes and instances

◆ obtain the quantity of the container carrier

Add bean

/**
 * Add a Class object and its corresponding Bean instance to the container
 * @param clazz
 * @param Bean
 * @return
 */
public Object addBean(Class<?> clazz,Object Bean){
    return beanMap.put(clazz, Bean);
}

Delete: delete by key

/**
 * Delete managed objects in container
 * @param clazz
 * @return
 */
public Object removeBean(Class<?> clazz){
    return beanMap.remove(clazz);
}

Get bean instance

/**
 * Get Bean instance from Class object
 * @param clazz
 * @return
 */
public Object getBean(Class<?> clazz){
    return beanMap.get(clazz);
}

Get all bean managed objects, that is, the collection of key s

/**
 * Gets a collection of all Class objects managed by the container
 * @return Class aggregate
 */
public Set<Class<?>> getClasses(){
    return beanMap.keySet();
}

Get all bean instances, that is, the collection of v, keep consistent with the key, and forcibly convert to set

/**
 * Get all Bean collections
 * @return
 */
public Set<Object> getBeans(){
    return (Set<Object>) beanMap.values();
}

Obtain all Class objects, that is, all key s in the container according to the annotation, judge the null value (null or empty) of the returned classSet set, and return the results uniformly

/**
 * Obtain all Class objects in the container according to the annotation, that is, all key s
 * @param annotation
 * @return 
 */
public Set<Class<?>> getClassesByAnnotation(Class< ? extends Annotation> annotation){
    //1. Get all class objects of beanMap
    Set<Class<?>> keySet = getClasses();
    if(ValidationUtil.isEmpty(keySet)){
        log.warn("nothing in beanMap");
        return null;
    }
    //2. Filter the class objects marked by annotations through annotations and add them to the classSet
    Set<Class<?>> classSet=new HashSet<>();
    for (Class<?> clazz : keySet) {
        //Does the class have an associated annotation tag
        if(clazz.isAnnotationPresent(annotation)){
            classSet.add(clazz);
        }
    }
    return classSet.size()>0 ? classSet : null ;
}

Get the Class collection of the implementation Class or subclass through the interface or parent Class, excluding itself

/**
 * Get the Class collection of the implementation Class or subclass through the interface or parent Class, excluding itself
 */
public Set<Class<?>> getClassesBySuper(Class<?> interfaceOrClass){
    //1. Get all class objects of beanMap
    Set<Class<?>> keySet = getClasses();
    if(ValidationUtil.isEmpty(keySet)){
        log.warn("nothing in beanMap");
        return null;
    }
    //2. Judge whether the elements in the collection are subclasses of the incoming interface or class, and add them to the classSet
    Set<Class<?>> classSet=new HashSet<>();
    for (Class<?> clazz : keySet) {
        //Judge whether the class passes in the subclass of the parameter, and remove itself
        if(interfaceOrClass.isAssignableFrom(clazz) && !clazz.equals(interfaceOrClass)){
            classSet.add(clazz);
        }
    }
    return classSet.size()>0 ? classSet : null ;
}
A.isAssignableFrom(B); //To judge whether A is the parent class of B, you can also see whether AB and ab are the same or have inheritance relationship, which is not limited to parent-child classes, implementation classes, and grandparents and grandchildren 
Parent class.isAssignableFrom(Subclass);
Interface.isAssignableFrom(Implementation class);

test

The operations on the container are based on the container being loaded, so the order should be specified when testing

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)

Use instanceof to judge whether the controller instance is created by MainPageController

The dispatcher servlet is not annotated and is not managed by the bean container, so the instance object obtained by using the bean container should be null

package org.simpleframework.core;

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BeanContainerTest {
    private static BeanContainer beanContainer;

    @BeforeAll  //@BeforeAll: initialize only once
    static void init(){
        beanContainer=BeanContainer.getInstance();
    }

    @DisplayName("Load the target class and its instances into the container")
    @Order(1)
    @Test
    public void loadBeansTest(){
        //First determine whether to initialize
        Assertions.assertEquals(false,beanContainer.isLoaded());
        beanContainer.loadBeans("com.imooc");
        Assertions.assertEquals(6,beanContainer.size());
        Assertions.assertEquals(true,beanContainer.isLoaded());

    }

    @DisplayName("Get its instance according to class")
    @Order(2)
    @Test
    public void getBeanTest(){
        MainPageController bean = (MainPageController) beanContainer.getBean(MainPageController.class);
        //Use instanceof to judge whether the controller instance is created by MainPageController
        Assertions.assertEquals(true,(bean instanceof MainPageController));
        //The dispatcher servlet is not annotated and is not managed by the bean container, so the instance object obtained by using the bean container should be null
        DispatcherServlet bean1 = (DispatcherServlet) beanContainer.getBean(DispatcherServlet.class);
        Assertions.assertEquals(null,bean1);
    }

    @DisplayName("Obtain the corresponding instance according to the annotation")
    @Order(3)
    @Test
    public void getClassesByAnnotationTest(){
        Assertions.assertEquals(true,beanContainer.isLoaded());
        Assertions.assertEquals(3,beanContainer.getClassesByAnnotation(Controller.class).size());
    }

    @DisplayName("Get the implementation class according to the interface")
    @Order(4)
    @Test
    public void getClassesBySuperTest(){
        Assertions.assertEquals(true,beanContainer.isLoaded());
        Assertions.assertEquals(true,beanContainer.getClassesBySuper(HeadLineService.class).contains(HeadLineServiceImpl.class));
    }



}

Container managed Bean instance

All were single cases

The Spring framework has multiple scopes

◆singleton

◆ prototype: each time you pass the getBean method of the container, you will get a new instance.

◆ request: for each http request, a new bean instance will be generated for the bean defined by request (that is, different bean instances will be generated for each http request). This scope is valid only when spring is used in web applications.

◆ session: for each http request, a new bean instance will be generated for the bean defined by session (that is, when the session is valid, the returned bean is valid, and when it is invalid and accessed again, the returned bean is new). This scope is valid only when spring is used in web applications.

◆ global session: for each global http session, a new instance will be generated for the bean defined by the session.

Implement container dependency injection

At present, the Bean instances managed in the container may still be incomplete

Reading Guide:

Although the bean instance is managed by the container, the bean object obtained from the container is not injected into the corresponding member variable object. At this time, if we call the method encapsulated by the object, we will report a null pointer exception (equivalent to that the obtained object is not assigned to the corresponding object variable), That is, next, we need to solve the problem of giving the obtained object to the corresponding owner to enrich the owner, either null or dependency injection.

Realization idea

◆ define relevant annotation labels
◆ create the member variable instance marked by annotation and inject it into the member variable

◆ use of dependency injection (we only support member variable level injection, unlike spring framework, which also supports constructor and factory injection)

1. Define relevant annotation labels

@Target(ElementType.TYPE) //Only supported on member variables
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}

Provide dependency injection services,

You need to get the instance in the container first,

In order to obtain the bean container instance when the DependencyInjector instance is created, the logic for obtaining the container instance can be written in the construction method

/*
* Execute IoC
* */
public void doIoc(){
    //Air judgment
    if(ValidationUtil.isEmpty(beanContainer.getClasses())){
        log.warn("bean is nothing class");
        return;
    }
    //1. Traverse all Class objects in the Bean container
    for (Class<?> clazz : beanContainer.getClasses()) {
        //2. Traverse all member variables of the Class object
        Field[] fields = clazz.getDeclaredFields();
        //Air judgment
        if(ValidationUtil.isEmpty(fields)){
            continue;
        }
        for (Field field : fields) {
            //3. Find the member variables marked by Autowired
            if(field.isAnnotationPresent(Autowired.class)){
                //4. Gets the type of these member variables
                Class<?> fieldClass = field.getType();
                //5. Get the corresponding instances of the types of these member variables in the container
                Object fieldValue=getFileInstance(fieldClass);
                if(fieldValue==null){
                    throw new RuntimeException("unable to inject relevant type,target fieldClass is:"+fieldClass.getName());
                }else {
                    //6. Inject the corresponding member variable instance into the instance of the class where the member variable is located through reflection
                    Object targetBean = beanContainer.getBean(clazz);
                    ClassUtil.setField(field,targetBean,fieldValue,true);
                }
            }
        }
    }
}

Empty first

Assigning values to instance variables is a general method

/**
 * Set the property value of the class
 * @param field Member variable
 * @param targetBean  Class instance
 * @param value  Value of member variable
 * @param accessible Allow setting private properties
 */
public static void setField(Field field, Object targetBean, Object value, boolean accessible) {
    field.setAccessible(accessible);
    try {
        field.set(targetBean,value);
    } catch (IllegalAccessException e) {
        log.error("setField error",e);
        throw new RuntimeException(e);
    }

}

If the obtained bean instance is not empty, it indicates that it is directly found in the container (indicating that Class is a Class) and returned directly. If it is empty, you need to judge whether it is an interface (return the corresponding implementation Class). After finding the corresponding implementation Class, find the implementation Class in the container, and return if there is an implementation Class. Return null if no

/*
* Get its instance or implementation Class in beanContainer according to Class
* */
private Object getFileInstance(Class<?> fieldClass) {
    //Get bean from container
    Object fieldValue = beanContainer.getBean(fieldClass);
    //Air judgment
    if(fieldValue!=null){
        return fieldValue;//Find it directly, indicating that it is a class
    }else {
        //There are two situations when the bean is not directly found in the container: 1. The interface is passed in and the implementation class is stored in the bean. 2. There is no interface
       Class<?> implementedClass= getImplmentClass(fieldClass);//Find the implementation class according to the interface
        if(implementedClass!=null){
            return beanContainer.getBean(implementedClass);
        }else {
            return null;
        }

    }

}

Because Autowired is injected according to the type of member variable, in the case of multiple implementation classes, Qualifier is added to specify the specific implementation class name.

We use the simplest implementation, adding the value attribute to Autowired

@Target(ElementType.TYPE) //Only supported on member variables
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
    String value() default "";
}

Refactoring the injection ioc method (first obtain the Auyowired instance, and then obtain its value), obtain its instance or implementation Class in the beanContainer according to the Class (pass a parameter)

First, judge whether the value of autowiredValue is the default value. If it is empty, it means that the user does not tell the framework to return the specific implementation class. Two situations need to be handled: there is only one implementation class. 2. Multiple implementation classes throw exceptions

If the value is set to a value, you need to traverse the classSet. The name of the class is the name of the package, so use getSimpleName

/*
* Gets the implementation class of the interface
* */
private Class<?> getImplmentedClass(Class<?> fieldClass, String autowiredValue) {
    Set<Class<?>> classSet = beanContainer.getClassesBySuper(fieldClass);
    //Air judgment
    if(!ValidationUtil.isEmpty(classSet)){
        //First, judge whether the value of autowiredValue is the default value (empty)
        if(ValidationUtil.isEmpty(autowiredValue)){
            //Then judge how many implementation classes there are
            if(classSet.size()==1){
                return classSet.iterator().next();//An implementation class returns directly without ambiguity
            }else {
                //If multiple implementation classes do not specify specific implementation class names, an error is reported
                throw new RuntimeException("multiple implemented classes for" +fieldClass.getName()+ " please set @Autowired's value to pick one");

            }
        }else {//Implementation class specified
            //Traverse the classSet, find and return
            for (Class<?> clazz : classSet) {
                //Get the short name of the class. getSimpleName() has only one class name
                if(autowiredValue.equals(clazz.getSimpleName())){
                    return clazz;
                }

            }

        }


    }
    return null;

}

Overall dependency injection Code:

@Slf4j
public class DependencyInjector {
    //You need to get the instance in the container first,
    private BeanContainer beanContainer;

    //To get the bean container instance when the DependencyInjector instance is created,
    // You can write the logic to obtain the container instance in the construction method
    public DependencyInjector(){
        beanContainer=BeanContainer.getInstance();
    }
    /*
    * Execute IoC
    * */
    public void doIoc(){
        //Air judgment
        if(ValidationUtil.isEmpty(beanContainer.getClasses())){
            log.warn("bean is nothing class");
            return;
        }
        //1. Traverse all Class objects in the Bean container
        for (Class<?> clazz : beanContainer.getClasses()) {
            //2. Traverse all member variables of the Class object
            Field[] fields = clazz.getDeclaredFields();
            //Air judgment
            if(ValidationUtil.isEmpty(fields)){
                continue;
            }
            for (Field field : fields) {
                //3. Find the member variables marked by Autowired
                if(field.isAnnotationPresent(Autowired.class)){
                    //First get the Autowired annotation instance, and then get the attributes in the annotation
                    Autowired autowired = field.getAnnotation(Autowired.class);
                    String autowiredValue = autowired.value();

                    //4. Gets the type of these member variables
                    Class<?> fieldClass = field.getType();
                    //5. Get the corresponding instances of the types of these member variables in the container
                    Object fieldValue=getFileInstance(fieldClass,autowiredValue);
                    if(fieldValue==null){
                        throw new RuntimeException("unable to inject relevant type,target fieldClass is:"+fieldClass.getName());
                    }else {
                        //6. Inject the corresponding member variable instance into the instance of the class where the member variable is located through reflection
                        Object targetBean = beanContainer.getBean(clazz);
                        ClassUtil.setField(field,targetBean,fieldValue,true);
                    }
                }
            }
        }
    }

    /*
    * Get its instance or implementation Class in beanContainer according to Class
    * */
    private Object getFileInstance(Class<?> fieldClass, String autowiredValue) {
        //Get bean from container
        Object fieldValue = beanContainer.getBean(fieldClass);
        //Air judgment
        if(fieldValue!=null){
            return fieldValue;//Find it directly, indicating that it is a class
        }else {
            //There are two situations when the bean is not directly found in the container: 1. The interface is passed in and the implementation class is stored in the bean. 2. There is no interface
           Class<?> implementedClass= getImplmentedClass(fieldClass,autowiredValue);//Find the implementation class according to the interface
            if(implementedClass!=null){
                return beanContainer.getBean(implementedClass);
            }else {
                return null;
            }

        }

    }
    /*
    * Gets the implementation class of the interface
    * */
    private Class<?> getImplmentedClass(Class<?> fieldClass, String autowiredValue) {
        Set<Class<?>> classSet = beanContainer.getClassesBySuper(fieldClass);
        //Air judgment
        if(!ValidationUtil.isEmpty(classSet)){
            //First, judge whether the value of autowiredValue is the default value (empty)
            if(ValidationUtil.isEmpty(autowiredValue)){
                //Then judge how many implementation classes there are
                if(classSet.size()==1){
                    return classSet.iterator().next();//An implementation class returns directly without ambiguity
                }else {
                    //If multiple implementation classes do not specify specific implementation class names, an error is reported
                    throw new RuntimeException("multiple implemented classes for" +fieldClass.getName()+ " please set @Autowired's value to pick one");

                }
            }else {//Implementation class specified
                //Traverse the classSet, find and return
                for (Class<?> clazz : classSet) {
                    //Get the short name of the class. getSimpleName() has only one class name
                    if(autowiredValue.equals(clazz.getSimpleName())){
                        return clazz;
                    }

                }

            }

        }
        return null;

    }

}

Mark the member variable in the class to inject it into the instance

test

Create the same directory

1. Get container instance

2. Specify the scope to load the annotated class to the container management

3. First judge whether the container is loaded. After loading, you can get the bean instance

4. Determine whether it is an instance created by MainPageController

5. Set the get method to the controller to get the private member variable

6. Because doIoc was not called, the instance of the member variable should be null

7. Call doIoc for dependency injection

package org.simpleframework.inject;

public class DependencyInjectorTest {

    @Test
    @DisplayName("Dependency injection doIoc")
    public void doIocTest(){
        //1. Get container instance
        BeanContainer beanContainer = BeanContainer.getInstance();
        //2. Specify the scope to load the annotated class to the container management
        beanContainer.loadBeans("com.imooc");
        //3. First judge whether the container is loaded. After loading, you can get the bean instance
        Assertions.assertEquals(true,beanContainer.isLoaded());
        //4. Determine whether it is an instance created by MainPageController
        MainPageController bean = (MainPageController) beanContainer.getBean(MainPageController.class);
        Assertions.assertEquals(true,(bean instanceof MainPageController));

        //6. Because doIoc was not called, the instance of the member variable should be null
        Assertions.assertEquals(null,bean.getHeadLineShopCategoryCombineService());
        //7. Call doIoc for dependency injection
        new DependencyInjector().doIoc();
        Assertions.assertNotEquals(null,bean.getHeadLineShopCategoryCombineService());
    }
}

Verify that there are multiple implementation classes for the same interface, and then create a headline implementation class. At this time, there are two implementation classes for the member variables in MainPageController. If Autowired does not indicate who the value is, an exception will be thrown

public class DependencyInjectorTest {

    @Test
    @DisplayName("Dependency injection doIoc")
    public void doIocTest(){
        //1. Get container instance
        BeanContainer beanContainer = BeanContainer.getInstance();
        //2. Specify the scope to load the annotated class to the container management
        beanContainer.loadBeans("com.imooc");
        //3. First judge whether the container is loaded. After loading, you can get the bean instance
        Assertions.assertEquals(true,beanContainer.isLoaded());
        //4. Determine whether it is an instance created by MainPageController
        MainPageController bean = (MainPageController) beanContainer.getBean(MainPageController.class);
        Assertions.assertEquals(true,(bean instanceof MainPageController));

        //6. Because doIoc was not called, the instance of the member variable should be null
        Assertions.assertEquals(null,bean.getHeadLineShopCategoryCombineService());
        //7. Call doIoc for dependency injection
        new DependencyInjector().doIoc();
        Assertions.assertNotEquals(null,bean.getHeadLineShopCategoryCombineService());
        Assertions.assertEquals(true,bean.getHeadLineShopCategoryCombineService() instanceof HeadLineShopCategoryCombineServiceImpl);
        Assertions.assertEquals(false,bean.getHeadLineShopCategoryCombineService() instanceof HeadLineShopCategoryServiceImpl2);
    }
}

Summary of this chapter

Topics: Java Container Singleton pattern