Que oportunidades a Spring oferece para personalizar seu comportamento?

Olá a todos. Em contato com Vladislav Rodin. Atualmente, sou o chefe do curso de arquiteto de carga alta da OTUS e também ensino cursos de arquitetura de software.

Além de ensinar, também estou escrevendo material com direitos autorais para o blog OTUS no Haber e quero coincidir com o artigo de hoje para lançar o curso "Desenvolvedor no Spring Framework" , que agora está aberto para recrutamento.




Introdução


Do ponto de vista do leitor, o código do aplicativo usando Spring parece bastante simples: alguns beans são declarados, as classes são marcadas com anotações e, em seguida, os beans são injetados quando necessário, tudo funciona bem. Mas um leitor curioso tem uma pergunta: “Como isso funciona? O que está acontecendo?". Neste artigo, tentaremos responder a essa pergunta, mas não para satisfazer a curiosidade ociosa.

A estrutura Spring é conhecida por ser flexível o suficiente e fornece opções para personalizar o comportamento da estrutura. O Spring também possui várias regras bastante interessantes para aplicar determinadas anotações (por exemplo, Transacional). Para entender o significado dessas regras, para poder derivá-las e também para entender o que e como você pode configurar no Spring, você precisa entender vários princípios de operação do que está no Spring sob o capô. Como você sabe, o conhecimento de vários princípios isenta o conhecimento de muitos fatos. Sugiro que você se familiarize com esses princípios abaixo se, é claro, ainda não os conhece.

Configurações de leitura


No início, você precisa analisar as configurações que estão no seu aplicativo. Como existem vários tipos de configurações (configurações xml, groovy, java, configuração baseada em anotações), métodos diferentes são usados ​​para lê-las. De uma forma ou de outra, é coletado um mapa no formato Map <String, BeanDefinition>, no qual os nomes dos beans são designados às suas definições de bean. Os objetos da classe BeanDefinition são meta-informações sobre beans e contêm a identificação, o nome, a classe, os métodos de destruição e init do bean.

Exemplos de classes envolvidas nesse processo: GroovyBeanDefinitionReader, XmlBeanDefinitionReader, AnnotatedBeanDefinitionReader, implementando a interface BeanDefinitionReader .

Configurando Definições de Beand


Portanto, temos descrições de beans, mas eles não existem, eles ainda não foram criados. Antes de criar beans, o Spring oferece a capacidade de personalizar as BeanDefinitions resultantes. Para esses propósitos, a interface BeanFactoryPostProcessor é usada . Se parece com isso:

public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

O parâmetro method dessa interface permite usar seu próprio método getBeanDefinitionNames para obter os nomes pelos quais você pode obter o BeanDefinitions no mapa e editá-los.

Por que isso pode ser necessário? Suponha que alguns beans exijam detalhes para se conectar a qualquer sistema externo, como um banco de dados. Queremos que os beans já sejam criados com os detalhes, mas os próprios detalhes são armazenados no arquivo de propriedades. Podemos aplicar um dos BeanFactoryPostProcessors padrão - PropertySourcesPlaceholderConfigurer, que substituirá a propriedade name no BeanDefinition pelo valor real armazenado no arquivo de propriedades. Ou seja, ele realmente substituirá Valor ("usuário") por Valor ("raiz") no BeanDefinion. Para que isso funcione, é claro que o PropertySourcesPlaceholderConfigurer precisa estar conectado. Mas isso não se limita a isso, você pode registrar seu BeanFactoryPostProcessor, para implementar qualquer lógica necessária para processar as Definições de Bean.

Criando Beans


Nesse estágio, temos um mapa que possui os nomes dos beans localizados pelas chaves e as BeanDefinitions configuradas pelos valores. Agora você precisa criar esses beans. É isso que o BeanFactory faz . Mas aqui você pode adicionar personalização escrevendo e registrando seu FactoryBean . FactoryBean é uma interface do formulário:

public interface FactoryBean {
    T getObject() throws Exception;
    Class<?> getObjectType();
    boolean isSingleton();
}

Portanto, um BeanFactory cria um próprio feijão se não houver FactoryBean correspondente à classe de bean ou solicita ao FactoryBean que crie esse bean. Há uma pequena nuance: se o escopo do bean for único, então o bean será criado nesse estágio, se protótipo, toda vez que esse bean for necessário, ele será solicitado ao BeanFactory.

Como resultado, obtemos novamente um mapa, mas já é um pouco diferente: as chaves contêm os nomes dos beans e os valores dos próprios beans. Mas isso é verdade apenas para singletones.

Configurando Beans


Agora vem a fase mais interessante. Temos um mapa contendo os beans criados, mas esses beans ainda não foram configurados. Ou seja, não processamos anotações que definem o estado do bean: Ligado automaticamente, Valor. Também não processamos anotações que alteram o comportamento do bean: Transacional, Assíncrono. BeanPostProcessors , que são novamente implementações da interface correspondente, nos permite resolver este problema :

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

Vemos 2 métodos com nomes assustadores, mas abrangentes. Ambos os métodos usam um bean como entrada, a partir do qual você pode perguntar qual classe é e depois usar a API do Reflection para processar anotações. Retorne os métodos do bean, possivelmente substituídos pelo proxy.

Para cada bean antes de colocá-lo no contexto, acontece o seguinte: os métodos postProcessBeforeInitialization são acionados para todos os BeanPostProcessors, o método init é acionado e os métodos postProcessAfterInitialization também são acionados para todos os BeanPostProcessors.

Esses dois métodos têm semânticas diferentes: postProcessBeforeInitialization processa anotações de estado, postProcessAfterInitialization processa o comportamento, porque o proxy é usado para processar o comportamento, o que pode levar à perda de anotações. É por isso que o comportamento muda em último lugar.

Onde está a personalização? Podemos escrever nossa anotação, BeanPostProcessor, e o Spring a processará. No entanto, para que o BeanPostProcessor funcione, ele também deve ser registrado como um bean.

Por exemplo, para incorporar um número aleatório em um campo, criamos uma anotação InjectRandomInt (pendurada nos campos), criamos e registramos uma InjectRandomIntBeanPostProcessor, no primeiro método em que processamos a anotação criada e, no segundo método, simplesmente retornamos o bean de entrada.

Para criar um perfil dos beans, crie uma anotação de Perfil que se transmita aos métodos, crie e registre um ProfileBeanPostProcessor, no primeiro método do qual retornamos o bean de entrada e, no segundo método, retornamos um proxy que agrupa a chamada ao método original com log de recorte e tempo de execução.



Saiba mais sobre o curso



All Articles