[Análise de código-fonte] Dependências circulares do Spring (injeção de setter, injeção de construtor, múltiplas instâncias, AOP)

escreva na frente

Em primeiro lugar, a demonstração de dependência circular mais simples é: A->B e B->A. Este artigo se concentra neste exemplo para explicar dependências circulares injetadas por setters, dependências circulares injetadas por construtores, dependências circulares de múltiplas instâncias e dependências circulares com AOP. Aqui estão algumas conclusões:

  • Spring não pode resolver todas as dependências cíclicas, como aquelas injetadas por construtores.
  • Spring depende da exposição antecipada de objetos para resolver dependências cíclicas, o que é alcançado por meio do cache de nível três.
  • Os pools de cache de três níveis são chamados singletonObjects, earlySingletonObjects e singletonFactories no código-fonte.
  • No Spring, todos os objetos são obtidos através do getBean, ou seja, são encontrados no cache de terceiro nível. Se houver, são obtidos diretamente do pool de cache e retornados. Caso contrário, são criados.
  • A criação de objetos no Spring é dividida basicamente nas seguintes etapas:
    • selecione construtor
    • Instanciando objetos via reflexão
    • Coloque o objeto vazio instanciado no pool de cache de terceiro nível (singletonFactories)
    • Atribuir valores às propriedades de objetos vazios instanciados (injeção de dependência)
    • Execute alguns métodos de inicialização
    • Coloque os objetos criados no pool de cache de primeiro nível (singletonObjects)
  • Ao atribuir propriedades a um objeto, é realizada injeção de setter. O método de injeção é obter o objeto por meio de getBean.
  • A injeção do construtor (se houver) será realizada durante a fase de instanciação, e o método de injeção é obter o objeto por meio de getBean.
  • A ordem em que os objetos são criados determina que o Spring pode resolver dependências cíclicas injetadas por setters, mas não pode resolver dependências cíclicas injetadas por construtores.
  • Quando ocorre uma dependência circular que o Spring não consegue resolver, o Spring irá detectá-la através de alguns marcadores e lançar uma exceção para encerrar o programa.
  • O cache de terceiro nível armazena ObjectFactory em vez de Object.Quando ObjectFactory é chamado getObject, ele executará getEarlySingleton() para inicializar o objeto e retornar.
  • Independentemente de ocorrer uma dependência circular, o objeto instanciado será colocado no conjunto de cache de terceiro nível, porque não se sabe se uma dependência circular ocorrerá quando for colocado no conjunto de cache de terceiro nível.
  • Quando não ocorre nenhuma dependência circular, o cache de terceiro nível não será usado para inicializar objetos e o cache de segundo nível não será usado.
  • Somente quando ocorrer uma dependência circular o objeto será inicializado através do BeanFactory do cache de terceiro nível e colocado no pool de cache de segundo nível.
  • O cache de segundo nível pode ser o objeto original ou o objeto proxy, dependendo do resultado de ObjectFactory.getObject().
  • A existência do pool de cache de terceiro nível é para lidar com a operação AOP de A antecipadamente quando ocorrem dependências circulares, para que B possa referenciar corretamente o objeto proxy de A.
  • O cache de primeiro nível armazena objetos criados.

O principal problema da dependência circular é que getBean será chamado infinitamente aninhado sem interferir nele.

  • getBean(A)
    • getBean(B)
      • getBean(A)
        • getBean(B)
          • getBean(A)
            • …………

Portanto, entender por que o Spring pode resolver certas dependências circulares é, na verdade, entender por que o Spring pode impedir o aninhamento infinito de getBean em alguns casos.

lógica getBean

Clicando no código-fonte, você pode ver que getBean é na verdade apenas um "substituto".

	@Override
	public Object getBean(String name) throws BeansException {
    
    
		return doGetBean(name, null, null, false);
	}

	@Override
	public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
    
    
		return doGetBean(name, requiredType, null, false);
	}

	@Override
	public Object getBean(String name, Object... args) throws BeansException {
    
    
		return doGetBean(name, null, args, false);
	}

getBean é um método muito importante no Spring. Ele tem várias sobrecargas. Como chamar doGetBean é determinado com base nos parâmetros. O código doGetBean é muito longo e não será postado. O processo simplificado é o seguinte: Este método chama principalmente getSingleton duas vezes
Insira a descrição da imagem aqui
. Este getSingleton também é um método com múltiplas sobrecargas. O primeiro getSingleton é para verificar a lógica do cache, e o segundo getSingleton é para criar a lógica. Chamando esses dois getSingletons um após o outro, realiza-se a lógica do getBean que mencionamos acima: se houver, ele será retornado, se não houver, será criado.
É claro que os beans do Spring não possuem apenas tipos singleton, mas também tipos de casos múltiplos, outros tipos, etc. O processo acima é baseado na premissa de que o objeto criado é um singleton. Na verdade, doGetBean também tem a lógica de como lidar com tipos de beans de múltiplas instâncias e outros tipos de beans. Não vou entrar em detalhes aqui por enquanto, falarei sobre isso mais tarde.

