Desarrollo manual: programa de configuración simple basado en anotaciones Spring: análisis de código fuente


En el artículo anterior "Desarrollo manual - Programa simple basado en configuración Spring XML - Análisis de código fuente" , leímos la información del objeto bean del archivo de configuración XML, luego la inicializamos en el contenedor que diseñamos, inyectamos atributos y finalmente pasamos getBean ( ) el método regresa. En este artículo, implementaremos un contenedor Spring simple basado en la perspectiva de anotaciones. También haremos algunos cambios aquí. Anteriormente, inicializamos el contenedor de paso de valores a través del nombre del archivo xml. Aquí, inicializamos el contenedor a través del tipo de interfaz de paso de valores. Por tanto, este artículo tiene las dos características siguientes:

  • Implementar la simulación del contenedor Spring basada en anotaciones.
  • Inicialice el contenedor ioc a través del tipo de interfaz

@Nota de diseño@

Hay muchas anotaciones en Spring, aquí diseñaremos una anotación para su uso. Entonces, ¿cómo diseñar anotaciones? El diseño de anotaciones de Spring se basa en metaanotaciones . Las metaanotaciones son la base de Java y son las siguientes:

  • @Objetivo

    Se utiliza para especificar el alcance de uso de las anotaciones.

    • ElementType.TYPE: clase, interfaz, anotación, enumeración
    • ElementType.FIELD: campos, constantes de enumeración
    • ElementType.METHOD: Método
    • ElementType.PARAMETER: parámetros formales
    • Tipo de elemento.CONSTRUCTOR:Constructor
    • ElementType.LOCAL_VARIABLE: variable local
    • ElementType.ANNOTATION_TYPE: Anotación
    • ElementType.PACKAGE:包
    • ElementType.TYPE_PARAMETER: parámetro de tipo
    • ElementType.TYPE_USE: tipo de uso
  • @Retención

    Política de retención para especificar anotaciones

    • RetentionPolicy.SOURCE: las anotaciones solo se conservan en el código fuente y el compilador las descartará durante la compilación.
    • RetentionPolicy.CLASS: las anotaciones (política de retención predeterminada) se conservarán en el archivo de clase, pero no se cargarán en la máquina virtual y no se podrán obtener en tiempo de ejecución.
    • RetentionPolicy.RUNTIME: las anotaciones se conservarán en el archivo de clase y se cargarán en la máquina virtual, que se puede obtener en tiempo de ejecución.
  • @Documentado

    • Se utiliza para incluir anotaciones en javadoc.
    • De forma predeterminada, javadoc no incluye anotaciones, pero si se utiliza la anotación @Documented, la información del tipo de anotación relevante se incluirá en el documento generado.
  • @Heredado

    Se utiliza para indicar que las anotaciones de la clase principal serán heredadas por las subclases.

  • @Repetible

    Las anotaciones utilizadas para declarar etiquetas son anotaciones de tipo repetible y se pueden usar varias veces en el mismo lugar.

Estas metaanotaciones son los componentes más básicos y debemos utilizarlas al diseñar anotaciones. Ahora diseñaremos nuestra propia anotación ComponentScan:

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

}

@Retention(RetentionPolicy.RUNTIME)Indica que esta anotación tendrá efecto en tiempo de ejecución; @Target(ElementType.TYPE)indica el tipo que la anotación puede modificar. Cuando ingresamos el Typecódigo fuente, sabemos a través de la anotación que TYPEcontiene Clase, interfaz (incluido el tipo de anotación) o declaración de enumeración , lo que significa que puede ser una clase o interfaz. …:

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
}

En este momento, para verificar la nueva anotación ComponentScan que diseñamos, creamos una nueva clase de configuración LingHuSpringConfig. De hecho, esta clase de configuración no implementará nada específico. Simplemente coloca una anotación ComponentScan en el nombre de la clase y luego establece un valor, de la siguiente manera:

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

}

Nuestro propósito al configurar esta clase de configuración es: cuando inicializamos el contenedor, simplemente pasamos LingHuSpringConfig.classla interfaz directamente, inicializamos el contenedor ioc a través del tipo de interfaz y el contenedor escanea la ruta de clase completa com.linghu.spring.component según el anotaciones que diseñamos .

$contenedor de diseño$

De hecho, el diseño de este contenedor es similar al descrito en "Desarrollo manual - Programa simple basado en configuración Spring XML - Análisis de código fuente" , ambos requieren:

  • uno ConcurrentHashMapcomo contenedor
  • Un constructor para inicializar el contenedor.
  • Proporcione un getBeanmétodo que devuelva nuestro contenedor ioc.

La mayor parte del trabajo aquí se realiza en el constructor . El trabajo completado es el siguiente:

  • Busque @ComponentScanla clase de configuración y lea el valor para obtener la ruta de clase.
  • A través de la ruta de clase en el paso anterior, debemos ir a la ruta del targetdirectorio correspondiente para indexar todos los archivos, que en realidad son esos archivos .class. Filtramos estos archivos. Durante el proceso de filtrado, determinamos si tienen anotaciones agregadas. . Si es así, agréguelos. Las rutas de clase de estos archivos se guardan en el contenedor ioc.
  • Al recuperar y filtrar archivos, debemos extraer los nombres de los archivos .class almacenados en el archivo componente y luego guardar estos nombres en el contenedor.
  • Obtenga la ruta de clases completa y determine si estas clases tienen anotaciones: @compoment, @controller, @Service…. ¿Necesito inyectar el contenedor?
  • Obtenga el valor de la anotación del componente. Este valor se utiliza como el nombre de identificación del objeto bean y se almacena en el contenedor ioc.

Finalmente nos dimos cuenta, a través de las anotaciones que definimos, escaneamos la ruta de clase de la clase anotada y la agregamos al contenedor ioc que creamos, finalmente obtuvimos los objetos que necesitábamos a través del contenedor ioc que diseñamos nosotros mismos. ¿Cómo nos ayuda ioc a crear objetos? Creado a través de la reflexión , la ruta de clases requerida por la reflexión es lo que leemos en la anotación.

imagen

#Código completo#

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: "Implementación del mecanismo de contenedor de resortes"

Supongo que te gusta

Origin blog.csdn.net/weixin_43891901/article/details/132789804
Recomendado
Clasificación