Spring framework 5 : Uso de los scopes (prototype y singleton) utilizando la anotación @Scope


El scope de un bean define su ciclo de vida, en los posts anteriores hemos creado beans pero no hemos definido su scope, al hacer esto Spring le asigna uno por defecto, a continuación se muestran los scopes disponibles:

ScopeDescripción
SingletonCrea una sola instancia del bean por contenedor de Spring
PrototypeCrea una nueva instancia cada vez que se solicita
RequestCrea una nueva instancia por cada petición HTTP,
solo se puede utilizar en una aplicación web
SessionCrea una nueva instancia por cada sesión HTTP
ApplicationCrea una nueva instancia por cada ServletContext

En este post nos enfocaremos en los scopes Singleton y Prototype.

Scope singleton

El scope singleton es el scope por defecto en spring, veamos el siguiente bean:

import org.springframework.stereotype.Component;

/**
 * @author raidentrance
 *
 */
@Component
public class SingletonScopeBean {
}

Ahora veamos la siguiente clase aplicación:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import com.devs4j.scopes.SingletonScopeBean;

@SpringBootApplication
public class SpringExampleApplication {

	public static void main(String[] args) {
		ConfigurableApplicationContext applicationContext = SpringApplication.run(SpringExampleApplication.class, args);

		SingletonScopeBean singletonBean = applicationContext.getBean(SingletonScopeBean.class);

		SingletonScopeBean secondSingletonBean = applicationContext.getBean(SingletonScopeBean.class);

		System.out.println(singletonBean.equals(secondSingletonBean));
	}

}

La salida será la siguiente:

true

Podemos ver que tenemos 2 beans singletonBean y secondSingletonBean y al llamar el método equals la salida es true, indicando que es el mismo objeto.

Scope prototype

El scope prototype indica que se creará un bean nuevo cada que se mande llamar, por esto re utilizaremos el bean anterior y solo cambiaremos su scope como se muestra a continuación:

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

@Component
@Scope("prototype")
public class PrototypeScopeBean {

}

Re utilizaremos la clase aplicación:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import com.devs4j.scopes.PrototypeScopeBean;

@SpringBootApplication
public class SpringExampleApplication {

	public static void main(String[] args) {
		ConfigurableApplicationContext applicationContext = SpringApplication.run(SpringExampleApplication.class, args);

		PrototypeScopeBean prototypeBean = applicationContext.getBean(PrototypeScopeBean.class);

		PrototypeScopeBean secondPrototypeBean = applicationContext.getBean(PrototypeScopeBean.class);

		System.out.println(prototypeBean.equals(secondPrototypeBean));
	}

}

La salida será la siguiente:

false

Esto se debe a que cada vez que se mande llamar el método getBean se creará un bean nuevo, es por esto que al mandar llamar al método equals el resultado será false.

Para estar al pendiente sobre nuestro contenido nuevo síguenos en nuestras redes sociales https://www.facebook.com/devs4j/ y https://twitter.com/devs4j.

Autor: Alejandro Agapito Bautista
Twitter: @raidentrance
Contacto:raidentrance@gmail.com

Spring framework 5 : Uso de la anotación @Primary


En el ejemplo anterior vimos como utilizar la anotación @Qualifier y como nos ayuda cuando tenemos multiples implementaciones de una interfaz. El problema de esa solución es cuando se utiliza @Autowired sin especificar el qualifier dado que Spring falla con el siguiente error:

Field userService in com.devs4j.qualifier.UserController required a single bean, but 2 were found

Esto se resuelve fácilmente utilizando un qualifier o utilizando la anotación @Primary, esta anotación nos permite indicar el bean default a inyectar en caso de que no se especifique ningún qualifier, veamos el siguiente ejemplo:

UserService.java


/**
 * @author raidentrance
 *
 */
public interface UserService {

	public boolean authenticate(String username, String password);
}

UserServiceDatabaseImpl.java


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;

/**
 * @author raidentrance
 *
 */
@Service
@Primary
public class UserServiceDatabaseImpl implements UserService {

	private static Logger log = LoggerFactory.getLogger(UserServiceDatabaseImpl.class);
	

	@Override
	public boolean authenticate(String username, String password) {
		log.info("authenticating against the database");
		return false;
	}

}

UserServiceActiveDirectoryImpl.java


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

/**
 * @author raidentrance
 *
 */
@Service
public class UserServiceActiveDirectoryImpl implements UserService {

	private static Logger log = LoggerFactory.getLogger(UserServiceActiveDirectoryImpl.class);

	@Override
	public boolean authenticate(String username, String password) {
		log.info("authenticating against the active directory ");
		return false;
	}

}

Como podemos ver tenemos las siguientes clases:

  • UserService : Interfaz que cuenta con 2 implementaciones
  • UserServiceDatabaseImpl : Implementación de UserService que utiliza la anotación @Primary
  • UserServiceActiveDirectoryImpl : Implementación de UserService

Una vez que definimos nuestra interfaz y sus implementaciones el siguiente paso será utilizarlo en otro bean, veamos el bean UserController :

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