getSingleton obtém lógica de cache

Vamos dar uma olhada mais de perto na lógica do primeiro getSingleton:

	@Nullable // doGetBean 第一次调用 getSingleton 的是这个
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    
    
		// Quick check for existing instance without full singleton lock
		Object singletonObject = this.singletonObjects.get(beanName); //先检查单例缓存池有没有这个对象,一级缓存,有就直接返回
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    
     // 是否正在创建,没有正在创建的话就返回
			singletonObject = this.earlySingletonObjects.get(beanName); // 查看是否有缓存,二级缓存,早期单例池,有也直接返回
			if (singletonObject == null && allowEarlyReference) {
    
    
				synchronized (this.singletonObjects) {
    
     // 锁 争夺锁的条件: 无一级,正在创建标记,无二级,允许早期引用
					// Consistent creation of early reference within full singleton lock
					singletonObject = this.singletonObjects.get(beanName); // 单例模式的双检,加锁之后再查一次一级缓存
					if (singletonObject == null) {
    
     // 经过双检,一级缓存确实没有这个bean
						singletonObject = this.earlySingletonObjects.get(beanName); // 单例模式的双检,加锁之后再查一次二级缓存
						if (singletonObject == null) {
    
    // 经过双检,二级缓存确实没有这个bean (可是这个锁锁的是一级而不是二级缓存,这样能达成双检的目的吗)
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);//三级缓存,单例工厂池
							if (singletonFactory != null) {
    
     //在三级缓存中存在
								singletonObject = singletonFactory.getObject();//获取三级缓存 (getEarlyBeanReference)
								this.earlySingletonObjects.put(beanName, singletonObject); // 放入二级缓存
								this.singletonFactories.remove(beanName); // 移除三级缓存
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}

Os singletonObjects, earlySingletonObjects e singletonFactories aqui são o que chamamos de cache de terceiro nível. Além do cache de terceiro nível, existem duas variáveis ​​que afetam a lógica de obtenção do cache: permitEarlyReference e isSingletonCurrentlyInCreation (dentro do método é verificar se a coleção singletonsCurrentlyInCreation possui este beanName) onde allowEarlyReference é um atributo configurável do Spring,
e o padrão é verdadeiro. Ou seja, referências iniciais são permitidas. Somente quando verdadeiro o Spring pode resolver algumas dependências circulares. E singletonsCurrentlyInCreation é na verdade uma marca, que irá marcar e remover a marca antes e depois da criação do objeto, o que acontece no segundo getSingleton, que será discutido mais adiante.
Este código está aninhado em muitas camadas e parece ser uma dor de cabeça de usar. A tradução do pseudocódigo é:

if(不存在一级缓存 && 对象没有被标记为正在创建){
    
    
	if(不存在二级缓存&&允许早期引用){
    
    
		synchronized(一级缓存池){
    
    
			if(不存在一级缓存){
    
    
				if(不存在二级缓存){
    
    
					if(存在三级缓存){
    
    
						将三级缓存移动到二级缓存
					}
				}
			}
		}
	}
}
返回对象

Resumindo, a lógica é

  • Se houver um cache de primeiro nível, ele retornará diretamente
  • Caso não esteja sendo criado retorne diretamente (somente se este objeto estiver sendo criado é possível acessar o cache de segundo nível e o cache de terceiro nível)
  • Se houver um cache de segundo nível, retorne diretamente
  • Se as referências aos objetos anteriores não forem permitidas, retorne diretamente (somente se as referências aos objetos anteriores tiverem permissão para acessar o cache de segundo nível e o cache de terceiro nível)
  • Se houver um cache de primeiro nível, ele retornará diretamente
  • Se houver um cache de segundo nível, retorne diretamente
  • Se houver um cache de terceiro nível, primeiro coloque-o no cache de segundo nível e depois retorne

Observe que o cache de primeiro nível e o cache de segundo nível são consultados e verificados duas vezes. Se você entende o modo singleton, é fácil saber que uma operação de verificação dupla é usada aqui. As duas últimas verificações são para garantir que o objeto deve ser um singleton. de. Para nos ajudar a entender a lógica deste código, não consideramos a situação do multi-threading. Depois de convertê-lo em uma lógica single-threaded, a lógica é:

if(不存在一级缓存 && 对象没有被标记为正在创建){
    
    
	if(不存在二级缓存 && 允许早期引用){
    
    
		if(存在三级缓存){
    
    
			将三级缓存移动到二级缓存
		}
	}
}
返回对象

Se não houver cache de terceiro nível, então nulo será retornado diretamente e a lógica de criação do objeto será executada posteriormente.
Claro, mesmo que tenha sido simplificado até este ponto, é provável que você fique confuso e tenha muitas dúvidas: Por que são projetados três caches? Qual é a diferença? Por que a ordem do julgamento é desta forma? Por que mover o cache de terceiro nível para o cache de segundo nível? Etc., etc.
Estas serão respondidas uma a uma abaixo. Por enquanto, você pode ter uma ideia aproximada deste processo.

O mais detalhado é que o bloqueio sincronizado aqui é o monitor de objetos do pool de cache de primeiro nível, então a granularidade do bloqueio será menor que a do monitor de classe, afinal a classe onde esse método está localizado tem mais mais de 600 linhas e muitos métodos usam bloqueios. A vantagem de usar o monitor de objetos do conjunto de cache de primeiro nível como um bloqueio é que ele é lógico e também melhora a simultaneidade e reduz a possibilidade de conflito.

getSingleton cria lógica de objeto

Na segunda vez que getSingleton executa a lógica de criação de um objeto, ele faz principalmente apenas quatro coisas:

  • Marque este objeto como sendo criado (singletonsCurrentlyInCreation.add(beanName))
  • Chame singletonFactory.getObject() para criar um objeto
  • Cancele a marca de que o objeto está sendo criado (singletonsCurrentlyInCreation.remove(beanName))
  • Exclua o objeto dos conjuntos de cache de segundo e terceiro níveis e inclua-o no conjunto de cache de primeiro nível.

singletonFactory é um objeto do tipo ObjectFactory.Este chamado ObjectFactory é uma interface funcional:

@FunctionalInterface
public interface ObjectFactory<T> {
    
    
	T getObject() throws BeansException;
}

Vendo isso, não posso deixar de ficar um pouco confuso. Este método getObject não tem parâmetros e ObjectFactory não tem variáveis ​​de membro. Como ele sabe qual objeto eu quero criar? Rastreando a origem deste objeto, podemos descobrir que este singletonFactory é um parâmetro de entrada de getSingleton e passamos uma expressão lambda quando doGetBean chama getSingleton (ou seja, this) pela segunda vez.

sharedInstance = getSingleton(beanName, () -> {
    
    
	try {
    
    
		return createBean(beanName, mbd, args); // 创建对象的实例
	}
	catch (BeansException ex) {
    
    
		// Explicitly remove instance from singleton cache: It might have been put there
		// eagerly by the creation process, to allow for circular reference resolution.
		// Also remove any beans that received a temporary reference to the bean.
		destroySingleton(beanName);
		throw ex;
	}
});

Isso envolve o conceito de encerramento. A definição de encerramento é: um bloco de código a ser executado e um ambiente de computação que fornece ligação para variáveis ​​livres . Em Java, uma expressão lambda é um encerramento. Uma expressão lambda tem 3 partes:

  1. bloco de código
  2. parâmetro
  3. valor da variável livre

A chamada variável livre é: não um parâmetro, não definido no bloco de código da expressão lambda, não definido em nenhum contexto global, mas uma variável local definida no ambiente em que a expressão lambda é definida. (Neste caso, são as variáveis ​​locais de doCreateBean, nomeadamente beanName, mbd, args)

A expressão lambda deve armazenar o valor da variável livre. Este processo é denominado "a variável livre é capturada pela expressão lambda".
As capturas têm uma limitação importante: nas expressões lambda, você só pode referenciar o valor e não alterar a variável. Caso contrário, a execução simultânea causará insegurança no thread. Além disso, é ilegal fazer referência a uma variável em uma expressão lambda que pode mudar externamente. Portanto, as expressões lambda só podem fazer referência a variáveis ​​​​que são declaradas explícita ou implicitamente como finais.

Isso é muito longe.

O que quero expressar aqui é: Embora ObjectFactory não tenha nenhuma variável de membro e getObject não tenha nenhum parâmetro de entrada, o que passamos aqui é uma expressão lambda. Devido às suas características de fechamento, o Spring pode criar corretamente os beans que precisamos .

createBean e doCreateBean criam objetos

getObject cria objetos através de createBean. O processo principal de createBean é:

  • Preparar informações de definição de Bean (incluindo informações como Escopo)
  • Chame doCreateBean para criar um objeto

(Quando esse aninhamento de camadas terminará?)

O principal processo do doCreateBean é:

  • selecione construtor
  • Instanciando objetos via reflexão
  • Coloque o objeto vazio instanciado no pool de cache de terceiro nível (singletonFactories)
  • Atribuir valores às propriedades de objetos vazios instanciados (injeção de dependência)
  • Execute alguns métodos de inicialização
  • Coloque os objetos criados no pool de cache de primeiro nível (singletonObjects)

Finalmente, depois de chamar camada por camada, encontramos um método que realmente define como criar um bean!
Este processo é a peça lógica mais importante para o problema da dependência circular.Este processo revela porque o Spring pode resolver a dependência circular do setter, mas porque não pode resolver a dependência circular do construtor.

Como o Spring resolve dependências circulares injetadas por setters?

A essência da resolução de dependências circulares do setter é permitir que objetos bean recebam dependências inacabadas sob a premissa de que todos são singleton e essas dependências existem nos caches de segundo e terceiro níveis. Ao criar A, primeiro instancie-o com um construtor sem argumento e coloque o A não atribuído no cache de terceiro nível e, em seguida, execute a atribuição de atributos (injeção de setter em B), porque B não foi criado neste momento, ele será executado o processo de criação, o setter também será injetado em A durante o estágio de atribuição de atributos B. Quando A é obtido por meio do método getBean, verifica-se que A está no cache de terceiro nível e o processo de criação de A não será executado, mas retornado diretamente ao cache de terceiro nível. A atribuição de B é concluída e o B inicializado é retornado para A. Neste ponto, A também é inicializado. Como A e B são singletons, B mantém uma referência e o A anteriormente não atribuído torna-se o A inicializado. O processo simplificado é o seguinte: Como você pode ver, porque o primeiro getBean(A) se coloca no cache de terceiro nível antes da atribuição do atributo, então quando o segundo getBean(A), getSingletonObject O cache pode ser obtido (é claro, porque foi previamente marcado como sendo criado), encerrando o aninhamento infinito. Isso é para resolver a dependência circular.


Insira a descrição da imagem aqui

Por que o Spring não consegue resolver dependências circulares injetadas por construtores?

Se for injeção de construtor, será injetado durante a fase de instanciação, e o método injetado também obterá dependências chamando getBean. No entanto, como B não se coloca no pool de cache antes de chamar getBean, ele não pode obter o cache de terceiro nível quando B chama getBean para obter A, então o processo de criação continuará. Se nenhuma intervenção for realizada, ocorrerá um loop infinito .
Insira a descrição da imagem aqui
Perceber! ! ! A implementação do Spring não será aninhada infinitamente. Leitores interessados ​​​​podem descobrir que, embora o primeiro e o segundo getBean(A) passem pelo processo de criação, seus julgamentos em getSingleton#1 são diferentes. Em outras palavras, o primeiro e o segundo getBean(A) não são exatamente iguais, alguns de seus estados são diferentes! Spring usa esses estados para identificar quais dependências circulares não podem ser resolvidas e lançar uma exceção. Ou seja, o loop infinito desenhado no canto inferior direito da imagem é um método de desenho omitido e não acontecerá de fato.

Como o Spring descobre dependências circulares injetadas por construtores?

De acordo com nosso pensamento, o resultado da dependência circular deveria ser um loop infinito, mas obviamente, quando escrevemos um código de dependência circular, o Spring lançará uma exceção para encerrar o programa

Unsatisfied dependency expressed through constructor parameter 0

Então, onde o Spring lançou essa exceção? Como ele detectou e julgou isso?
Quando ocorre uma dependência circular injetada pelo construtor, A entrará no segundo processo de criação, ou seja, getSingleton será chamado pela segunda vez. Como mencionado acima, quando getSingleton cria um objeto, ele primeiro marcará que o objeto está sendo criado ( singletonsCurrentlyInCreation), vamos dar uma olhada em como ele é implementado em detalhes:

	protected void beforeSingletonCreation(String beanName) {
    
     // 查看是否存在,不存在则添加。如果存在或者添加失败则抛异常(构造器注入循环依赖在此发现)
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
    
    
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}

Como você pode ver, se for constatado que este bean foi marcado como criado, uma exceção será lançada. Quando o segundo getBean(A) for executado no segundo getSingleton, ele inevitavelmente detectará a marca quando o processo for criado pela primeira vez, portanto lançará uma exceção e encerrará o programa.

Como o Spring descobre dependências circulares de beans de múltiplas instâncias?

As duas dependências cíclicas mencionadas acima são todas discutidas com base nos singletons.
Múltiplas instâncias e singletons são na verdade semelhantes. Para a injecção de setter,as dependências circulares podem ser resolvidas,mas para a injecção de construtor,elas não podem ser resolvidas. Mas como múltiplas instâncias não chamam o método getSingleton, naturalmente não há marca. Em outras palavras, não importa quantas vezes um bean de múltiplas instâncias seja criado, beforeSingletonCreation não será chamado, mas o Spring pode realmente detectá-lo e relatar um erro:

Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

Então, onde o Bean de múltiplas instâncias lança uma exceção?
Independentemente de ser uma instância única ou múltiplas instâncias, o mesmo createBean é chamado. A instância única é detectada em getSingleton, que é a camada externa de createBean, então as múltiplas instâncias são naturalmente detectadas na camada externa de createBean, ou seja , o doGetBean é chamado
. Existe um trecho de código antes de getSingleton ou createBean:

if (isPrototypeCurrentlyInCreation(beanName)) {
    
    //如果是多实例bean,并且已经创建过了,就会判断为多实例循环依赖(构造器注入),抛异常
	throw new BeanCurrentlyInCreationException(beanName);
}
-------------------
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
    
    
	Object curVal = this.prototypesCurrentlyInCreation.get();
	return (curVal != null &&
			(curVal.equals(beanName) || (curVal instanceof Set<?> set && set.contains(beanName))));
}

Como o nome sugere, esse código serve para resolver a dependência circular de beans de múltiplas instâncias.
Então, quando essa marcação às vezes é feita? Claro que ainda está no doGetBean

else if (mbd.isPrototype()) {
    
     //多实例创建
	// It's a prototype -> create a new instance.
	Object prototypeInstance = null;
	try {
    
    
		beforePrototypeCreation(beanName);
		prototypeInstance = createBean(beanName, mbd, args);
	}
	finally {
    
    
		afterPrototypeCreation(beanName);
	}
	beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}

A lógica desta parte é muito parecida com a lógica do getSingleton, ou seja, seja uma instância única ou múltiplas instâncias, ele será marcado, a diferença é que o local onde está marcado é diferente.
Insira a descrição da imagem aqui

Cache L3

Você ainda se lembra da lógica de criação de getSingleton#1 escrita acima? De acordo com o processo de criação, primeiro colocamos o objeto no cache de terceiro nível e, em seguida, colocamos o objeto do cache de terceiro nível no cache de segundo nível quando ocorre uma dependência circular. Após a conclusão da inicialização, colocamos o cache de segundo nível no cache de primeiro nível. Parece um pouco difícil entender por que esse objeto é movimentado dessa maneira, ou quais serão as consequências se isso não for feito? Por exemplo, eu uso apenas o cache de primeiro nível e coloco o objeto diretamente nele após a instanciação. Quando ocorre uma dependência circular, ela também pode ser encontrada no cache de primeiro nível. Isso também resolve a dependência circular e economiza muitas etapas . Essas perguntas serão respondidas a seguir. Neste momento, podemos primeiro dar uma olhada em como o cache de terceiro nível é definido no código-fonte:

	/** Cache of singleton objects: bean name to bean instance.一级缓存 */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** Cache of early singleton objects: bean name to bean instance. 二级缓存 */
	private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

	/** Cache of singleton factories: bean name to ObjectFactory. 三级缓存 */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

Ficaremos surpresos ao descobrir que o cache de primeiro e segundo nível é diferente: o que é armazenado no cache de terceiro nível não é Object, mas ObjectFactory. Leitores com boa memória devem lembrar que mencionamos acima que o parâmetro que passamos ao chamar getSingleton#2 também era um ObjectFactory. Na verdade, a memória não é uma coisa ruim neste momento, porque o ObjectFactory mencionado acima quase não tem conexão semântica com o ObjectFactory armazenado no cache de terceiro nível aqui , exceto que eles são da mesma classe e chamar getObject pode criar um objeto fora . Em outras palavras, são dois tipos de fábricas, produzindo dois objetos diferentes. Como ObjectFactory é apenas uma interface funcional, isso só pode significar que a expressão lambda que a implementa possui determinados recursos. Assim como ambas as classes implementam Comparable, pode não haver nenhuma conexão semântica entre elas. Isso só pode significar que todas podem ser comparadas entre si. A seguir está a descrição oficial desta interface:

Define uma fábrica que pode retornar uma instância de Object (possivelmente compartilhada ou independente) quando invocada.
Esta interface é normalmente usada para encapsular uma fábrica genérica que retorna uma nova instância (protótipo) de algum objeto de destino em cada invocação.
Essa interface é semelhante ao FactoryBean, mas as implementações deste último normalmente devem ser definidas como instâncias SPI em um BeanFactory, enquanto as implementações desta classe normalmente devem ser alimentadas como uma API para outros beans (por meio de injeção). Como tal, o método getObject() possui um comportamento diferente de tratamento de exceções.

Simplificando: a classe que implementa esta interface significa que ela pode fornecer uma API para criar objetos para o mundo exterior. Os objetos criados por esta API são múltiplas instâncias, ou seja, quantos objetos serão gerados tantas vezes quanto o fábrica é chamada. Como mencionado acima,quando ocorre uma dependência circular,esta API será chamada para gerar múltiplas instâncias.Isso fará com que os beans no contêiner Spring não sejam singletons? Claro que não, em primeiro lugar porque se a dependência circular puder ser resolvida, esta interface só será chamada uma vez. Se não puder ser resolvido, uma exceção será lançada e o programa terminará antes de chamar esta API pela segunda vez. A segunda razão é que o getSingleton que chama esta API usa verificação dupla para garantir que esta API será chamada apenas uma vez. Finalmente, como a estrutura de dados de cada nível do pool de cache é uma tabela hash, seu recurso Key exclusivo garante que haverá apenas uma instância de beanName.
(Mais uma vez, estou indo longe demais, haha, mas acho que isso deve ficar claro)

O que foi adicionado ao pool de cache L3?

Como mencionado acima, o ObjectFactory que passamos para getSingleton#2 e o ObjectFactory em cache de terceiro nível são duas coisas diferentes. Sabemos que o primeiro chama principalmente um método createBean, então qual é o último?
Dê uma olhada na instrução para adicionar o cache de terceiro nível no código-fonte:

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

Conciso e claro, getEarlyBeanReference é chamado e as variáveis ​​livres beanName, mbd e bean também são capturadas pela expressão lambda. Se o lambda passado para getSingleton#2 acima não refletir as vantagens do fechamento, então esta expressão lambda pode torná-lo mais óbvio desta vez: quando o bloco de código for executado, a variável livre provavelmente já não existe em contexto ou foi alterado. Para realizar corretamente os cálculos que desejamos, é particularmente importante ter um ambiente de cálculo que possa capturar essas variáveis ​​livres. A combinação deste ambiente de cálculo e bloco de código é um fechamento. Em Java é expressão lambda.
Então, o que esse método faz? O código é o seguinte:

	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    
    
		Object exposedObject = bean; // 为了让我们自己实现的后置处理器能增强bean,不直接放bean而是放工厂(lambda表达式)
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    
    
			// 如果有后置增强器就执行 (AutowiredAnnotationBeanPostProcessor 在此提供接口进行增强)
			for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
    
    
				exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
		return exposedObject; // 否则返回原来的bean
	}

Uma série de pós-processadores é chamada aqui.Além de alguns pós-processadores que vêm com o Spring, se habilitarmos o AOP, haverá um AnnotationAwareAspectJAutoProxyCreator adicional aqui para gerar um proxy para o objeto. Quando o getObject do cache de terceiro nível for chamado para obter o objeto, o AOP será executado para gerar o objeto proxy.
Então, quando vamos ligar? Leitores com boa memória provavelmente conseguirão lembrar o que foi mencionado acima. Leitores com memória fraca podem tentar a pesquisa global, o que é realmente útil.
Insira a descrição da imagem aqui
Na verdade, não há muitos lugares onde o Spring usa essa interface. Existem dois lugares principais. Você pode ver que esses dois lugares são quase exatamente iguais, exceto pelos comentários que escrevi. Eles estão até na mesma classe, mas são também mencionado acima., que são duas plantas diferentes.

singletonObject = singletonFactory.getObject();//获取三级缓存 (getEarlyBeanReference)
this.earlySingletonObjects.put(beanName, singletonObject); // 放入二级缓存
this.singletonFactories.remove(beanName); // 移除三级缓存

Embora tenhamos encontrado o local onde foi chamado, ainda não respondemos à pergunta “quando chamá-lo”. Você pode dizer: getObject é chamado ao "colocar o cache de terceiro nível no cache de segundo nível" em getSingleton#1.
Isso é verdade, mas acho que uma resposta melhor é: quando ocorre uma dependência circular, getBean(A) será chamado pela segunda vez.
Esta operação ocorrerá se e somente se isso acontecer, o que provavelmente significa que esta parte do código está preparada para quando isso acontecer.

Cem mil cache L3, por quê?

Quando ocorre uma dependência circular, na segunda vez getBean(A) colocará o cache de terceiro nível de A no pool de cache de segundo nível e retornará esse cache de segundo nível. Porquê regressar à cache de segundo nível? Como ocorre uma dependência circular, B precisa obter uma referência para A. Por que é necessário colocar o cache de terceiro nível no cache de segundo nível? Porque B precisa de uma referência a A em vez de uma referência à fábrica. Então não seria possível armazenar diretamente uma referência no cache de segundo nível (ou mesmo de primeiro nível)? Não, além de retornar uma referência a A, o cache de terceiro nível também realiza uma série de pós-processamento em A. B só quer uma referência a A. Quanto a saber se A realizou o pós-processamento e se a inicialização foi concluída, B não se importa. Não importa como A mude ou como lidar com isso, B só precisa encontrar A. Depois disso pós-processamento, B não consegue lidar com isso? E isso deveria ser resolvido mais tarde, então por que tem que ser feito com antecedência?
O problema está aqui: o requisito de B é referenciar corretamente A. De modo geral, parece não haver problema se o pós-processamento for executado primeiro e depois executado, porque a maior parte do pós-processamento não alterará a referência do objeto. Mas como mencionado antes, se o AOP estiver ativado, o proxy AOP será executado aqui. O proxy é uma referência e o A original é outra referência. Você acha que B deveria manter a referência do proxy neste momento ? Ou que tal uma referência a A? Claro que é uma referência ao agente. Quando ocorre uma dependência circular, quando A é injetado em B, A não será executado até a fase de atribuição, e uma série de pós-processamento será executada durante a fase de inicialização de A. Esses pós-processamento podem gerar objetos proxy. você deseja injetar B para este objeto proxy, você só pode realizar esse pós-processamento antecipadamente e, em seguida, retornar a referência inicializada de A para B. Caso contrário, o Spring não pode garantir que B obtenha a referência correta neste momento.
Se usarmos diretamente o cache de segundo nível (ou mesmo de primeiro nível) aqui, isso significa que todos os beans devem completar o proxy AOP nesta etapa. Isso viola o design do Spring de combinar AOP e o ciclo de vida do Bean: deixe o Bean concluir o proxy na última etapa do ciclo de vida em vez de concluir o proxy imediatamente após a instanciação.

(100000=7)

Por que precisamos de um cache de terceiro nível?

Agora é fácil responder a esta pergunta. Os três caches representam diferentes estados de um objeto Bean:

  • No cache de primeiro nível, significa que o objeto singleton foi completamente criado e pode ser usado diretamente.
  • No cache de segundo nível, o objeto está em estado de exposição inicial neste momento, o que garante que sua referência final seja esta, mas não garante que seus atributos sejam completamente preenchidos e inicializados.
  • No cache de terceiro nível, o objeto está no estágio de pós-instanciação, mas não foi exposto anteriormente e não pode ser exposto posteriormente.Neste momento, não há garantia de que seus atributos sejam completamente preenchidos e a inicialização seja concluída.

Uma coisa a notar é que qualquer objeto será adicionado ao cache de terceiro nível após a instanciação, mesmo se não houver dependência circular, porque até agora o Spring não pode determinar se este Bean e outros Beans estão em um relacionamento de dependência circular. Mas somente quando uma dependência circular realmente ocorrer, o ObjectFactory do cache de terceiro nível será usado para gerar objetos antecipadamente para garantir que outros objetos possam se referir a si mesmos corretamente, caso contrário, apenas uma fábrica será criada e colocada no terceiro nível cache, mas não O objeto será realmente criado por meio desta fábrica e o cache de terceiro nível será removido após a criação.
Se houver um cache de três níveis, o fluxo de execução será assim:
Insira a descrição da imagem aqui
Se não houver cache de três níveis, então o fluxo de execução será assim:
Insira a descrição da imagem aqui
É um compromisso executar o pós-processador para inicialização antecipadamente, e não é necessário para a maioria dos beans, portanto o cache de nível 3 é usado. Ao mesmo tempo, se você usar apenas o cache de primeiro nível, há outra desvantagem: isso trará problemas de segurança de thread em um ambiente multithread.Se um bean que não foi criado for colocado no cache, outros threads poderão obter um bean incompleto. Como resultado, o programa tem resultados inesperados.

É claro que a primavera distinguirá os grãos comuns dos grãos de fábrica por meio de prefixos, de modo que apenas um contêiner possa ser usado para armazenar os dois tipos de grãos. De acordo com esta ideia, podemos realmente usar prefixos para distinguir os três caches, de modo que apenas um cache seja suficiente! Mas o cache de terceiro nível é na verdade um conceito lógico, não um conceito de contêiner. Na verdade, o Spring pode precisar acessar o cache com frequência, e a fusão dos três caches em um contêiner também pode afetar o desempenho do sistema.

O que acontecerá com a inicialização de A no loop?

A primeira coisa a notar é que embora uma série de pós-processadores sejam executados no método getEarlyBeanReference, isso não significa que a inicialização foi concluída. Existem muitos tipos de pós-processadores e apenas o pós-processador do tipo smartInstantiationAware é executado aqui.

static class BeanPostProcessorCache {
    
    
	final List<InstantiationAwareBeanPostProcessor> instantiationAware = new ArrayList<>();
	final List<SmartInstantiationAwareBeanPostProcessor> smartInstantiationAware = new ArrayList<>();
	final List<DestructionAwareBeanPostProcessor> destructionAware = new ArrayList<>();
	final List<MergedBeanDefinitionPostProcessor> mergedDefinition = new ArrayList<>();
}

Existem mais tipos de pós-processadores do que os listados acima. Portanto, mesmo que A incorpore a execução de alguns pós-processadores, isso não significa que a inicialização esteja concluída e sua fase de inicialização não pode ser ignorada.

A já criou um objeto proxy uma vez em getEarlyBeanReference. Depois que B for criado, A também será inicializado. Ele criará outro objeto proxy neste momento? Claro que é impossível criá-lo novamente.
O AOP cria um objeto proxy por meio do método wrapIfNecessary e, durante a inicialização, o AOP executará a seguinte lógica:

Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
    
     // 如果之前已经创建了一次代理了,那么此时一定不相等,则不再进行代理,直接返回bean
	return wrapIfNecessary(bean, beanName, cacheKey); // 通过此方法创建代理对象
}

