[Tópico Spring] Análise dos princípios básicos subjacentes do Spring - orientação

prefácio

Primavera, pode-se dizer que a maioria dos nossos jogadores Java [o estranho mais familiar]. Oito palavras para descrever: meio compreendido,
insira a descrição da imagem aqui
você diria uma aplicação simples, todos nós sabemos disso, se você realmente deseja expandir e dizer duas frases, então você só pode chegar a estas duas frases: esta é a primeira frase, seguida pela segunda frase, bem, terminei.
insira a descrição da imagem aqui
Mas ah, xdm,Diz-se que Spring é um código-fonte muito, muito, muito bom, não apenas possui cenários de aplicativos de padrões de design ricos, mas também o código é lindamente escrito e organizado, por isso recomendo fortemente que todos o aprendam. Além de poder fingir no dia a dia, você também pode enriquecer seus conhecimentos e melhorar sua capacidade de escrever códigos.

leia a navegação

Leitores: Pessoas com experiência em desenvolvimento Spring

Pré-conhecimento

Q1: Você pode descrever o processo de criação de objetos JVM?

Resposta: Olhe a foto e fale:
insira a descrição da imagem aqui

  1. Carregamento de classe: Antes de usar uma classe, a máquina virtual Java precisa carregar o bytecode da classe na memória. O carregamento de classe é o processo central da máquina virtual Java, responsável por localizar o arquivo de bytecode da classe e carregá-lo na área de método da memória. O carregamento da classe inclui cinco fases: carregamento, verificação, preparação, análise e inicialização.
  2. Alocação de memória: Após a conclusão do carregamento da classe, a máquina virtual Java aloca espaço de memória para o objeto. A alocação de memória geralmente é realizada no heap (Heap), mas também existem objetos em alguns casos especiais que podem alocar memória na pilha (Stack), como objetos locais na pilha de threads.
  3. Instanciação (inicialização do valor zero): Após alocar memória, a máquina virtual Java inicializa o espaço de memória do objeto com valor zero. Isso inclui valores padrão para tipos primitivos (como 0, falso, etc.) e valores padrão (nulos) para tipos de referência.
  4. Defina o cabeçalho do objeto: O layout do objeto Java na memória inclui duas partes: o cabeçalho do objeto e os dados da instância. O cabeçalho do objeto armazena alguns metadados, como código hash do objeto, status do bloqueio, etc. Durante a criação do objeto, a Java Virtual Machine define o valor do cabeçalho do objeto.
  5. Executando o construtor: A última etapa na criação do objeto é executar o construtor. O construtor é usado para inicializar os dados de instância do objeto e realizar outras operações de inicialização necessárias. O construtor pode ser o construtor padrão da classe ou um construtor personalizado.
  6. Retornar referência de objeto: após a criação do objeto, a máquina virtual Java retorna uma referência ao objeto. Através da referência, o programa pode manipular as propriedades e métodos do objeto.