/**
 * @author raidentrance
 *
 */
@Controller
public class UserController {
	
	@Autowired
	private  UserService userService;
	
	public boolean authenticate(String username, String password) {
		return userService.authenticate(username, password);
	}
}

Como podemos ver es posible inyectar UserService sin especificar ningún qualifier sin recibir ningún error, esto es gracias a que definimos la anotación @Primary en la clase UserServiceDatabaseImpl.

Para estar al pendiente sobre nuestro contenido nuevo síguenos en nuestras redes sociales  https://www.facebook.com/devs4j/ y https://twitter.com/devs4j.

Autor: Alejandro Agapito Bautista
Twitter: @raidentrance
Contacto:raidentrance@gmail.com

Spring framework 5 : Dependency Injection utilizando Spring


Una vez que entendimos como funciona dependency injection e inversion of control el siguiente paso es aprender a utilizarlo con Spring framework.

Crear un Service de ejemplo

El primer paso será crear un service de ejemplo, a continuación se muestra la interfaz y su implementación:

MathService.java

/**
 * 
 * @author raidentrance
 *
 */
public interface MathService {
		
	/**
	 * Receives a set of numbers and return the sum
	 * @param values
	 * @return
	 */
	double sum(double... values);

}

MathServiceImpl.java


import org.springframework.stereotype.Service;

/**
 * @author raidentrance
 *
 */
@Service
public class MathServiceImpl implements MathService {

	@Override
	public double sum(double... values) {
		double sum = 0.0;
		for (double value : values) {
			sum += value;
		}
		return sum;
	}
}

Como vemos la clase MathServiceImpl esta marcada con la anotación @Service, esta nos permitirá indicarle a spring que debe crear una instancia de esta y mantenerla dentro de su contexto.

Accediendo al objeto creado

Una vez que creamos nuestras clases MathService y MathServiceImpl el siguiente paso será utilizarlas en nuestro código, para esto haremos lo siguiente dentro de nuestra clase de aplicación:


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import com.devs4j.service.MathService;
import com.devs4j.service.MathServiceImpl;

@SpringBootApplication
public class Devs4jSpringCoreApplication {

	public static void main(String[] args) {
		ConfigurableApplicationContext applicationContext = SpringApplication.run(Devs4jSpringCoreApplication.class,
				args);
		/**
		 * Get bean by name
		 */
		MathService bean = (MathService) applicationContext.getBean("mathServiceImpl");
		System.out.println(bean.sum(1.1, 2.3, 4.4));

		/**
		 * Get bean by type
		 */
		MathService bean2 = applicationContext.getBean(MathServiceImpl.class);
		System.out.println(bean2.sum(1, 2.3, 4));

		/**
		 * Get bean by name and type
		 */
		MathService bean3 = applicationContext.getBean("mathServiceImpl", MathServiceImpl.class);
		System.out.println(bean3.sum(1, 2.3, 4));
	}

}

Como podemos ver al iniciar la aplicación Spring framework devolverá un objeto de tipo ConfigurableApplicationContext el cual nos permitirá acceder a los beans que existen dentro del contexto de Spring, para hacerlo podremos alguno de los siguientes métodos:

  • getBean(String name) : Devuelve el objeto que tenga el nombre que se pasa como parámetro, por default el nombre del bean será el mismo al de la clase pero iniciando con minúsculas, si se desea cambiar se puede poner el nuevo nombre dentro de la anotación @Service(“nuevoNombreDelBean”).
  • getBean(Class<T> requiredType) : Devuelve un objeto del tipo que se esta solicitando.
  • getBean(String name, Class<T> requiredType) :Devuelve un objeto del tipo que se esta solicitando con el nombre que se está solicitando.

Algunas de las siguientes excepciones se pueden generar al utilizar los métodos anteriores:

  • NoUniqueBeanDefinitionException : En caso de que existan más de un bean del mismo tipo.
  • NoSuchBeanDefinitionException : En caso de que no se encuentre el bean que se está solicitando.
  • BeansException : En caso de que no se pueda crear el bean solicitado.
  • BeanNotOfRequiredTypeException : En caso de que el bean con el nombre solicitado no sea del tipo especificado.

Inyectando beans en otros beans

Una vez que ya sabemos como agregar un bean al contexto de spring y como obtenerlo, el siguiente paso es como hacer dependencias entre ellos, existen 3 formas de hacerlo, veamos una por una inyectando el servicio MathService que creamos previamente en la siguiente interfaz.

/**
 * 
 * @author raidentrance
 *
 */
public interface ComplexCalculatorService {

	/**
	 * Calculates the avergae based on a set of double values
	 * 
	 * @param values
	 * @return
	 */
	double average(double... values);

}

Inyección por constructor

La primera forma que veremos será inyección por constructor:


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

/**
 * @author raidentrance
 *
 */
@Service
public class ComplexCalculatorServiceImpl implements ComplexCalculatorService {

	private MathService mathService;

	@Autowired
	public ComplexCalculatorServiceImpl(MathService mathService) {
		this.mathService = mathService;
	}

