Try to implement spring

Posted by Frank H. Shaw on Sun, 02 Jan 2022 21:16:51 +0100

preface

If you want to achieve the same effect, please keep consistent with the code in this article. The logic is not complex. After the effect is completed, you can modify it according to your own ideas. The code in this article is mainly implemented, and there must be some cases that have not been handled.

cause

A JAVA framework called Spring is often used in daily development. As long as an annotation is added, it can be registered as a bean and handed over to the framework for management. When using classes registered as beans, you don't need new to create objects, but @ Autowired can be added to complete automatic injection.

1. What does the framework manage?

 A: don't answer first

2. How can automatic injection be used without new?

 A: according to java The basic knowledge of this should be the value assigned by reflection. The idea is to get the annotation on the field through reflection, if any@Autowired The value is assigned.

Start implementation

Since most of the JDK used is 1.8, the code is in jdk1 8, and the development tools use IDEA. By the way, this is not to make wheels repeatedly, but to satisfy curiosity. By the way, practice the knowledge of annotation and reflection.

1. New Maven project, directory structure

2. Let's create a new class SpringApplicationContext under the spring package, and run the code in this class when it starts later.

3. If you write a startup class following the Springboot project, you must add a static run method to the class we just wrote.

Startup class

public class SpringApplication {
    public static void main(String[] args) {
        //Take Class as a parameter to facilitate reflection
        SpringApplicationContext.run(SpringApplication.class);
    }
}

SpringApplicationContext class

public static void run(Class configClass){
       
}

4. Preparations are completed

  • Since you want to use reflection to assign values to class fields, you have to know which classes need to be assigned. Naturally, you need to traverse the class files in the compiled directory and get their annotations.
//Add an annotation to specify the package path to be managed by spring
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
    String value();
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

//Describes whether the bean is a singleton
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
    String value();
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//Identify that this field needs to be automatically injected
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.FIELD})
public @interface Autowired {
    String value() default "";
}
//The object that describes the Bean
public class BeanDefinition {
    private Class clazz;
    private String scope;

    public Class getClazz() {
        return clazz;
    }

    public void setClazz(Class clazz) {
        this.clazz = clazz;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }
}
import com.spring.ComponentScan;
import com.spring.SpringApplicationContext;
import com.test.service.UserService;

@ComponentScan("com.test.service")
public class SpringApplication {
    public static void main(String[] args) {
        SpringApplicationContext.run(SpringApplication.class);
        UserService userService = (UserService) SpringApplicationContext.getBean("userService");
        userService.test();
    }
}
//Add a method scan in run. Exceptions can be thrown or caught according to the idea prompt when writing scan without writing first
public class SpringApplicationContext {
    //The following is used to cache the information we traverse during scanning
    private static ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    //Store scanned singleton objects
    private static ConcurrentHashMap<String,Object> singleObjects = new ConcurrentHashMap<>();