(PS: Por que você está fazendo essa pergunta? Como o Spring é uma tecnologia IOC, não importa o quanto funcione, ele deve seguir este processo básico para criar objetos. No entanto, posso dizer com antecedência que o Spring IOC adicionou muitos novos slots neste processo e enriqueceu as funções do IOC por meio de hot swapping!

Q2: Quais são as características do Spring?

Resposta: As características do Spring são os dois conceitos de IOC e AOP! Pode-se até dizer que:Spring é um contêiner IOC que implementa tecnologia AOP. (contêiner, contêiner, contêiner)

Q3: O que é IOC e o que é AOP?
Resposta: A seguinte resposta vem do Baidu [Wen Xin Yi Yan]:

  • IOC (Inversão de Controle) é um padrão de design (pensamento) que permite a criação e gerenciamento de objetos a serem manipulados pelo contêiner Spring ao invés de criar objetos diretamente no código . Ao usar o IOC, as dependências dos objetos podem ser desacopladas do código, tornando o código mais flexível, sustentável e testável.
  • AOP (Programação Orientada a Aspectos) também é um padrão de design (pensamento), que usa proxy dinâmico de pré-compilação e tempo de execução para adicionar funções dinamicamente aos programas sem modificar o código-fonte . AOP resolve problemas que não podem ser resolvidos na programação orientada a objetos, como gerenciamento de transações, segurança, registro, etc.

A estrutura Spring torna o programa mais modular, flexível e fácil de manter, implementando IOC e AOP. Ao mesmo tempo, Spring também oferece muitos outros módulos e funções, como DAO, ORM, WebMVC, etc., tornando-o uma poderosa estrutura de desenvolvimento Java.

Resumo do pré-conhecimento

Da pergunta acima, mencionamos uma coisa muito importante, a saber:Spring é um contêiner IOC que implementa tecnologia AOP. Além disso, também descreve genericamente os conceitos de IOC e AOP. Agora que também sabemos que o IOC realmente gerencia a criação de objetos, então, quando se trata de criação de objetos, ele deve ser inseparável do processo de criação de objetos que mencionamos no Q1. Além disso, não importa como o objeto é criado ou quem o cria, não há como sair do processo acima.
Na verdade, o que posso dizer de antemão é que o processo de criação de objetos no IOC nada mais é do que enriquecer alguns detalhes e adicionar alguns pontos de extensão no processo de criação de objetos acima para fornecer suporte para a realização da função Spring.

Conteúdo do curso

Para realizar a pesquisa sobre o código-fonte do Spring, falaremos brevemente sobre alguns pontos básicos de conhecimento do Spring aqui, para que todos possam ter uma compreensão clara de alguma lógica básica na parte inferior do Spring.

1. Iniciando o contêiner Spring

Acho que amigos que vivenciaram a era SSM/SSH estarão familiarizados com o seguinte código:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();
System.out.println(userService);
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">

<!--    <import resource="引入其他bean xml配置文件" />-->
    <bean id="userService" class="org.example.spring.bean.UserService"/>
</beans>

Não importa se você realmente não está familiarizado, o seguinte pode ser relativamente familiar: ( Também explicarei o Spring deste método de inicialização mais tarde. Além dos mais convencionais abaixo, é também porque o método a seguir é mais amplamente utilizado, atualizado e com conteúdo relativamente rico! )

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();
System.out.println(userService);
@Component
public class UserService {
    
    

    public void test() {
    
    
        System.out.println("这是一个测试方法");
    }
}

Haha, acho que muitos amigos que entraram diretamente na era do Java [SpringBoot] podem nem ter visto o que foi dito acima.
O que o código acima faz? Muito simples, basta iniciar um contêiner Spring. Os dois métodos de inicialização diferentes acima são apenas métodos diferentes de registro de beans. Por exemplo, o primeiro é definido pela leitura das tags xmlinternas <bean>e o último é o Bean de anotação de leitura.
Neste ponto gostaria de te fazer uma pergunta, ou seja, através do código do segundo método acima, o que você encontrou? Minha descoberta é: basta chamar uma linha de código e posso começar a usar o Bean definido pelo Spring, não me importo com injeção de dependência, AOP, etc., apenas faço isso diretamente. O que isso prova? Na verdade, é superficial e um pouco absurdo, e essa é a prova:Através desta linha de código, ela me ajuda a completar todos os recursos básicos do Spring que normalmente usamos.

2. Especulação geral do processo

De acordo com as operações relacionadas ao Spring que aprendemos antes, podemos simplesmente especular sobre o que é feito nesta linha de código.

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

2.1 Digitalização

O primeiro é o primeiro ponto [digitalização]. Escrevemos tantos beans no projeto, ou seja, há tantas classes no meu projeto, que são beans e quais são classes comuns, como o Spring reconhece isso? Na verdade, o motivo é muito simples: Spring não é tão inteligente. Se você deseja obter as informações desta classe, Spring deve dar uma olhada [pessoalmente] para saber as informações específicas desta classe. Quantos arquivos você tem, quantas classes ele irá digitalizar. O código chave é o seguinte:

// 定义需要扫描的基础包名
@ComponentScan("org.tuling.spring")
public class AppConfig {
    
    
}

2.2 COI

Depois de verificar todos os arquivos, o Spring pode basicamente determinar quais são Beans e quais são classes comuns. A seguir, você pode começar a criar beans, aqui está o chamado processo IOC

2.3 POA

AOP deve ter acontecido após o IOC. Se você entende o [padrão proxy] no padrão de design, não é difícil entender isso. Afinal, se o objeto alvo não estiver totalmente funcional, a funcionalidade do objeto proxy também será afetada.

2.4 Resumo

insira a descrição da imagem aqui

3. [Digitalização] O processo é simples de especular

Também dissemos antes que a digitalização exige que o Spring verifique pessoalmente quais precisam ser criados e quais não. Veja nosso exemplo new AnnotationConfigApplicationContext(AppConfig.class), as etapas gerais são as seguintes: (especulação simples, desconhecida)

  1. Ele precisa olhar primeiro AppConfig.class, ler o caminho base do pacote digitalizado
  2. De acordo com o caminho básico lido na etapa anterior, percorra todos os arquivos do pacote, se houver anotações como @Component, @Serviceetc. na classe, ela é confirmada como Bean
  3. Após a filtragem, registre as informações lidas do Bean, por exemplo, salve-as em um Mapa para posterior travessia

insira a descrição da imagem aqui

4. 【IOC】Processo simples de adivinhação

O processo IOC, na verdade, tem um termo mais profissional no Spring, chamado: Ciclo de vida do feijão. Algumas palavras simples contêm muito conteúdo. Antes disso, vamos dar uma olhada no [processo de criação de objeto JVM] em [Pré-conhecimento] para aprofundar sua impressão.
Incluindo:

  1. Use o método de construção desta classe para instanciar um objeto (mas se houver vários métodos de construção em uma classe, o Spring escolherá, isso é chamado de método de construção inferido)
  2. Após obter um objeto, o Spring julgará se há atributos anotados por @Autowired no objeto, descobrirá esses atributos e atribuirá valores pelo Spring (injeção de dependência)
  3. Após a injeção de dependência, o Spring julgará se o objeto implementa a interface BeanNameAware, a interface BeanClassLoaderAware e a interface BeanFactoryAware.Se for implementado, significa que o objeto atual deve implementar os métodos setBeanName(), setBeanClassLoader() e setBeanFactory() definidos na interface. Então o Spring chamará esses métodos e passará os parâmetros correspondentes (retorno de chamada do Aware)
  4. Após o retorno de chamada do Aware, o Spring julgará se existe um método anotado por @PostConstruct no objeto. Se existir, o Spring chamará esse método do objeto atual (antes da inicialização)
  5. Imediatamente depois, o Spring julgará se o objeto implementa a interface InitializingBean.Se for implementado, significa que o objeto atual deve implementar o método afterPropertiesSet() na interface, e então o Spring chamará o método afterPropertiesSet() no objeto atual (inicialização)
  6. Finalmente, o Spring julgará se o objeto atual precisa executar o AOP. Caso contrário, o Bean será criado. Se o AOP for necessário, ele executará o proxy dinâmico e gerará um objeto proxy como um Bean (após a inicialização)

Outra coisa a observar é que após a criação do objeto Bean:

  1. Se o Bean atual for um Bean singleton, então o objeto Bean será armazenado em um Map<String, Object>, a chave do Mapa é o beanName e o valor é o objeto Bean. Dessa forma, na próxima vez que você obtiverBean, poderá obter diretamente o objeto Bean correspondente do Mapa (na verdade, no código-fonte do Spring, este Mapa é um pool singleton);
  2. Se o bean atual for um bean protótipo, não haverá outras ações no futuro e nenhum mapa será armazenado. O processo de criação acima será executado novamente na próxima vez que você obtiverBean, e um novo objeto Bean será obtido.

insira a descrição da imagem aqui

4.1 Descrição detalhada do processo de inferência do método de construção

No processo de geração de beans com base em uma determinada classe, o Spring precisa usar o método de construção da classe para instanciar um objeto, mas se uma classe tiver vários métodos de construção, qual deles o Spring usará?

A lógica de julgamento do Spring é a seguinte:

  1. Se uma classe tiver apenas um construtor, independentemente de o construtor ter ou não parâmetros, o Spring usará esse construtor para criar objetos porque não há escolha;
  2. Se esta classe tiver vários construtores:
    • Se existir um construtor sem argumento, use o construtor sem argumento. Porque em Java, o próprio construtor sem argumento possui um significado padrão;
    • Se não houver nenhum construtor sem argumento, então observe vários construtores sem argumento, qual deles tem @Autowiredmodificação, escolha-o; se não houver nenhum, você só poderá relatar um erro

ainda tenho uma pergunta. Se o Spring escolher um método de construção com parâmetros, o Spring precisará passar parâmetros ao chamar esse método de construção com parâmetros, então de onde vem esse parâmetro? A resposta é: Spring encontrará o objeto Bean no Spring de acordo com o tipo e nome do parâmetro de entrada.
3. Primeiro pesquise de acordo com o tipo de parâmetro de entrada, se apenas um for encontrado, use-o diretamente como parâmetro de entrada;
4. Se mais de um for encontrado de acordo com o tipo, determine o único de acordo com o nome do parâmetro de entrada 5.
Finalmente, se não for encontrado, um erro será relatado e o objeto Bean atual não poderá ser criado.

5. [AOP] processo simples de adivinhação

AOP é proxy dinâmico. No processo de criação de um Bean, o Spring está na última etapa (Antes de colocar no pool singleton) julgará se o Bean que está sendo criado precisa executar AOP e, se necessário, executará proxy dinâmico.
Então, como julgar se um Bean precisa ser proxy pelo AOP? Proceda da seguinte forma:

  1. Encontre todos os beans de faceta (as facetas também são de beans, ou chamadas: beans especiais)
  2. Percorra cada método no aspecto para ver se @Before, @After e outras anotações (notificações) foram escritas
  3. Se estiver escrito, julgue se o Pointcut correspondente corresponde à classe do objeto Bean atual
  4. Se corresponder, significa que o objeto Bean atual tem um Pointcut correspondente, o que significa que o AOP é necessário

O processo geral de AOP usando cglib: (veja o paradigma de proxy acima)

  1. Adicione uma classe proxy XxxProxy, que herda do objeto proxy XxxTarget e contém uma variável de membro XxxTarget (esta variável de membro precisa passar por um ciclo de declaração Bean, ou seja, IOC completo, etc.)
  2. Substitua o método da classe pai na classe proxy
  3. Ao executar o método da classe proxy, o método da classe proxy é chamado, mas ao mesmo tempo, a lógica do aspecto precisa ser executada

insira a descrição da imagem aqui

Então aqui está um paradigma para [modo proxy]:

// 被代理对象
public class ProxyTarget {
    
    
    public void run() {
    
    
        System.out.println("这是普通对象的run");
    }
}


// 代理对象
public class ProxyModel extends ProxyTarget {
    
    
    private ProxyTarget proxyTarget;

    public void setProxyTarget(ProxyTarget proxyTarget) {
    
    
        this.proxyTarget = proxyTarget;
    }

    @Override
    public void run() {
    
    
        System.out.println("我代理对象可以在这里做加强---1");
        super.run();
        System.out.println("我代理对象也可以在这里做加强---2");
    }
}

Seis, assuntos de primavera

Quando adicionamos a anotação @Transactional a um método, significa que o método iniciará uma transação Spring quando for chamado, e o objeto Bean correspondente à classe onde o método está localizado será o objeto proxy da classe.
As etapas quando o objeto proxy da transação Spring executa um determinado método:

  1. Determine se o método atualmente em execução possui a anotação @Transactional
  2. Se existir, use o gerenciador de transações (TransactionManager) para criar uma nova conexão com o banco de dados
  3. Modifique o autocommit da conexão com o banco de dados para false
  4. Execute target.test(), execute o código de lógica de negócios escrito pelo programador, ou seja, execute sql
  5. Após a execução, se não houver exceção, envie, caso contrário, reverta

Critérios para julgar se uma transação Spring irá falhar: Quando um método anotado com @Transactional é chamado, é necessário julgar se ele é chamado diretamente pelo objeto proxy. Se sim, a transação terá efeito, e se não, ela entrará em vigor. falhar.(PS: Esse ponto é fácil de passar despercebido)
Além disso, há outro exemplo clássico, ou seja , o resultado é diferente quando @Beanexiste ou não , como segue:@Configuration

A maneira de declarar o bean:

@ComponentScan("org.tuling.spring")
@Configuration
public class AppConfig {
    
    
    
    @Bean
    public UserService userService() {
    
    
        return new UserService(walletService());
    }

    @Bean
    public UserService userService1() {
    
    
        return new UserService(walletService());
    }

    @Bean
    public WalletService walletService() {
    
    
        return new WalletService();
    }
}

// UserService声明
public class UserService {
    
    
    private WalletService walletService;

    public UserService() {
    
    }
    public UserService(WalletService walletService) {
    
    
        this.walletService = walletService;
    }

    public WalletService getWalletService() {
    
    
        return walletService;
    }

    /**
     * 自我介绍
     */
    public void selfIntroduction() {
    
    
        System.out.println("你好,我是阿通,我有好多钱");
        walletService.showMyBalance();
    }
}

Veja o método de declaração do Bean acima. De acordo com a suposição, WalletServiceele deve ser um singleton, portanto deve ser igual userServiceao objeto userService1mantido .walletService

Método de chamada:

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        UserService userService = (UserService)context.getBean("userService");
        System.out.println(userService);
        System.out.println(userService.getWalletService());


        System.out.println("--------------------------------");
        UserService userService1 = (UserService)context.getBean("userService1");
        System.out.println(userService1);
        System.out.println(userService1.getWalletService());

A saída resultante é a seguinte:

org.tuling.spring.bean.UserService@2c34f934
org.tuling.spring.bean.WalletService@12d3a4e9
--------------------------------
org.tuling.spring.bean.UserService@240237d2
org.tuling.spring.bean.WalletService@12d3a4e9

Olhando os resultados, não há problema e a saída está conforme planejado. Mas se removermos o método que declara o Bean @Configuration, o resultado ficará assim:

@ComponentScan("org.tuling.spring")
//@Configuration
public class AppConfig {
    
    

    @Bean
    public UserService userService() {
    
    
        return new UserService(walletService());
    }

    @Bean
    public UserService userService1() {
    
    
        return new UserService(walletService());
    }

    @Bean
    public WalletService walletService() {
    
    
        return new WalletService();
    }
}
org.tuling.spring.bean.UserService@710726a3
org.tuling.spring.bean.WalletService@646007f4
--------------------------------
org.tuling.spring.bean.UserService@481a15ff
org.tuling.spring.bean.WalletService@78186a70

Por que simplesmente anotar um @Configurationresultado é diferente? analise conforme abaixo:

  1. @BeanA anotação pode registrar o objeto retornado pelo método como um Bean, e o Bean será gerenciado pelo contêiner Spring. nada mais
  2. Portanto, quando o userService()método é chamado novamente walletService(), na verdade é apenas uma chamada Java comum e com certeza será chamado novamente.new WalletService()
  3. Depois de @Configurationanotados, todos os métodos serão proxy (a evidência do código-fonte ainda não foi encontrada e irei anexá-la mais tarde, quando entender)

resumir

  1. Simplesmente aprendi o processo de inicialização do Spring
  2. Através da série de algumas operações comuns do Spring, tenho uma compreensão geral do processo geral de IOC e AOP

Acho que você gosta

Origin blog.csdn.net/qq_32681589/article/details/132101489
Recomendado
Clasificación