Guia rápido para escopos Spring Bean

Guia rápido para escopos Spring Bean

1. Visão geral

Neste tutorial rápido, você aprenderá sobre os diferentes tipos de escopos de bean na estrutura do Spring.

O escopo de um bean define o ciclo de vida e a visibilidade desse bean nos contextos em que é usado.

A versão mais recente do framework Spring define 6 tipos de escopos:

  • singleton

  • protótipo

  • solicitação

  • sessão

  • inscrição *websocket

Os últimos quatro escopos mencionados request, session, application e websocket estão disponíveis apenas em um aplicativo compatível com a web.

Leitura adicional:

https://www..com/spring-bean [O que é um Spring Bean?]

Uma explicação rápida e prática do que é um Spring Bean.

https://www..com/spring-bean-annotations [Anotações do Spring Bean]

Aprenda como e quando usar as anotações padrão do bean Spring - @Component, @Repository, @Service e @Controller.

===* 2. Escopo Singleton *

Definir um bean com escopo singleton significa que o contêiner cria uma única instância desse bean, e todas as solicitações para esse nome de bean retornarão o mesmo objeto, que é armazenado em cache. Quaisquer modificações no objeto serão refletidas em todas as referências ao bean. Este escopo é o valor padrão se nenhum outro escopo for especificado.

Vamos criar uma entidade Person para exemplificar o conceito de escopos:

public class Person {
    private String name;

   //standard constructor, getters and setters
}

Depois, definimos o bean com o escopo singleton usando a anotação _ @ Scope_:

@Bean
@Scope("singleton")
public Person personSingleton() {
    return new Person();
}

Também podemos usar uma constante em vez do valor String da seguinte maneira:

@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)

Agora, continuamos a escrever um teste que mostra que dois objetos referentes ao mesmo bean terão os mesmos valores, mesmo que apenas um deles mude seu estado, pois ambos estão fazendo referência à mesma instância do bean:

private static final String NAME = "John Smith";

@Test
public void givenSingletonScope_whenSetName_thenEqualNames() {
    ApplicationContext applicationContext =
      new ClassPathXmlApplicationContext("scopes.xml");

    Person personSingletonA = (Person) applicationContext.getBean("personSingleton");
    Person personSingletonB = (Person) applicationContext.getBean("personSingleton");

    personSingletonA.setName(NAME);
    Assert.assertEquals(NAME, personSingletonB.getName());

    ((AbstractApplicationContext) applicationContext).close();
}

O arquivo scopes.xml neste exemplo deve conter as definições xml dos beans usados:

<?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.xsd">

    <bean id="personSingleton" class="org..scopes.Person" scope="singleton"/>
</beans>

===* 3. Escopo do protótipo *

Um bean com escopo prototype retornará uma instância diferente toda vez que for solicitada no contêiner. É definido configurando o valor prototype como a anotação _ @ Scope_ na definição do bean:

@Bean
@Scope("prototype")
public Person personPrototype() {
    return new Person();
}

Também poderíamos usar uma constante, como fizemos no escopo do singleton:

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)

Agora, escreveremos um teste semelhante ao anterior, que mostra que dois objetos que solicitam o mesmo nome de bean com protótipo de escopo terão estados diferentes, pois não estão mais se referindo à mesma instância de bean:

private static final String NAME = "John Smith";
private static final String NAME_OTHER = "Anna Jones";

@Test
public void givenPrototypeScope_whenSetNames_thenDifferentNames() {
    ApplicationContext applicationContext =
      new ClassPathXmlApplicationContext("scopes.xml");

    Person personPrototypeA = (Person) applicationContext.getBean("personPrototype");
    Person personPrototypeB = (Person) applicationContext.getBean("personPrototype");

    personPrototypeA.setName(NAME);
    personPrototypeB.setName(NAME_OTHER);

    Assert.assertEquals(NAME, personPrototypeA.getName());
    Assert.assertEquals(NAME_OTHER, personPrototypeB.getName());

    ((AbstractApplicationContext) applicationContext).close();
}

O arquivo scopes.xml é semelhante ao apresentado na seção anterior ao incluir a definição xml para o bean com o escopo prototype:

<bean id="personPrototype" class="org..scopes.Person" scope="prototype"/>

===* 4. Escopos cientes da Web *

Como mencionado, existem quatro escopos adicionais disponíveis apenas em um contexto de aplicativo com reconhecimento da Web. Estes são usados ​​com menos frequência na prática.

O escopo request cria uma instância de bean para uma única solicitação HTTP, enquanto o escopo session cria para uma Sessão HTTP.

O scope do aplicativo cria a instância do bean para o ciclo de vida de um ServletContext e o scope do websocket o cria para uma sessão WebSocket específica.