Se um proxy tiver sido criado antes, haverá uma marca. Se houver uma marca, o método wrapIfNecessary não será mais executado. Retorne diretamente ao Bean original.
Mas o problema surge novamente: depois que A executa a inicialização, ele ainda é um objeto sem proxy, mas o objeto retornado para a função de nível superior será armazenado no cache de primeiro nível.

// .....
exposedObject = initializeBean(beanName, exposedObject, mbd);
// .....
return exposedObject;

O objeto armazenado no cache de primeiro nível não seria um objeto sem proxy? Mas o que está armazenado deve ser um objeto proxy. Após inicializar o Bean, antes de retornar ao ExpoObject (ou seja, entre a segunda omissão e o bom no código acima), existe um julgamento lógico

if (earlySingletonExposure) {
    
    
	// 尝试去拿缓存,但是禁用早期引用,也就说,三级缓存是拿不到的,但此时上面创建的实例也还没有放进一级缓存
	// 循环依赖时A被B从三级放到二级,这时可以拿到,也就是说这里拿到的都是那些被提前引用的Bean。
	Object earlySingletonReference = getSingleton(beanName, false);
	if (earlySingletonReference != null) {
    
    
		// bean 是实例化完时的引用
		// earlySingletonReference 是 二级缓存中的对象,如果被AOP代理了,这个就是代理对象,因为提前执行了AOP,B此时持有的也是这个对象
		// exposedObject 是初始化方法后的对象引用,但是他和bean一般还都是一样的,否则就是初始化的时候改变了引用,此时就会进入下面else if的逻辑当中
		if (exposedObject == bean) {
    
     // 看是否初始化之后引用发生了变化
			exposedObject = earlySingletonReference;
			// 这里的逻辑就是要返回一个对象,如果在getEarlyBeanReference前后bean对象没有被替换(代理),那么就这两个是同一个引用,直接暴露即可,否则返回的是代理对象
		}// 否则就要判断一下是否允许直接注入一个不由容器实例的对象
		else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
    
    
			// .......
		}
	}
}

