Spring y Design Patterns
Introducción
En el mundo Java actual, es relativamente raro tener que escribir una funcionalidad técnica que sea completamente nueva. En su lugar, ensamblamos componentes ya construidos, basándonos en frameworks y librerías open source. Cada vez más se trata de implementar componentes funcionales reutilizables para agrupar y compartir los desarrollos tanto como sea posible. Por lo tanto, por una parte, debemos tener cuidado de no reinventar la rueda y, por otra, debemos esforzarnos por hacer que el código sea reutilizable.
De hecho, la mantenibilidad del código es un tema que se debe tomar en serio porque a menudo es un equipo el que codifica y otro el que posteriormente hace el mantenimiento. También es habitual que los proyectos sean a largo plazo y estén basados en software a gran escala desarrollado durante varios años, además de que los desarrolladores solo hacen parte de la aplicación que será reutilizada por otros, con lo que cada pieza de software desarrollada se debe considerar como parte de un framework más grande y reutilizable a voluntad. Por lo tanto, el código debe ser modular, bien estructurado, documentado con javadoc (con @snippet en Java 18) y también debe poder ser verificado (y probado).
En el capítulo Documentación Spring REST Docs, veremos que también es posible documentar su código y API usando metadatos...
El singleton en Java
Un singleton es un objeto único en un espacio dado. Se crea la primera vez que se usa y luego se reutiliza. Lo usamos cuando queremos compartir un recurso o cuando queremos aprovechar la reentrada del código para tener solo una copia de una secuencia de procesamiento para un objetivo determinado, es decir, un método único que varios procesos pueden utilizar, ya sea de manera secuencial en un sistema de un solo thread o de manera concurrente (simultánea), gracias a la reentrada en un sistema multi thread.
1. Objetivo
Históricamente, cuando necesitábamos una acción, la implementábamos en el objeto que la requería. Por lo tanto, para un procesamiento idéntico había una multitud de copias de estas acciones en las diversas instanciaciones de los objetos que lo utilizaban. Esto se debía a que, en un sistema orientado a objetos, los procesos se encapsulan en objetos junto con los datos.
Para las acciones que no se basan en el estado del objeto que lo tiene, se decidió cambiar la acción a la parte estática de la clase. De hecho, la parte estática se comparte entre todas las instancias de la clase. Por lo tanto, no hay más duplicación de código en la memoria.
Algunos servicios se separan de los datos que procesan y, a continuación, se agrupan en clases no instanciadas como clases masivamente estáticas. Existe un sistema que, por un lado, separa los datos del procesamiento con los POJO, es decir, objetos sin procesamiento con excepción de los mecanismos de gestión de instancias (como constructores y descriptores de acceso) y, por otro lado, realiza el procesamiento separado de los datos como se haría en C. Con este tipo de arquitectura, alcanzamos rápidamente los límites del sistema.
En ese momento se desarrolla el singleton. Se trata de una clase que solo se instancia una vez y después todos la reutilizan. Soporta todos los conceptos de programación orientada a objetos. Por lo tanto, tenemos la simplicidad de uso de una clase, pero sin copias innecesarias.
Los primeros singletons (antes de que hubiera enumeraciones) usaban un código parecido al siguiente (en un sistema de un solo thread):
class MiSingleton
private static MiSingleton UNIQUE = null;
private MiSingleton(){ ...
Inversión de control
Con el principio de IOC (Inversion of Control o inversión de control), ya no se instancian las clases que implementan las funciones de una librería, sino que la aplicación las llama directamente. El programa indica que necesita una librería y se le proporciona de forma automática. Es un mecanismo externo que hace que la clase esté disponible a través de un framework o mecanismo de plugins. Por ejemplo, con Spring, la aplicación indica que necesita un servicio con una API específica y Spring le proporciona el mejor candidato.
Spring permite especificar el servicio deseado, a través de una interfaz en los casos más comunes o a través de una clase cuando no hay interfaz. Para esto, generalmente utiliza una Factory de objetos que, en sí misma, es un desing pattern para hacer que el objeto esté disponible.
List<String> lista = new ArrayList<String>();
UsuarioDao dao = (UsuarioDao)
factory.getBean("usuarioDao");
Una buena costumbre que se debe tener en general en Java es declarar un objeto usando la interfaz que mejor lo caracterice. En lugar de instanciar un objeto usted mismo, se deja que Spring encuentre la clase que mejor corresponda al objeto que se va a instanciar.
Se llama al mecanismo de IOC de inyección de dependencias y se utiliza la anotación @Autowired o @Inject. Más adelante veremos...
Facade
Una facade es, por ejemplo, una clase que concentra las API de un conjunto de clases en un único punto de una clase de facade. Spring JDBC es un ejemplo de una facade que oculta las diferencias entre implementaciones de varias bases de datos a través de una API unificada. Spring JDBC también simplifica la gestión de excepciones relacionadas con las bases de datos. Ofrece una facade que encapsula de manera uniforme procesamientos que son muy diferentes de una base a otra, pero que se ven como idénticos en el lado de la facade.
Un servicio Spring (anotado con @Service) se puede considerar como una facade que, por un lado, expone las API de negocio y, por otro, interactúa con DAO para realizar operaciones de negocio y bases de datos.
Esta facade permite diseñar una API que puede ser sencilla y verificable. El programa que utiliza la facade ya no necesita las dependencias porque se llevan a la facade.
Factory
Spring utiliza un modelo de factories para crear beans utilizando una referencia al contexto de la aplicación. Estos beans se describen en la configuración. Parte de la configuración se fija de antemano, mientras que otra parte se deduce de reglas relativamente complejas. Spring encuentra el mejor candidato para un contrato de API determinada.
BeanFactory factory = new XmlBeanFactory(new
FileSystemResource("spring.xml"));
Triangulo triangulo = (Triangulo) factory.getBean("triangulo");
triangulo.draw();
A través de la interfaz BeanFactory, Spring proporciona una batería completa de factories para cubrir tantos casos de uso como sea posible. Esta interfaz tiene nueve subinterfaces y veinticuatro implementaciones en su última versión.
La factory puede ser visible en el caso de una aplicación standalone. Seguidamente, usamos una factory que se basa en archivos, como el mencionado en el ejemplo. Para una aplicación web, la factory se esconde en un filtro de servlet.
Decorador
Un decorador se basa en un componente concreto que implementa una interfaz. En sí mismo, el decorador deriva de esta interfaz. Incluye el objeto original y le agrega atributos o métodos. Permite interceptar métodos, pero utiliza herencia.
Proxy
Spring utiliza mucho este desing pattern para todo lo relacionado con la programación orientada a aspectos (AOP) y el acceso remoto.
El AOP se describe en detalle en el capítulo Programación orientada a aspectos con Spring.
Spring no da acceso directo a los objetos, sino que lo hace a través de un objeto intermedio llamado proxy, que permite a Spring modificar el comportamiento del objeto original.
interface Pojo3 {
public void foo();
}
class SimplePojo3 implements Pojo3 {
public void foo() {
System.out.println("foo");
}
}
class RetryAdvice implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable {
System.out.println("Después "+method.getName());
}
}
public class MainConAspect {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo3());
Class<?>...
Modelo Vista Controlador (MVC)
Spring MVC permite simplemente crear aplicaciones web separando los tres elementos principales:
El modelo |
Los datos. |
La vista |
Lo que se muestra. |
El controlador |
El procesamiento de los datos y la secuencia de vistas. |
Spring delega la elección de la vista a un ViewResolver que es, en sí mismo, un desing pattern.
Por ejemplo, el siguiente controlador en la solicitud de la URL /hola pedirá la visualización de la página hola.jsp y le proporcionará la fecha para que la página la muestre:
@Controller
public class HolaController {
@RequestMapping("/hola")
public ModelAndView hola() {
ModelAndView mav = new ModelAndView();
mav.setViewName("hola");
mav.addObject("fecha", new Date());
return mav;
}
Templates
Las plantillas o templates se utilizan para todos los códigos genéricos.
Spring utiliza estas plantillas masivamente. Estos son algunos ejemplos:
Template |
Módulo |
Utilidad |
RepeatTemplate |
Spring Batch |
Implementación de RepeatOperations |
JdbcTemplate |
Spring |
Operación común JDBC |
QueryDslJdbcTemplate |
Spring Data |
Soporte QueryDsl |
NamedParameterJdbcTemplate |
Spring |
JdbcTemplate, pero con parámetros con nombre |
TransactionTemplate |
Spring |
Gestión genérica de transacciones |
HibernateTemplate |
Spring |
Operaciones actuales en Hibernate |
JdoTemplate |
Spring |
JDO genérico |
JpaTemplate |
Spring |
Operación JPA |
JpaTransactionManager |
Spring |
Transacciones JPA |
RestTemplate |
Spring |
Web service REST |
SimpMessagingTemplate |
Spring |
Mensaje (por ejemplo, JMS) |
Este libro presenta ejemplos para usar algunas de estas plantillas.
Las plantillas son un aspecto muy potente de Spring porque simplifican el código. Enmascaran la complejidad e incluso es posible interconectar un sistema exótico extendiendo un template Spring y ofreciendo una interfaz estándar a los desarrolladores.
Estrategia
La inyección de dependencias (DI) es un design pattern de estrategia. De hecho, siempre que desee configurar una lógica de intercambio entre beans, encontrará una interfaz que se corresponde con un constructor o un método setter apropiado en la clase de inicio para cablear su propia implementación de esta interfaz.
Para una misma interfaz, podemos pedirle a Spring que inyecte un objeto que implemente la interfaz y, de esta manera, elegir un objeto u otro con las mismas API de procesamiento, pero con API con comportamientos diferentes. Por ejemplo, para una lista que tiene por interfaz List puede elegir entre las siguientes implementaciones: AbstractList, AbstractSequentialList, ArrayList, AttributeList, CopyOnWriteArrayList, Linked List, RoleList, RoleUnresolvedList, Stack, Vector, etc., dependiendo del comportamiento que quiera tener.
Puntos clave
-
El framework Spring está completamente orientado a desing pattern.
-
Es necesario identificar los design patterns en los comentarios del código.
-
Antes de codificar cualquier cosa, debe ver si es un desing pattern y reutilizar o especializar el código que ya implementa ese desing pattern.
-
La codificación orientada a desing pattern, permite que su código sea más fácilmente reutilizable.