    public static void run(Class configClass){
        //Scan startup class annotation
        try {
            scan(configClass);
        } catch (IOException | URISyntaxException e) {
            e.printStackTrace();
        }
}
/**
 *  To scan class files, we need to prepare conditions, such as file path
 */
private static void scan(Class configClass) throws IOException, URISyntaxException {
    //Get annotation object
    ComponentScan componentScan = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);
    //Gets the value in the annotation
    String path = componentScan.value();
    //Convert annotation path to file path
    path = path.replace(".", File.separator);
    //Get the class loader to load the scanned classes. (ps: there are a lot to learn about class loaders)
    ClassLoader classLoader = SpringApplicationContext.class.getClassLoader();
    //Gets the compiled resource path
    URL resource = classLoader.getResource(path);
    //Convert to URI, that is, the input parameter after
    URI resourceUri = resource.toURI();
    //Determine whether it is a folder, and then traverse
    if(Files.isDirectory(Paths.get(resourceUri))){
        //Directly call the file traversal method provided by JDK
        Files.walkFileTree(Paths.get(resourceUri),new SimpleFileVisitor<Path>(){
                //Override the method being traversed        
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    //If you are traversing a file, start the management operation
                    if(!Files.isDirectory(file)){
                        //Get full path of file
                        String fileName = file.toAbsolutePath().toString();
                        //Convert it to a format that can be used as an input parameter
                        String className = fileName.replace(File.separator,".").substring(fileName.indexOf("com"),fileName.indexOf(".class"));
                        //If the file is class bytecode file, which is what we compiled
                        if(fileName.endsWith(".class")){
                            //Start getting Class object
                            Class<?> clazz = null;
                            try {
                                //Get Class object
                                clazz = classLoader.loadClass(className);
                            } catch (ClassNotFoundException e) {
                                e.printStackTrace();
                            }
                            //Start the reflection operation to determine whether there is an annotation Component on the class
                            if(clazz.isAnnotationPresent(Component.class)){
                                //An annotation indicates that this is a Bean and needs to be managed
                                Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
                                //The bean name here is written manually for convenience.
                                String beanName = componentAnnotation.value();
                                //You need an object to describe the Bean (for example, whether it is a singleton or not)
                                BeanDefinition beanDefinition = new BeanDefinition();
                                //Store the clazz information in the object describing the Bean
                                beanDefinition.setClazz(clazz);
                                //If there is Scope annotation on the object, further judgment is required
                                if(clazz.isAnnotationPresent(Scope.class)){
                                    //Get annotation object
                                    Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
                                    beanDefinition.setScope(scopeAnnotation.value());
                                }else {
                                    //If no annotation is added, it is a single example by default
                                    beanDefinition.setScope("singleton");
                                }
                                //Cache the information we get through a parallel Map
                                beanDefinitionMap.put(beanName,beanDefinition);
                            }
                        }
                    }
                    return FileVisitResult.CONTINUE;
                }
            });
    }
}
//Go back to the run method and continue writing
public static void run(Class configClass){
        //Scan startup class annotation
        try {
            scan(configClass);
        } catch (IOException | URISyntaxException e) {
            e.printStackTrace();
        }
        //After scanning the bytecode file, start field injection and other management operations, and traverse the cached information
        for(Map.Entry<String,BeanDefinition> entry : beanDefinitionMap.entrySet()){
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();
            if("singleton".equals(beanDefinition.getScope())){
                //Prevent code bloating and separate the operation of bean generation into one method
                Object bean = createBean(beanName,beanDefinition);
                //Store the generated object into the singleton pool
                singleObjects.put(beanName,bean);
            }
        }
    }
//Generate bean s and assign values to the fields that need to be automatically injected first
private static Object createBean(String beanName,BeanDefinition beanDefinition){        
        //Get the Class object from the description bean object
        Class<?> clazz = beanDefinition.getClazz();
        try{
            //Instantiate by reflection
            Object instance = clazz.getDeclaredConstructor().newInstance();
            //Traverse fields on Class
            for(Field declaredFiled : clazz.getDeclaredFields()){
                //If a field traversed has Autowired annotation, it will be assigned
                if(declaredFiled.isAnnotationPresent(Autowired.class)){
                    //Get the field name, get the bean assignment from the cache, and write a separate method
                    Object bean = getBean(declaredFiled.getName());
                    declaredFiled.setAccessible(true);
                    declaredFiled.set(instance,bean);
                }
            }
            return instance;
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }
    //Method to get Bean
    public static Object getBean(String beanName){
        //Determine whether there is this bean
        if(beanDefinitionMap.containsKey(beanName)){
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            //If it is a singleton, remove it from the singleton pool
            if("singleton".equals(beanDefinition.getScope())){
                Object singleObject = singleObjects.get(beanName);
                return singleObject;
            }else {
                Object bean = createBean(beanName,beanDefinition);
                return bean;
            }
        }else {
            throw new NullPointerException();
        }
    }

5. Test whether it is useful according to daily coding

  • Simulate the business code under the service package
import com.spring.Component;

@Component("orderService")
public class OrderService {
}
public interface UserService {
    void test();
}
import com.spring.Autowired;
import com.spring.Component;

@Component("userService")
public class UserServiceImpl implements UserService{

    @Autowired
    private OrderService orderService;
    
    //If automatic injection fails, null will be printed
    @Override
    public void test() {
        System.out.println("test method:"+orderService);
    }
}
import com.spring.ComponentScan;
import com.spring.SpringApplicationContext;
import com.test.service.UserService;

//Start the class to get the cached bean and call its test method to see if the object is printed
@ComponentScan("com.test.service")
public class SpringApplication {
    public static void main(String[] args) {
        SpringApplicationContext.run(SpringApplication.class);
        UserService userService = (UserService) SpringApplicationContext.getBean("userService");
        userService.test();
    }
}

Summary

There is still some code to be added. Of course, automatic injection has been written. Does the Spring framework do this? I click on the Spring source code and find that I can't read it in 10 minutes and don't bother to study it carefully because it is nested layer by layer. It's important to score recently. Work also needs a good ability to collect and filter information, such as using a good search engine.
Think about it:

  1. How to solve circular dependency
  2. The implementation of controller is spring MVC (keyword: dispatcherservlet)

Topics: Java Spring