Manual development - simple Spring annotation-based configuration program - source code analysis


In the previous article "Manual Development - Simple Spring XML Configuration-Based Program - Source Code Analysis" , we read the bean object information from the XML configuration file, then initialized it in the container we designed, injected attributes, and finally passed getBean () method returns. In this article, we will implement a simple Spring container based on the annotation perspective. We will also make some changes here. Previously, we initialized the value-passing container through the xml file name. Here, we initialized the container through the value-passing interface type. Therefore, this article has the following two features:

  • Implement Spring container simulation based on annotations
  • Initialize ioc container through interface type

@Design Note@

There are many annotations in Spring. Here we will design an annotation for use. So how to design annotations? Spring's annotation design is based on meta-annotations . Meta-annotations are the foundation of Java. Meta-annotations are as follows:

  • @Target

    Used to specify the usage scope of annotations

    • ElementType.TYPE: class, interface, annotation, enumeration
    • ElementType.FIELD: fields, enumeration constants
    • ElementType.METHOD: Method
    • ElementType.PARAMETER: formal parameters
    • ElementType.CONSTRUCTOR:Constructor
    • ElementType.LOCAL_VARIABLE: local variable
    • ElementType.ANNOTATION_TYPE: Annotation
    • ElementType.PACKAGE:包
    • ElementType.TYPE_PARAMETER: type parameter
    • ElementType.TYPE_USE: type use
  • @Retention

    Retention policy for specifying annotations

    • RetentionPolicy.SOURCE: Annotations are only retained in the source code and will be discarded by the compiler during compilation.
    • RetentionPolicy.CLASS: (default retention policy) annotations will be retained in the Class file, but will not be loaded into the virtual machine and cannot be obtained at runtime.
    • RetentionPolicy.RUNTIME: Annotations will be retained in the Class file and loaded into the virtual machine, which can be obtained at runtime.
  • @Documented

    • Used to include annotations in javadoc
    • By default, javadoc does not include annotations, but if the @Documented annotation is used, the relevant annotation type information will be included in the generated document.
  • @Inherited

    Used to indicate that parent class annotations will be inherited by subclasses

  • @Repeatable

    The annotations used to declare tags are repeatable type annotations and can be used multiple times in the same place.

These meta-annotations are the most basic components, and we need to use them when designing annotations. Now we will design our own ComponentScan annotation:

/**
 * @author linghu
 * @date 2023/8/30 13:56
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
    
    
    String value();

}

@Retention(RetentionPolicy.RUNTIME)Indicates that this annotation will take effect at runtime; @Target(ElementType.TYPE)indicates the type that the annotation can modify. When we enter the Typesource code, we know through the annotation that TYPEit contains Class, interface (including annotation type), or enum declaration , which means it can be a class or interface. …:

public enum ElementType {
    
    
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

At this time, in order to verify the new annotation ComponentScan we designed, we create a new LingHuSpringConfig configuration class. In fact, this configuration class will not implement anything specifically. It just puts an annotation ComponentScan on the class name, and then sets a value, as follows:

/**
 * @author linghu
 * @date 2023/8/30 14:09
 * 这个配置文件作用类似于beans.xml文件,用于对spring容器指定配置信息
 */
@ComponentScan(value = "com.linghu.spring.component")
public class LingHuSpringConfig {
    
    

}

Our purpose of setting up this configuration class is: when we initialize the container, we just pass LingHuSpringConfig.classthe interface directly, initialize the ioc container through the interface type, and the container scans the full class path com.linghu.spring.component based on the annotations we designed .

$designcontainer$

In fact, the design of this container is similar to that described in "Manual Development - Simple Spring XML Configuration-Based Program - Source Code Analysis" , both require:

  • one ConcurrentHashMapas container
  • A constructor to initialize the container.
  • Provide a getBeanmethod that returns our ioc container.

Most of the work here is done in the constructor . The completed work is as follows:

  • Find @ComponentScanthe configuration class and read the value to get the class path.
  • Through the class path in the previous step, we need to go to the path of the corresponding targetdirectory to index all files, which are actually those .class files. We filter these files. During the filtering process, we determine whether they have annotations added. If they have, add them. The classpaths of these files are saved in the ioc container.
  • When retrieving and filtering files, we need to extract the names of the .class files stored in the component file, and then save these names into the container.
  • Get the complete class path and determine whether these classes have annotations: @compoment, @controller, @Service…. Do I need to inject the container?
  • Get the value of the Component annotation. This value is used as the id name of the bean object and is stored in the ioc container.

