Ambiti dei bean di Spring

Spring Bean Scopes ci permette di avere un controllo più granulare sulla creazione delle istanze dei bean. A volte vogliamo creare un’istanza di bean come singleton, ma in altri casi potremmo volerla creare ad ogni richiesta o una volta per sessione.

Spring Bean Scopes

Ci sono cinque tipi di spring bean scopes:

  1. singleton – verrà creata solo un’istanza del spring bean per il contenitore spring. Questo è l’ambito predefinito del spring bean. Quando si utilizza questo ambito, assicurarsi che il bean non abbia variabili di istanza condivise altrimenti potrebbe portare a problemi di inconsistenza dei dati.
  2. prototype – Verrà creata una nuova istanza ogni volta che il bean viene richiesto dal contenitore spring.
  3. request – Questo è lo stesso dell’ambito prototype, tuttavia è pensato per essere utilizzato per applicazioni web. Verrà creata una nuova istanza del bean per ogni richiesta HTTP.
  4. session – Un nuovo bean sarà creato per ogni sessione HTTP dal contenitore.
  5. global-session – Questo viene utilizzato per creare bean di sessione globali per le applicazioni Portlet.

Singleton e Prototipo di Fagiolo Spring

Le scope di fagioli singleton e prototipo possono essere utilizzate nelle app Spring standalone. Vediamo come possiamo configurare facilmente questi scope utilizzando l’annotazione @Scope. Supponiamo di avere una classe di bean Java.

package com.journaldev.spring;

public class MyBean {

	public MyBean() {
		System.out.println("MyBean instance created");
	}

}

Definiamo la classe di configurazione Spring dove definiremo il metodo per ottenere un’istanza di MyBean dal contenitore Spring.

package com.journaldev.spring;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class MyConfiguration {
	
	@Bean
	@Scope(value="singleton")
    public MyBean myBean() {
		return new MyBean();
	}
	
}

Nota che singleton è lo scope predefinito, quindi possiamo rimuovere @Scope(value="singleton") dalla definizione del bean sopra. Ora creiamo un metodo principale e testiamo lo scope singleton.

package com.journaldev.spring;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MySpringApp {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
		ctx.register(MyConfiguration.class);
		ctx.refresh();

		 MyBean mb1 = ctx.getBean(MyBean.class);
		 System.out.println(mb1.hashCode());

		 MyBean mb2 = ctx.getBean(MyBean.class);
		 System.out.println(mb2.hashCode());

		ctx.close();
	}

}

Quando il programma sopra viene eseguito, otterremo un output come segue.

MyBean instance created
867988177
867988177

Nota che entrambe le istanze di MyBean hanno lo stesso hashcode e il costruttore viene chiamato una sola volta, il che significa che il contenitore Spring restituisce sempre la stessa istanza di MyBean. Ora cambiamo lo scope in prototipo.

@Bean
@Scope(value="prototype")
public MyBean myBean() {
	return new MyBean();
}

Questa volta otterremo il seguente output quando viene eseguito il metodo principale.

MyBean instance created
867988177
MyBean instance created
443934570

È chiaro che l’istanza di MyBean viene creata ogni volta che viene richiesta dal contenitore Spring. Ora cambiamo lo scope in richiesta.

@Bean
@Scope(value="request")
public MyBean myBean() {
	return new MyBean();
}

In questo caso, otterremo la seguente eccezione.

Exception in thread "main" java.lang.IllegalStateException: No Scope registered for scope name 'request'
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:347)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:224)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1015)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:339)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:334)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1107)
	at com.journaldev.spring.MySpringApp.main(MySpringApp.java:12)

È perché gli ambiti di richiesta, sessione e sessione-globale non sono disponibili per le applicazioni standalone.

Ambito di richiesta e sessione del fagiolo Spring

Per l’esempio di ambito di richiesta e sessione del fagiolo Spring, creeremo un’applicazione web Spring Boot. Crea un progetto starter di Spring Boot e scegli “web” in modo da poterlo eseguire come un’applicazione web. Il nostro progetto finale avrà un’aspetto simile all’immagine sottostante. ServletInitializer e SpringBootMvcApplication sono classi spring boot generate automaticamente. Non è necessario apportare alcuna modifica lì. Ecco il mio file pom.xml, dai un’occhiata alle dipendenze per la nostra applicazione. Il tuo file pom.xml potrebbe essere leggermente diverso in base alla versione di Eclipse che stai utilizzando.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.journaldev.spring</groupId>
	<artifactId>Spring-Boot-MVC</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>

	<name>Spring-Boot-MVC</name>
	<description>Spring Beans Scope MVC</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>10</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

