Desenvolvimento manual - programa de configuração simples baseado em anotação Spring - análise de código-fonte


No artigo anterior "Desenvolvimento Manual - Programa Simples Baseado em Configuração XML Spring - Análise de Código Fonte" , lemos as informações do objeto bean do arquivo de configuração XML, inicializamos no contêiner que projetamos, injetamos atributos e, finalmente, passamos getBean ( ) o método retorna. Neste artigo, implementaremos um contêiner Spring simples baseado na perspectiva de anotação. Também faremos algumas alterações aqui. Anteriormente, inicializamos o contêiner de passagem de valor por meio do nome do arquivo xml. Aqui, inicializamos o contêiner por meio do tipo de interface de passagem de valor. Portanto, este artigo possui os dois recursos a seguir:

  • Implementar simulação de contêiner Spring com base em anotações
  • Inicialize o contêiner ioc por meio do tipo de interface

@Nota de projeto@

Existem muitas anotações no Spring. Aqui projetaremos uma anotação para uso. Então, como projetar anotações? O design de anotação do Spring é baseado em meta-anotações . Meta-anotações são a base do Java. As meta-anotações são as seguintes:

  • @Alvo

    Usado para especificar o escopo de uso das anotações

    • ElementType.TYPE: classe, interface, anotação, enumeração
    • ElementType.FIELD: campos, constantes de enumeração
    • ElementType.METHOD: Método
    • ElementType.PARAMETER: parâmetros formais
    • ElementType.CONSTRUCTOR:Construtor
    • ElementType.LOCAL_VARIABLE: variável local
    • ElementType.ANNOTATION_TYPE: anotação
    • ElementType.PACKAGE:包
    • ElementType.TYPE_PARAMETER: parâmetro de tipo
    • ElementType.TYPE_USE: tipo de uso
  • @Retenção

    Política de retenção para especificar anotações

    • RetentionPolicy.SOURCE: As anotações são retidas apenas no código-fonte e serão descartadas pelo compilador durante a compilação.
    • RetentionPolicy.CLASS: as anotações (política de retenção padrão) serão retidas no arquivo de classe, mas não serão carregadas na máquina virtual e não poderão ser obtidas em tempo de execução.
    • RetentionPolicy.RUNTIME: As anotações serão retidas no arquivo Class e carregadas na máquina virtual, que pode ser obtida em tempo de execução.
  • @Documentado

    • Usado para incluir anotações em javadoc
    • Por padrão, o javadoc não inclui anotações, mas se a anotação @Documented for usada, as informações relevantes do tipo de anotação serão incluídas no documento gerado.
  • @Herdado

    Usado para indicar que as anotações da classe pai serão herdadas pelas subclasses

  • @Repetivel

    As anotações usadas para declarar tags são anotações de tipo repetível e podem ser usadas várias vezes no mesmo local.

Essas meta-anotações são os componentes mais básicos e precisamos usá-las ao projetar anotações. Agora projetaremos nossa própria anotação 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 anotação terá efeito em tempo de execução; @Target(ElementType.TYPE)indica o tipo que a anotação pode modificar. Quando inserimos o Typecódigo-fonte, sabemos através da anotação que TYPEele contém Classe, interface (incluindo tipo de anotação) ou declaração enum , o que significa que pode ser uma classe ou 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
}

Neste momento, para verificar a nova anotação ComponentScan que projetamos, criamos uma nova classe de configuração LingHuSpringConfig. Na verdade, esta classe de configuração não implementará nada especificamente. Ela apenas coloca uma anotação ComponentScan no nome da classe e, em seguida, define um valor, da seguinte forma:

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

}

Nosso objetivo ao configurar esta classe de configuração é: quando inicializamos o contêiner, apenas passamos LingHuSpringConfig.classa interface diretamente, inicializamos o contêiner ioc por meio do tipo de interface e o contêiner verifica o caminho de classe completo com.linghu.spring.component com base no anotações que projetamos .

$designcontainer$

Na verdade, o design deste contêiner é semelhante ao descrito em "Desenvolvimento Manual - Programa Simples Baseado em Configuração XML Spring - Análise de Código Fonte" , ambos requerem:

  • um ConcurrentHashMapcomo contêiner
  • Um construtor para inicializar o contêiner.
  • Forneça um getBeanmétodo que retorne nosso contêiner ioc.

A maior parte do trabalho aqui é feita no construtor . O trabalho concluído é o seguinte:

  • Encontre @ComponentScana classe de configuração e leia o valor para obter o caminho da classe.
  • Através do caminho de classe na etapa anterior, precisamos ir até o caminho do targetdiretório correspondente para indexar todos os arquivos, que na verdade são aqueles arquivos .class. Filtramos esses arquivos. Durante o processo de filtragem, determinamos se eles possuem anotações adicionadas . Se tiverem, adicione-os. Os caminhos de classe desses arquivos são salvos no contêiner ioc.
  • Ao recuperar e filtrar arquivos, precisamos extrair os nomes dos arquivos .class armazenados no arquivo do componente e, em seguida, salvar esses nomes no contêiner.
  • Obtenha o caminho de classe completo e determine se essas classes possuem anotações: @compoment, @controller, @Service…. Preciso injetar o recipiente?
  • Obtenha o valor da anotação Component.Este valor é usado como o nome do ID do objeto bean e é armazenado no contêiner ioc.

Finalmente, percebemos isso. Através das anotações que definimos, verificamos o caminho da classe anotada e o adicionamos ao contêiner ioc que criamos. Finalmente, obtivemos os objetos que precisávamos por meio do contêiner ioc que nós mesmos projetamos. Como o ioc nos ajuda a criar objetos? Criado por meio de reflexão , o caminho de classe exigido pela reflexão é o que lemos na anotação.

imagem

#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: "Implementando Mecanismo de Contêiner Spring"

Acho que você gosta

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