Como você pode ver, obteremos o objeto para o qual o proxy foi executado antes e então exposedObject = earlySingletonReference;atualizaremos o valor de retorno para que a referência correta seja colocada no cache de primeiro nível. Você pode querer perguntar: o método de inicialização executado por A não se tornaria inválido?Afinal, todo A foi substituído pelo objeto proxy anterior. Não se esqueça, o objeto proxy contém uma referência a A internamente, portanto A é válido quando inicializado.

Resumir

Neste ponto, finalmente terminei de escrever a maioria das perguntas sobre as dependências circulares do Spring. Depois de escrever isso, meu princípio desta parte do Spring fica muito claro, mas não posso garantir que os leitores tenham uma compreensão muito clara após a leitura Em última análise, isso ainda precisa ser lido pessoalmente para experimentar o código. O artigo só pode fornecer algumas idéias e insights. E como a operadora é limitada, na verdade, o código do fragmento não se limita à compreensão do programa, e com certeza vai ficar um pouco tonto, só pode ficar mais claro depois de rastreá-lo algumas vezes. Para o código de rastreamento, embora o IDEA tenha uma pilha de chamadas, ele se perderá rapidamente no código enorme se você continuar abrindo e empurrando a pilha. Portanto, além do rastreamento repetido, acho que você também pode usar um mapa mental para criar uma chama semelhante gráfico Algo que equivale a desenhar um mapa que ajuda a esclarecer o processo de execução do programa. Para o código da parte de dependência circular, fiz esse mapa, você pode consultá-lo:
Insira a descrição da imagem aqui
e
Insira a descrição da imagem aqui
Insira a descrição da imagem aqui
o mapa de AOP acima omite muitos julgamentos e etapas lógicas e é apenas para referência.

Acho que você gosta

Origin blog.csdn.net/weixin_45654405/article/details/127579128
Recomendado
Clasificación