Finally, we realized it. Through the annotations we defined, we scanned the class path of the annotated class and added it to the container ioc we created. Finally, we got the objects we needed through the ioc container we designed ourselves. How does ioc help us create objects? Created through reflection , the class path required by reflection is what we read from the annotation.

img

#Complete code#

LingSpringApplicationContext.java:

package com.linghu.spring.annotation;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author linghu
 * @date 2023/8/30 14:13
 * 这个类充当spring原生的容器ApplicationContext
 */
public class LingSpringApplicationContext {
    
    
    private Class configClass;

    //ioc里存放的是通过反射创建的对象(基于注解形式)
    private final ConcurrentHashMap<String,Object> ioc=
            new ConcurrentHashMap<>();


    public LingSpringApplicationContext(Class configClass) {
    
    
        this.configClass = configClass;
//        System.out.println("this.configClass="+this.configClass);

        //获取到配置类的@ComponentScan(value = "com.linghu.spring.component")
        ComponentScan componentScan = 
                (ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
        //取出注解的value值:com.linghu.spring.component。得到类路径,要扫描的包
        String path = componentScan.value();
//        System.out.println("value="+value);

        //得到要扫描包下的资源(.class文件)
        //1、得到类的加载器
        ClassLoader classLoader =
                LingSpringApplicationContext.class.getClassLoader();
        path = path.replace(".", "/");

        URL resource = classLoader.getResource(path);
//        System.out.println("resource="+resource);

        //将要加载的资源(.class)路径下的文件进行遍历=》io
        File file = new File(resource.getFile());
        if (file.isDirectory()){
    
    
            File[] files = file.listFiles();
            for (File f :files) {
    
    
                //获取"com.linghu.spring.component"下的所有class文件
                System.out.println("===========");
                //D:\Java\JavaProjects\spring\target\classes\com\linghu\spring\component\UserDAO.class
                System.out.println(f.getAbsolutePath());
                String fileAbsolutePath = f.getAbsolutePath();

                //只处理.class文件
                if (fileAbsolutePath.endsWith(".class")){
    
    

                //1、获取到类名=》字符串截取
                String className =
                        fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
//                System.out.println("className="+className);

                //2、获取类的完整的路径
                String classFullName = path.replace("/", ".") + "." + className;
                System.out.println("classFullName="+classFullName);

                //3、判断该类是不是需要注入容器,就看该类是不是有注解@compoment,@controller,@Service...
                    try {
    
    
                        //得到指定类的类对象,相当于Class.forName("com.xxx")
                        Class<?> aClass = classLoader.loadClass(classFullName);
                        if (aClass.isAnnotationPresent(Component.class)||
                                aClass.isAnnotationPresent(Service.class)||
                                aClass.isAnnotationPresent(Repository.class)||
                                        aClass.isAnnotationPresent(Controller.class)){
    
    

                            //演示一个component注解指定value,分配id
                            if (aClass.isAnnotationPresent(Component.class)){
    
    
                                Component component = aClass.getDeclaredAnnotation(Component.class);
                                String id = component.value();
                                if (!"".endsWith(id)){
    
    
                                    className=id;//用户自定义的bean id 替换掉类名
                                }
                            }


                            //这时就可以反射对象,放入到ioc容器中了
                            Class<?> clazz = Class.forName(classFullName);
                            Object instance = clazz.newInstance();//反射完成
                            //放入到容器中,将类的首字母变成小写,这里用了Stringutils
                            ioc.put(StringUtils.uncapitalize(className),instance);

                        }

                    } catch (ClassNotFoundException e) {
    
    
                        e.printStackTrace();
                    } catch (InstantiationException e) {
    
    
                        throw new RuntimeException(e);
                    } catch (IllegalAccessException e) {
    
    
                        throw new RuntimeException(e);
                    }
                }


        }
    }
    }

    //返回容器中的对象
    public Object getBean(String name){
    
    
        return ioc.get(name);
    }
}

Gitee: "Implementing Spring Container Mechanism"

Guess you like

Origin blog.csdn.net/weixin_43891901/article/details/132789804