Creiamo alcuni componenti spring e li configuriamo come fagioli spring nel contenitore spring con ambito come richiesta e sessione.

Ambito di richiesta del fagiolo Spring

package com.journaldev.spring;

import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class DataRequestScope {

	private String name = "Request Scope";
	
	public DataRequestScope() {
		System.out.println("DataRequestScope Constructor Called");
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

Spring Bean Session Scope

package com.journaldev.spring;

import org.springframework.context.annotation.Scope;
import java.time.LocalDateTime;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;

@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class DataSessionScope {

	private String name = "Session Scope";
	
	public DataSessionScope() {
		System.out.println("DataSessionScope Constructor Called at "+LocalDateTime.now());
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

Spring Componente

Ora creiamo un componente Spring e utilizziamo Spring per configurare automaticamente i bean sopra.

package com.journaldev.spring;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Customer {

	@Autowired
	private DataRequestScope dataRequestScope;
	
	@Autowired
	private DataSessionScope dataSessionScope;

	public DataRequestScope getDataRequestScope() {
		return dataRequestScope;
	}

	public void setDataRequestScope(DataRequestScope dataRequestScope) {
		this.dataRequestScope = dataRequestScope;
	}

	public DataSessionScope getDataSessionScope() {
		return dataSessionScope;
	}

	public void setDataSessionScope(DataSessionScope dataSessionScope) {
		this.dataSessionScope = dataSessionScope;
	}


}

Spring Rest Controller

Infine, creiamo una classe RestController e configuriamo alcuni endpoint API per scopi di testing.

package com.journaldev.spring;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloData {

	@Autowired
	private Customer customer;
	
	@RequestMapping("/nameRS")
	public String helloRS() {
		return customer.getDataRequestScope().getName();
	}
	
	@RequestMapping("/nameSSUpdated")
	public String helloSSUpdated() {
		customer.getDataSessionScope().setName("Session Scope Updated");
		return customer.getDataSessionScope().getName();
	}
	
	@RequestMapping("/nameSS")
	public String helloSS() {
		return customer.getDataSessionScope().getName();
	}
}

Configurazione del Timeout di Sessione di Spring Boot

Infine, dobbiamo configurare le variabili di timeout della sessione di Spring Boot, aggiungere le seguenti proprietà in src/main/resources/application.properties.

server.session.cookie.max-age= 1
server.session.timeout= 1

Ora i nostri bean Spring con ambito di sessione verranno invalidati in un minuto. Esegui semplicemente la classe SpringBootMvcApplication come applicazione Spring Boot. Dovresti vedere l’output sottostante per la configurazione dei nostri endpoint.

2018-05-23 17:02:25.830  INFO 6921 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/nameRS]}" onto public java.lang.String com.journaldev.spring.HelloData.helloRS()
2018-05-23 17:02:25.831  INFO 6921 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/nameSSUpdated]}" onto public java.lang.String com.journaldev.spring.HelloData.helloSSUpdated()
2018-05-23 17:02:25.832  INFO 6921 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/nameSS]}" onto public java.lang.String com.journaldev.spring.HelloData.helloSS()

Test di Ambito della Richiesta Spring Bean

Apri qualsiasi browser e vai all’URL https://localhost:8080/nameRS e controlla l’output della console. Dovresti vedere Costruttore DataRequestScope Chiamato stampato ad ogni richiesta.

Test di Ambito della Sessione Spring Bean

Vai su https://localhost:8080/nameSS e otterrai il seguente output. Ora vai su https://localhost:8080/nameSSUpdated in modo che il valore del nome DataSessionScope sia aggiornato a Session Scope Updated. Ora torna di nuovo su https://localhost:8080/nameSS e dovresti vedere il valore aggiornato. A questo punto, dovresti vedere DataSessionScope Constructor Called at XXX solo una volta nell’output della console. Ora aspetta per 1 minuto in modo che il nostro bean con ambito di sessione venga invalidato. Poi torna di nuovo su https://localhost:8080/nameSS e dovresti vedere l’output originale. Inoltre, controlla il messaggio della console per la creazione di DataSessionScope nuovamente da parte del contenitore. Questo è tutto per il tutorial sugli ambiti dei bean di Spring.

Puoi scaricare il progetto Spring Beans Scope Spring Boot dal nostro Repository GitHub.

Source:
https://www.digitalocean.com/community/tutorials/spring-bean-scopes