The core of Spring - IoC and DI

Posted by harman on Sat, 18 Sep 2021 11:06:06 +0200

IoC, the core of Spring

IoC control reverses the permission of Inverse of Control to create objects. Objects needed in Java programs are no longer created by programmers, but are created by IoC containers.

IoC core idea

Implement IoC in the way of Java Web

1,pom.xml

<dependencies>
    <!-- introduce Servlet rely on -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
    </dependency>
</dependencies>

<!-- set up Maven of JDK The default version is 5. It needs to be manually changed to 8 -->
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
            </configuration>
        </plugin>
    </plugins>
</build>

<packaging>war</packaging>

2. Create Servlet

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        resp.getWriter().write("Spring");
    }
}

3. Deploy to Tomcat

4. Servlet [i.e. Controller], Service and Dao [MVC three-tier architecture]

When requirements change, Java code may need to be modified frequently, which is inefficient. How to solve it?

Static factory

package com.southwind.factory;

import com.southwind.dao.HelloDao;
import com.southwind.dao.impl.HelloDaoImpl;

public class BeanFactory {
    public static HelloDao getDao(){
        return new HelloDaoImpl();
    }
}

service layer

private HelloDao helloDao = BeanFactory.getDao();

The above methods can not solve our problems. When the requirements change, we still need to modify the code. How to do it

Can you switch classes without changing the Java code?

How external profiles

Write the specific implementation class to the configuration file, and the Java program only needs to read the configuration file.

Common configuration files: XML, YAML, Properties, JSON

1. Define external profile
Here, the configuration file is placed in src/main/resources/factory.properties

  helloDao=com.southwind.dao.impl.HelloDaoImpl

2. The Java program reads this configuration file

package com.southwind.factory;

import com.southwind.dao.HelloDao;
import com.southwind.dao.impl.HelloDaoImpl;
import com.southwind.dao.impl.HelloDaoImpl2;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Properties;

public class BeanFactory {

    private static Properties properties;