Vamos criar uma classe a ser usada para instanciar os beans:

public class HelloMessageGenerator {
    private String message;

   //standard getter and setter
}

====* 4.1 Escopo da solicitação *

Podemos definir o bean com o escopo request usando a anotação _ @ Scope_:

@Bean
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator requestScopedBean() {
    return new HelloMessageGenerator();
}

O atributo proxyMode é necessário porque, no momento da instanciação do contexto do aplicativo da web, não há solicitação ativa. O Spring criará um proxy a ser injetado como uma dependência e instanciará o bean de destino quando for necessário em uma solicitação.

Em seguida, podemos definir um controlador que tenha uma referência injetada ao requestScopedBean. Precisamos acessar a mesma solicitação duas vezes para testar os escopos específicos da Web.

Se exibirmos a mensagem toda vez que a solicitação for executada, podemos ver que o valor é redefinido para nulo, mesmo que posteriormente seja alterado no método. Isso ocorre porque uma instância de bean diferente está sendo retornada para cada solicitação.

@Controller
public class ScopesController {
    @Resource(name = "requestScopedBean")
    HelloMessageGenerator requestScopedBean;

    @RequestMapping("/scopes/request")
    public String getRequestScopeMessage(final Model model) {
        model.addAttribute("previousMessage", requestScopedBean.getMessage());
        requestScopedBean.setMessage("Good morning!");
        model.addAttribute("currentMessage", requestScopedBean.getMessage());
        return "scopesExample";
    }
}

====* 4.2 Escopo da sessão *

Podemos definir o bean com o escopo session de maneira semelhante:

@Bean
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator sessionScopedBean() {
    return new HelloMessageGenerator();
}

Em seguida, definimos um controlador com uma referência ao sessionScopedBean. Novamente, precisamos executar duas solicitações para mostrar que o valor do campo message é o mesmo para a sessão.

Nesse caso, quando a solicitação é feita pela primeira vez, o valor message é null. Mas uma vez, ele é alterado, então esse valor __ é retido para solicitações subsequentes, pois a mesma instância do bean é retornada para toda a sessão.

@Controller
public class ScopesController {
    @Resource(name = "sessionScopedBean")
    HelloMessageGenerator sessionScopedBean;

    @RequestMapping("/scopes/session")
    public String getSessionScopeMessage(final Model model) {
        model.addAttribute("previousMessage", sessionScopedBean.getMessage());
        sessionScopedBean.setMessage("Good afternoon!");
        model.addAttribute("currentMessage", sessionScopedBean.getMessage());
        return "scopesExample";
    }
}

====* 4.3 Escopo do aplicativo *

O application scope cria a instância do bean para o ciclo de vida de um ServletContext.

Isso é semelhante ao escopo singleton, mas há uma diferença muito importante em relação ao escopo do bean.

Quando os beans têm escopo de application, a mesma instância do bean é compartilhada entre vários aplicativos baseados em servlet em execução no mesmo ServletContext, enquanto os beans com escopo singleton têm escopo apenas para um único contexto de aplicativo.

Vamos criar o bean com o escopo application:

@Bean
@Scope(
  value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator applicationScopedBean() {
    return new HelloMessageGenerator();
}

E o controlador que referencia esse bean:

@Controller
public class ScopesController {
    @Resource(name = "applicationScopedBean")
    HelloMessageGenerator applicationScopedBean;

    @RequestMapping("/scopes/application")
    public String getApplicationScopeMessage(final Model model) {
        model.addAttribute("previousMessage", applicationScopedBean.getMessage());
        applicationScopedBean.setMessage("Good afternoon!");
        model.addAttribute("currentMessage", applicationScopedBean.getMessage());
        return "scopesExample";
    }
}

Nesse caso, o valor message, uma vez definido no applicationScopedBean , será retido para todos os pedidos, sessões e até mesmo para um aplicativo de servlet diferente que acessará esse bean, desde que esteja em execução no mesmo ServletContext.

====* 4.4 Escopo WebSocket *

Por fim, vamos criar o bean com websocket scope:

@Bean
@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator websocketScopedBean() {
    return new HelloMessageGenerator();
}

Os beans com escopo WebSocket quando acessados ​​pela primeira vez são armazenados nos atributos da sessão WebSocket. A mesma instância do bean é retornada sempre que esse bean é acessado durante toda a sessão WebSocket.

Também podemos dizer que ele exibe comportamento singleton, mas limitado a apenas uma sessão WebSocket.

===* 5. Conclusão*

Demonstramos diferentes escopos de feijão fornecidos pelo Spring e quais são os usos pretendidos.

A implementação deste tutorial pode ser encontrada em o projeto GitHub - este é um projeto baseado no Eclipse, portanto, deve ser fácil importar e executar como está.