	@Override
	public double average(double... values) {
		double result = mathService.sum(values);
		return result / values.length;
	}
}

De acuerdo a lo anterior podemos notar que:

  • La clase en la que inyectaremos el service se debe encontrar en el contexto de spring, por esto ComplexCalculatorServiceImpl tiene la anotación @Service
  • La inyección de dependencias se hace a través del constructor, es por esto que le colocamos la anotación @Autowired en el

Inyección por setter

La siguiente forma de hacerlo será inyección por setter:

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

/**
 * @author raidentrance
 *
 */
@Service
public class ComplexCalculatorServiceImpl implements ComplexCalculatorService {

	private MathService mathService;

	@Autowired
	public void setMathService(MathService mathService) {
		this.mathService = mathService;
	}

	@Override
	public double average(double... values) {
		double result = mathService.sum(values);
		return result / values.length;
	}
}

A diferencia de la inyección por constructor en la inyección por setter la anotación @Autowired se colocará el el método setter.

Inyección por atributo

La siguiente forma de inyectar un atributo es a través de inyección por atributos:


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

/**
 * @author raidentrance
 *
 */
@Service
public class ComplexCalculatorServiceImpl implements ComplexCalculatorService {

	@Autowired
	private MathService mathService;

	@Override
	public double average(double... values) {
		double result = mathService.sum(values);
		return result / values.length;
	}
}

Como podemos ver haciendo inyección por atributo la clase luce más simple y limpia, el problema de utilizar este método de inyección es que en el se utiliza reflection dado que el atributo es privado y no se puede acceder desde fuera de la clase.

Para estar al pendiente sobre nuestro contenido nuevo síguenos en nuestras redes sociales https://www.facebook.com/devs4j/ y https://twitter.com/devs4j

Autor: Alejandro Agapito Bautista
Twitter: @raidentrance
Contacto:raidentrance@gmail.com

Spring framework 5 : Conceptos básicos sobre DI (Dependency Injection)


En el post anterior se explicó como crear un proyecto de Spring framework 5 desde cero https://devs4j.com/2019/01/28/spring-framework-5-creando-un-proyecto-de-spring, ahora toca el turno de hablar sobre inyección de dependencias en Spring framework 5.

Introducción

La definición más simple de Dependency Injection es cuando una un objeto necesita una dependencia y esta es inyectada por otro objeto, la clase que necesita la dependencia no es responsable sobre la creación del objeto.

Veamos el siguiente ejemplo:

Las clases UserDao y RoleDao necesitan un objeto de tipo Datasource, si no se utilizara inyección de dependencias, ambas clases deberían crear su propia instancia del objeto Datasource y si en el futuro es necesario actualizar la contraseña de la base de datos o incluso el motor de base de datos ese cambio se debería hacer en ambos Daos, lo cual reduce la mantenibilidad de la aplicación.

Haciendo uso de Dependency Injection un componente puede crear el objeto de tipo Datasource solo una vez e inyectarlo en todos los componentes que lo necesiten, esto nos ayuda a que los objetos de tipo UserDao y RoleDao no tengan la necesidad de crear los objetos a la base de datos, crear conexiones o cerrarlas.

Tipos de dependency injection

Los siguientes son los tipos de inyección de dependencias en Spring:

  • Por constructor : Las dependencias de la clase se inyectarán a través del constructor
  • Por métodos setters : Las dependencias de la clase se inyectarán a través de métodos setter
  • Por atributos de la clase : Las dependencias de la clase se inyectarán haciendo uso del api de reflection, esto debido a que un atributo de una clase puede ser privado y esto hace que no se pueda acceder desde afuera.

Se puede utilizar inyección de dependencias haciendo uso de interfaces o de clases concretas, es preferible hacerlo a través de interfaces porque esto permite:

  • Decidir la implementación que se inyectará en tiempo de ejecución
  • Seguir los principios de diseño SOLID
  • Haces el código más fácil de probar

En el ejemplo anterior hablamos sobre las clases UserDao y RoleDao utilizando una referencia de tipo Datasource, la cual es una interfaz, esto me permite que no importe el tipo de datasource que yo utilice la clase UserDao y RoleDao funcionarán correctamente.

Inversion of control

Inversion of control es una técnica que permite inyectar dependencias en tiempo de ejecución, esto significa que las dependencias no serán inyectadas de forma predeterminada todo el tiempo sino que pueden variar dependiendo del contexto de ejecución.

Algunas veces las personas confunden Inversion of control con Dependency Injection, la diferencia es que dependency injection se refiere a la composición y estructura de las clases e Inversion of control se refiere más al ambiente de ejecución del código.

En este post hablamos sobre la teoría de dependency injection, en el siguiente veremos como implementarlo utilizando Spring framework 5. Para estar al pendiente sobre nuestro contenido nuevo síguenos en nuestras redes sociales  https://www.facebook.com/devs4j/ y https://twitter.com/devs4j.

Autor: Alejandro Agapito Bautista
Twitter: @raidentrance
Contacto:raidentrance@gmail.com