    static {
        properties = new Properties();
        try {
            properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("factory.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static Object getDao(){
        String value = properties.getProperty("helloDao");
        //Create objects with reflection mechanism
        try {
            Class clazz = Class.forName(value);
            Object object = clazz.getConstructor(null).newInstance(null);
            return object;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }
}

3. Modify Service

   private HelloDao helloDao = (HelloDao) BeanFactory.getDao();

Beans in Spring IoC are singletons

 package com.southwind.factory;
    
    import com.southwind.dao.HelloDao;
    import com.southwind.dao.impl.HelloDaoImpl;
    import com.southwind.dao.impl.HelloDaoImpl2;
    
    import java.io.IOException;
    import java.lang.reflect.InvocationTargetException;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Properties;
    
    public class BeanFactory {
    
        private static Properties properties;
        private static Map<String,Object> cache = new HashMap<>();
    
        static {
            properties = new Properties();
            try {
                properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("factory.properties"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static Object getDao(String beanName){
            //First determine whether there are bean s in the cache
            if(!cache.containsKey(beanName)){
                synchronized (BeanFactory.class){
                    if(!cache.containsKey(beanName)){
                        //Cache bean s
                        //Create objects with reflection mechanism
                        try {
                            String value = properties.getProperty(beanName);
                            Class clazz = Class.forName(value);
                            Object object = clazz.getConstructor(null).newInstance(null);
                            cache.put(beanName, object);
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        } catch (InstantiationException e) {
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        } catch (NoSuchMethodException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            return cache.get(beanName);
        }
    }
 1,private HelloDao helloDao = new HelloDaoImpl();
    
 2,private HelloDao helloDao = (HelloDao)BeanFactory.getDao("helloDao");

1. It is strongly dependent / tightly coupled and cannot be modified after compilation. It has no scalability.
2. Weak dependency / loose coupling, which can still be modified after compilation, so that the program has better scalability.

Summary:
Programmers give up the permission to create objects and give the permission to create objects to BeanFactory. The idea of giving control to others is to control inversion IoC.

Use of Spring IoC

XML and annotations, but XML has been gradually eliminated. At present, the mainstream development is based on annotations, and Spring Boot is based on annotations.

package com.southwind.spring.entity;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Data
@Component("myOrder")
public class Order {
    @Value("xxx123")
    private String orderId;
    @Value("1000.0")
    private Float price;
}
package com.southwind.spring.entity;

import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Data
@Component
public class Account {
    @Value("1")
    private Integer id;
    @Value("Zhang San")
    private String name;
    @Value("22")
    private Integer age;
    @Autowired
    @Qualifier("order")
    private Order myOrder;
}
package com.southwind.spring.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        //Load IoC container
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.southwind.spring.entity");
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        System.out.println(applicationContext.getBeanDefinitionCount());
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
            System.out.println(applicationContext.getBean(beanDefinitionName));
        }
    }
}

IoC annotation based execution principle

Idea of handwritten code:

1. Customize a MyAnnotationConfigApplicationContext and pass in the package to be scanned in the constructor.

2. Get all classes under this package.

3. Traverse these classes, find the Class with @ Component annotation, obtain its Class and corresponding beanName, package it into a BeanDefinition and store it in the Set. This opportunity is the raw material automatically loaded by IoC.

4. Traverse the Set, create objects through the reflection mechanism, and detect whether the attribute has added @ Value annotation. If so, assign values to the attribute, and then store these dynamically created objects in the cache in the form of k-v.

5. Methods such as getBean are provided, and the corresponding bean can be retrieved through beanName.

code implementation

package com.southwind.myspring;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class BeanDefinition {
    private String beanName;
    private Class beanClass;
}

package com.southwind.myspring;

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 Autowired {
}

package com.southwind.myspring;

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
    String value() default "";
}

package com.southwind.myspring;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

public class MyAnnotationConfigApplicationContext {

    private Map<String,Object> ioc = new HashMap<>();
    private List<String> beanNames = new ArrayList<>();

    public MyAnnotationConfigApplicationContext(String pack) {
        //Traverse the package to find the target class (raw material)
        Set<BeanDefinition> beanDefinitions = findBeanDefinitions(pack);
        //Create bean s from raw materials
        createObject(beanDefinitions);
        //Automatic loading
        autowireObject(beanDefinitions);
    }

    public void autowireObject(Set<BeanDefinition> beanDefinitions){
        Iterator<BeanDefinition> iterator = beanDefinitions.iterator();
        while (iterator.hasNext()) {
            BeanDefinition beanDefinition = iterator.next();
            Class clazz = beanDefinition.getBeanClass();
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                Autowired annotation = declaredField.getAnnotation(Autowired.class);
                if(annotation!=null){
                    Qualifier qualifier = declaredField.getAnnotation(Qualifier.class);
                    if(qualifier!=null){
                        //byName
                        try {
                            String beanName = qualifier.value();
                            Object bean = getBean(beanName);
                            String fieldName = declaredField.getName();
                            String methodName = "set"+fieldName.substring(0, 1).toUpperCase()+fieldName.substring(1);
                            Method method = clazz.getMethod(methodName, declaredField.getType());
                            Object object = getBean(beanDefinition.getBeanName());
                            method.invoke(object, bean);
                        } catch (NoSuchMethodException e) {
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }else{
                        //byType
                    }
                }
            }
        }
    }

    public Object getBean(String beanName){
        return ioc.get(beanName);
    }

    public String[] getBeanDefinitionNames(){
        return beanNames.toArray(new String[0]);
    }

    public Integer getBeanDefinitionCount(){
        return beanNames.size();
    }

    public void createObject(Set<BeanDefinition> beanDefinitions){
        Iterator<BeanDefinition> iterator = beanDefinitions.iterator();
        while (iterator.hasNext()) {
            BeanDefinition beanDefinition = iterator.next();
            Class clazz = beanDefinition.getBeanClass();
            String beanName = beanDefinition.getBeanName();
            try {
                //Created object
                Object object = clazz.getConstructor().newInstance();
                //Complete the assignment of the attribute
                Field[] declaredFields = clazz.getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    Value valueAnnotation = declaredField.getAnnotation(Value.class);
                    if(valueAnnotation!=null){
                        String value = valueAnnotation.value();
                        String fieldName = declaredField.getName();
                        String methodName = "set"+fieldName.substring(0, 1).toUpperCase()+fieldName.substring(1);
                        Method method = clazz.getMethod(methodName,declaredField.getType());
                        //Complete data type conversion
                        Object val = null;
                        switch (declaredField.getType().getName()){
                            case "java.lang.Integer":
                                val = Integer.parseInt(value);
                                break;
                            case "java.lang.String":
                                val = value;
                                break;
                            case "java.lang.Float":
                                val = Float.parseFloat(value);
                                break;
                        }
                        method.invoke(object, val);
                    }
                }
                //Store in cache
                ioc.put(beanName, object);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
    }

    public Set<BeanDefinition> findBeanDefinitions(String pack){
        //1. Get all classes under the package
        Set<Class<?>> classes = MyTools.getClasses(pack);
        Iterator<Class<?>> iterator = classes.iterator();
        Set<BeanDefinition> beanDefinitions = new HashSet<>();
        while (iterator.hasNext()) {
            //2. Traverse these classes to find the annotated classes
            Class<?> clazz = iterator.next();
            Component componentAnnotation = clazz.getAnnotation(Component.class);
            if(componentAnnotation!=null){
                //Gets the value of the Component annotation
                String beanName = componentAnnotation.value();
                if("".equals(beanName)){
                    //Gets the lowercase initial of the class name
                    String className = clazz.getName().replaceAll(clazz.getPackage().getName() + ".", "");
                    beanName = className.substring(0, 1).toLowerCase()+className.substring(1);
                }
                //3. Encapsulate these classes into BeanDefinition and load them into the collection
                beanDefinitions.add(new BeanDefinition(beanName, clazz));
                beanNames.add(beanName);
            }
        }
        return beanDefinitions;
    }
}

package com.southwind.myspring;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class MyTools {

    public static Set<Class<?>> getClasses(String pack) {

        // Collection of the first class
        Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
        // Loop iteration
        boolean recursive = true;
        // Get the name of the package and replace it
        String packageName = pack;
        String packageDirName = packageName.replace('.', '/');
        // Define a collection of enumerations and loop to handle things in this directory
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            // Loop iteration
            while (dirs.hasMoreElements()) {
                // Get next element
                URL url = dirs.nextElement();
                // Get the name of the agreement
                String protocol = url.getProtocol();
                // If it is saved on the server as a file
                if ("file".equals(protocol)) {
                    // Gets the physical path of the package
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    // Scan the files under the whole package as files and add them to the collection
                    findClassesInPackageByFile(packageName, filePath, recursive, classes);
                } else if ("jar".equals(protocol)) {
                    // If it is a jar package file
                    // Define a JarFile
                    System.out.println("jar Type of scan");
                    JarFile jar;
                    try {
                        // Get jar
                        jar = ((JarURLConnection) url.openConnection()).getJarFile();
                        // Get an enumeration class from this jar package
                        Enumeration<JarEntry> entries = jar.entries();
                        findClassesInPackageByJar(packageName, entries, packageDirName, recursive, classes);
                    } catch (IOException e) {
                        // log.error("error in getting file from jar package while scanning user-defined view");
                        e.printStackTrace();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return classes;
    }

    private static void findClassesInPackageByJar(String packageName, Enumeration<JarEntry> entries, String packageDirName, final boolean recursive, Set<Class<?>> classes) {
        // Do the same loop iteration
        while (entries.hasMoreElements()) {
            // An entity in the jar can be a directory and other files in some jar packages, such as META-INF files
            JarEntry entry = entries.nextElement();
            String name = entry.getName();
            // If it starts with /
            if (name.charAt(0) == '/') {
                // Get the following string
                name = name.substring(1);
            }
            // If the first half is the same as the defined package name
            if (name.startsWith(packageDirName)) {
                int idx = name.lastIndexOf('/');
                // If it ends with "/", it is a package
                if (idx != -1) {
                    // Get the package name and replace "/" with "."
                    packageName = name.substring(0, idx).replace('/', '.');
                }
                // If it can be iterated and is a package
                if ((idx != -1) || recursive) {
                    // If it is a. class file and not a directory
                    if (name.endsWith(".class") && !entry.isDirectory()) {
                        // Remove the following ". Class" to get the real class name
                        String className = name.substring(packageName.length() + 1, name.length() - 6);
                        try {
                            // Add to classes
                            classes.add(Class.forName(packageName + '.' + className));
                        } catch (ClassNotFoundException e) {
                            // . error("error adding user-defined view class. Class file of this class cannot be found");
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    private static void findClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, Set<Class<?>> classes) {
        // Get the directory of this package and create a File
        File dir = new File(packagePath);
        // If it does not exist or is not a directory, it will be returned directly
        if (!dir.exists() || !dir.isDirectory()) {
            // log.warn("there are no files under user-defined package name" + packageName + ");
            return;
        }
        // If it exists, get all files under the package, including directories
        File[] dirfiles = dir.listFiles(new FileFilter() {
            // If the custom filtering rules can be looped (including subdirectories) or files ending in. Class (compiled java class files)
            @Override
            public boolean accept(File file) {
                return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
            }
        });
        // Cycle all files
        for (File file : dirfiles) {
            // If it is a directory, continue scanning
            if (file.isDirectory()) {
                findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classes);
            } else {
                // If it is a java class file, remove the following. Class and leave only the class name
                String className = file.getName().substring(0, file.getName().length() - 6);
                try {
                    // Add to collection
                    // classes.add(Class.forName(packageName + '.' +
                    // className));
                    // After replying to the classmate's reminder, there are some disadvantages in using forName here, which will trigger the static method. The classLoader's load function is not used
                    classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
                } catch (ClassNotFoundException e) {
                    // log.error("error adding user-defined view class. Class file of this class cannot be found");
                    e.printStackTrace();
                }
            }
        }
    }

}

package com.southwind.myspring;

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 Qualifier {
    String value();
}

package com.southwind.myspring;

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 Value {
    String value();
}

package com.southwind.myspring.entity;

import com.southwind.myspring.Component;
import com.southwind.myspring.Value;
import lombok.Data;

@Data
@Component("myOrder")
public class Order {
    @Value("xxx123")
    private String orderId;
    @Value("1000.5")
    private Float price;
}

package com.southwind.myspring.entity;

import com.southwind.myspring.Autowired;
import com.southwind.myspring.Component;
import com.southwind.myspring.Qualifier;
import com.southwind.myspring.Value;
import lombok.Data;

@Data
@Component
public class Account {
    @Value("1")
    private Integer id;
    @Value("Zhang San")
    private String name;
    @Value("22")
    private Integer age;
    @Autowired
    @Qualifier("myOrder")
    private Order order;
}

package com.southwind.myspring;

public class Test {
    public static void main(String[] args) {
        MyAnnotationConfigApplicationContext applicationContext = new MyAnnotationConfigApplicationContext("com.southwind.myspring.entity");
        System.out.println(applicationContext.getBeanDefinitionCount());
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
            System.out.println(applicationContext.getBean(beanDefinitionName));
        }
    }
}

Topics: Java Spring SSM mvc