Escopo personalizado na primavera

Escopo personalizado na primavera

1. Visão geral

Pronto para usar, o Spring oferece dois escopos de bean padrão (“singleton”e“prototype”) que podem ser usados ​​em qualquer aplicativo Spring, além de três escopos de bean adicionais (“request”,“session” e“globalSession”) para uso apenas em aplicativos compatíveis com a web.

Os escopos de bean padrão não podem ser substituídos e geralmente é considerado uma prática ruim substituir os escopos compatíveis com a web. No entanto, você pode ter um aplicativo que exija recursos diferentes ou adicionais daqueles encontrados nos escopos fornecidos.

Por exemplo, se você estiver desenvolvendo um sistema de multilocação, convém fornecer uma instância separada de um bean em particular ou conjunto de beans para cada inquilino. O Spring fornece um mecanismo para criar escopos personalizados para cenários como esse.

Neste tutorial rápido, demonstraremoshow to create, register, and use a custom scope in a Spring application.

2. Criação de uma classe de escopo personalizado

Para criar um escopo personalizado,we must implement the Scope interface. Ao fazer isso, devemos tambémensure that the implementation is thread-safe porque os escopos podem ser usados ​​por várias fábricas de bean ao mesmo tempo.

2.1. Gerenciando os objetos com escopo e retornos de chamada

Uma das primeiras coisas a considerar ao implementar uma classeScope personalizada é como você armazenará e gerenciará os objetos com escopo definido e os retornos de chamada de destruição. Isso pode ser feito usando um mapa ou uma classe dedicada, por exemplo.

Neste artigo, faremos isso de maneira thread-safe usando mapas sincronizados.

Vamos começar a definir nossa classe de escopo personalizado:

public class TenantScope implements Scope {
    private Map scopedObjects
      = Collections.synchronizedMap(new HashMap());
    private Map destructionCallbacks
      = Collections.synchronizedMap(new HashMap());
...
}

2.2. Recuperando um Objeto do Escopo

Para recuperar um objeto por nome de nosso escopo, vamos implementar o métodogetObject. Como afirma o JavaDoc,if the named object does not exist in the scope, this method must create and return a new object.

Em nossa implementação, verificamos se o objeto nomeado está em nosso mapa. Se for, nós o retornamos e se não, usamos oObjectFactory para criar um novo objeto, adicioná-lo ao nosso mapa e retorná-lo:

@Override
public Object get(String name, ObjectFactory objectFactory) {
    if(!scopedObjects.containsKey(name)) {
        scopedObjects.put(name, objectFactory.getObject());
    }
    return scopedObjects.get(name);
}

Dos cinco métodos definidos pela interfaceScope,only the get method is required to have a full implementation do comportamento descrito. Os outros quatro métodos são opcionais e podem lançarUnsupportedOperationException se não precisarem ou não puderem oferecer suporte a uma funcionalidade.

2.3. Registrando um retorno de chamada de destruição

Devemos também implementar o métodoregisterDestructionCallback. Este método fornece um retorno de chamada que deve ser executado quando o objeto nomeado for destruído ou se o próprio escopo for destruído pelo aplicativo:

@Override
public void registerDestructionCallback(String name, Runnable callback) {
    destructionCallbacks.put(name, callback);
}

2.4. Removendo um objeto do escopo

A seguir, vamos implementar o métodoremove, que remove o objeto nomeado do escopo e também remove seu retorno de chamada de destruição registrado, retornando o objeto removido:

@Override
public Object remove(String name) {
    destructionCallbacks.remove(name);
    return scopedObjects.remove(name);
}

Observe queit is the caller’s responsibility to actually execute the callback and destroy the removed object.

2.5. Obtendo o ID da conversa

Agora, vamos implementar o métodogetConversationId. Se seu escopo suportar o conceito de um ID de conversa, você o retornará aqui. Caso contrário, a convenção é retornarnull:

@Override
public String getConversationId() {
    return "tenant";
}

2.6. Resolvendo Objetos Contextuais

Finalmente, vamos implementar o métodoresolveContextualObject. Se o seu escopo oferecer suporte a vários objetos contextuais, você deve associar cada um a um valor de chave e retornar o objeto correspondente ao parâmetrokey fornecido. Caso contrário, a convenção é retornarnull:

@Override
public Object resolveContextualObject(String key) {
    return null;
}

3. Registrando o escopo personalizado

Para tornar o contêiner Spring ciente de seu novo escopo, você precisaregister it through the registerScope method on a ConfigurableBeanFactory instance. Vamos dar uma olhada na definição deste método:

void registerScope(String scopeName, Scope scope);

O primeiro parâmetro,scopeName, é usado para identificar / especificar um escopo por seu nome exclusivo. O segundo parâmetro,scope, é uma instância real da implementaçãoScope personalizada que você deseja registrar e usar.

Vamos criar umBeanFactoryPostProcessor personalizado e registrar nosso escopo personalizado usando umConfigurableListableBeanFactory:

public class TenantBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
        factory.registerScope("tenant", new TenantScope());
    }
}

Agora, vamos escrever uma classe de configuração Spring que carregue nossa implementaçãoBeanFactoryPostProcessor:

@Configuration
public class TenantScopeConfig {

    @Bean
    public static BeanFactoryPostProcessor beanFactoryPostProcessor() {
        return new TenantBeanFactoryPostProcessor();
    }
}

4. Usando o escopo personalizado

Agora que registramos nosso escopo personalizado, podemos aplicá-lo a qualquer um de nossos beans, assim como faríamos com qualquer outro bean que use um escopo diferente desingleton (o escopo padrão) - usando o@Scope anotação e especificando nosso escopo personalizado por nome.

Vamos criar uma classeTenantBean simples - declararemos beans com escopo de locatário desse tipo em um momento:

public class TenantBean {

    private final String name;

    public TenantBean(String name) {
        this.name = name;
    }

    public void sayHello() {
        System.out.println(
          String.format("Hello from %s of type %s",
          this.name,
          this.getClass().getName()));
    }
}

Observe que não usamos as anotações de nível de classe@Componente@Scope nesta classe.

Agora, vamos definir alguns beans com escopo de locatário em uma classe de configuração:

@Configuration
public class TenantBeansConfig {

    @Scope(scopeName = "tenant")
    @Bean
    public TenantBean foo() {
        return new TenantBean("foo");
    }

    @Scope(scopeName = "tenant")
    @Bean
    public TenantBean bar() {
        return new TenantBean("bar");
    }
}

5. Testando o escopo personalizado

Vamos escrever um teste para exercitar nossa configuração de escopo personalizado carregando umApplicationContext, registrando nossas classesConfiguration e recuperando nossos beans com escopo de locatário:

@Test
public final void whenRegisterScopeAndBeans_thenContextContainsFooAndBar() {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    try{
        ctx.register(TenantScopeConfig.class);
        ctx.register(TenantBeansConfig.class);
        ctx.refresh();

        TenantBean foo = (TenantBean) ctx.getBean("foo", TenantBean.class);
        foo.sayHello();
        TenantBean bar = (TenantBean) ctx.getBean("bar", TenantBean.class);
        bar.sayHello();
        Map foos = ctx.getBeansOfType(TenantBean.class);

        assertThat(foo, not(equalTo(bar)));
        assertThat(foos.size(), equalTo(2));
        assertTrue(foos.containsValue(foo));
        assertTrue(foos.containsValue(bar));

        BeanDefinition fooDefinition = ctx.getBeanDefinition("foo");
        BeanDefinition barDefinition = ctx.getBeanDefinition("bar");

        assertThat(fooDefinition.getScope(), equalTo("tenant"));
        assertThat(barDefinition.getScope(), equalTo("tenant"));
    }
    finally {
        ctx.close();
    }
}

E a saída do nosso teste é:

Hello from foo of type org.example.customscope.TenantBean
Hello from bar of type org.example.customscope.TenantBean

6. Conclusão

Neste tutorial rápido, mostramos como definir, registrar e usar um escopo personalizado no Spring.

Você pode ler mais sobre escopos personalizados emSpring Framework Reference. Você também pode dar uma olhada nas implementações do Spring de várias classesScope emSpring Framework repository on GitHub.

Como de costume, você pode encontrar os exemplos de código usados ​​neste artigo emGitHub project.