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 [Leia mais] →
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.
https://www..com/spring-bean-annotations [Leia mais] →
===* 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á.