1. Introducción
Los patrones de diseño son una parte esencial del desarrollo de software. Estas soluciones no solo resuelven problemas recurrentes, sino que también ayudan a los desarrolladores a comprender el diseño de un marco al reconocer patrones comunes.
En este tutorial, veremos cuatro de los patrones de diseño más comunes que se utilizan en Spring Framework:
- Patrón singleton
- Patrón de método de fábrica
- Patrón de proxy
- Patrón de plantilla
También veremos cómo Spring usa estos patrones para reducir la carga de los desarrolladores y ayudar a los usuarios a realizar rápidamente tareas tediosas.
2. Patrón singleton
El patrón singleton es un mecanismo que garantiza que solo exista una instancia de un objeto por aplicación . Este patrón puede ser útil cuando se administran recursos compartidos o se brindan servicios transversales, como el registro.
2.1. Frijoles Singleton
Generalmente, un singleton es globalmente único para una aplicación, pero en Spring, esta restricción es relajada. En cambio, Spring restringe un singleton a un objeto por contenedor Spring IoC . En la práctica, esto significa que Spring solo creará un bean para cada tipo por contexto de aplicación.
El enfoque de Spring difiere de la definición estricta de singleton ya que una aplicación puede tener más de un contenedor Spring. Por lo tanto, pueden existir varios objetos de la misma clase en una sola aplicación si tenemos varios contenedores.

Por defecto, Spring crea todos los beans como singletons.
2.2. Singleton cableados automáticamente
Por ejemplo, podemos crear dos controladores dentro de un único contexto de aplicación e inyectar un bean del mismo tipo en cada uno.
Primero, creamos un BookRepository que administra nuestros objetos de dominio de libros .
A continuación, creamos LibraryController , que usa BookRepository para devolver la cantidad de libros en la biblioteca:
@RestController public class LibraryController { @Autowired private BookRepository repository; @GetMapping("/count") public Long findCount() { System.out.println(repository); return repository.count(); } }
Por último, creamos un BookController , que se centra en acciones específicas del libro , como buscar un libro por su ID:
@RestController public class BookController { @Autowired private BookRepository repository; @GetMapping("/book/{id}") public Book findById(@PathVariable long id) { System.out.println(repository); return repository.findById(id).get(); } }
Luego iniciamos esta aplicación y realizamos un GET en / count y / book / 1:
curl -X GET //localhost:8080/count curl -X GET //localhost:8080/book/1
En la salida de la aplicación, vemos que ambos objetos BookRepository tienen el mismo ID de objeto:
[email protected] [email protected]
Los ID de objeto BookRepository en LibraryController y BookController son los mismos, lo que demuestra que Spring inyectó el mismo bean en ambos controladores.
Podemos crear instancias separadas del bean BookRepository cambiando el alcance del bean de singleton a prototype usando la anotación @ Scope (ConfigurableBeanFactory.SCOPE_PROTOTYPE) .
Hacerlo le indica a Spring que cree objetos separados para cada uno de los beans de BookRepository que crea. Por lo tanto, si inspeccionamos nuevamente el ID de objeto del BookRepository en cada uno de nuestros controladores, vemos que ya no son los mismos.
3. Patrón de método de fábrica
El patrón del método de fábrica implica una clase de fábrica con un método abstracto para crear el objeto deseado.
A menudo, queremos crear diferentes objetos basados en un contexto particular.
Por ejemplo, nuestra aplicación puede requerir un objeto de vehículo. En un entorno náutico, queremos crear barcos, pero en un entorno aeroespacial, queremos crear aviones:

Para lograr esto, podemos crear una implementación de fábrica para cada objeto deseado y devolver el objeto deseado del método de fábrica de concreto.
3.1. Contexto de la aplicación
Spring uses this technique at the root of its Dependency Injection (DI) framework.
Fundamentally, Spring treatsa bean container as a factory that produces beans.
Thus, Spring defines the BeanFactory interface as an abstraction of a bean container:
public interface BeanFactory { getBean(Class requiredType); getBean(Class requiredType, Object... args); getBean(String name); // ... ]
Each of the getBean methods is considered a factory method, which returns a bean matching the criteria supplied to the method, like the bean's type and name.
Spring then extends BeanFactory with the ApplicationContext interface, which introduces additional application configuration. Spring uses this configuration to start-up a bean container based on some external configuration, such as an XML file or Java annotations.
Using the ApplicationContext class implementations like AnnotationConfigApplicationContext, we can then create beans through the various factory methods inherited from the BeanFactory interface.
First, we create a simple application configuration:
@Configuration @ComponentScan(basePackageClasses = ApplicationConfig.class) public class ApplicationConfig { }
Next, we create a simple class, Foo, that accepts no constructor arguments:
@Component public class Foo { }
Then create another class, Bar, that accepts a single constructor argument:
@Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class Bar { private String name; public Bar(String name) { this.name = name; } // Getter ... }
Lastly, we create our beans through the AnnotationConfigApplicationContext implementation of ApplicationContext:
@Test public void whenGetSimpleBean_thenReturnConstructedBean() { ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class); Foo foo = context.getBean(Foo.class); assertNotNull(foo); } @Test public void whenGetPrototypeBean_thenReturnConstructedBean() { String expectedName = "Some name"; ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class); Bar bar = context.getBean(Bar.class, expectedName); assertNotNull(bar); assertThat(bar.getName(), is(expectedName)); }
Using the getBean factory method, we can create configured beans using just the class type and — in the case of Bar — constructor parameters.
3.2. External Configuration
This pattern is versatile because we can completely change the application's behavior based on external configuration.
If we wish to change the implementation of the autowired objects in the application, we can adjust the ApplicationContext implementation we use.

For example, we can change the AnnotationConfigApplicationContext to an XML-based configuration class, such as ClassPathXmlApplicationContext:
@Test public void givenXmlConfiguration_whenGetPrototypeBean_thenReturnConstructedBean() { String expectedName = "Some name"; ApplicationContext context = new ClassPathXmlApplicationContext("context.xml"); // Same test as before ... }
4. Proxy Pattern
Proxies are a handy tool in our digital world, and we use them very often outside of software (such as network proxies). In code, the proxy pattern is a technique that allows one object — the proxy — to control access to another object — the subject or service.

4.1. Transactions
To create a proxy, we create an object that implements the same interface as our subject and contains a reference to the subject.
We can then use the proxy in place of the subject.
In Spring, beans are proxied to control access to the underlying bean. We see this approach when using transactions:
@Service public class BookManager { @Autowired private BookRepository repository; @Transactional public Book create(String author) { System.out.println(repository.getClass().getName()); return repository.create(author); } }
In our BookManager class, we annotate the create method with the @Transactional annotation. This annotation instructs Spring to atomically execute our create method. Without a proxy, Spring wouldn't be able to control access to our BookRepository bean and ensure its transactional consistency.
4.2. CGLib Proxies
Instead, Spring creates a proxy that wraps our BookRepository bean and instruments our bean to execute our create method atomically.
When we call our BookManager#create method, we can see the output:
com.baeldung.patterns.proxy.BookRepository$$EnhancerBySpringCGLIB$$3dc2b55c
Typically, we would expect to see a standard BookRepository object ID; instead, we see an EnhancerBySpringCGLIB object ID.
Behind the scenes, Spring has wrapped our BookRepository object inside as EnhancerBySpringCGLIB object. Spring thus controls access to our BookRepository object (ensuring transactional consistency).

Generally, Spring uses two types of proxies:
- CGLib Proxies – Used when proxying classes
- JDK Dynamic Proxies – Used when proxying interfaces
While we used transactions to expose the underlying proxies, Spring will use proxies for any scenario in which it must control access to a bean.
5. Template Method Pattern
In many frameworks, a significant portion of the code is boilerplate code.
For example, when executing a query on a database, the same series of steps must be completed:
- Establish a connection
- Execute query
- Perform cleanup
- Close the connection
These steps are an ideal scenario for the template method pattern.
5.1. Templates & Callbacks
The template method pattern is a technique that defines the steps required for some action, implementing the boilerplate steps, and leaving the customizable steps as abstract. Subclasses can then implement this abstract class and provide a concrete implementation for the missing steps.
We can create a template in the case of our database query:
public abstract DatabaseQuery { public void execute() { Connection connection = createConnection(); executeQuery(connection); closeConnection(connection); } protected Connection createConnection() { // Connect to database... } protected void closeConnection(Connection connection) { // Close connection... } protected abstract void executeQuery(Connection connection); }
Alternatively, we can provide the missing step by supplying a callback method.
A callback method is a method that allows the subject to signal to the client that some desired action has completed.
In some cases, the subject can use this callback to perform actions — such as mapping results.

For example, instead of having an executeQuery method, we can supply the execute method a query string and a callback method to handle the results.
First, we create the callback method that takes a Results object and maps it to an object of type T:
public interface ResultsMapper { public T map(Results results); }
Then we change our DatabaseQuery class to utilize this callback:
public abstract DatabaseQuery { public T execute(String query, ResultsMapper mapper) { Connection connection = createConnection(); Results results = executeQuery(connection, query); closeConnection(connection); return mapper.map(results); ] protected Results executeQuery(Connection connection, String query) { // Perform query... } }
This callback mechanism is precisely the approach that Spring uses with the JdbcTemplate class.
5.2. JdbcTemplate
The JdbcTemplate class provides the query method, which accepts a query String and ResultSetExtractor object:
public class JdbcTemplate { public T query(final String sql, final ResultSetExtractor rse) throws DataAccessException { // Execute query... } // Other methods... }
The ResultSetExtractor converts the ResultSet object — representing the result of the query — into a domain object of type T:
@FunctionalInterface public interface ResultSetExtractor { T extractData(ResultSet rs) throws SQLException, DataAccessException; }
Spring further reduces boilerplate code by creating more specific callback interfaces.
For example, the RowMapper interface is used to convert a single row of SQL data into a domain object of type T.
@FunctionalInterface public interface RowMapper { T mapRow(ResultSet rs, int rowNum) throws SQLException; }
To adapt the RowMapper interface to the expected ResultSetExtractor, Spring creates the RowMapperResultSetExtractor class:
public class JdbcTemplate { public List query(String sql, RowMapper rowMapper) throws DataAccessException { return result(query(sql, new RowMapperResultSetExtractor(rowMapper))); } // Other methods... }
Instead of providing logic for converting an entire ResultSet object, including iteration over the rows, we can provide logic for how to convert a single row:
public class BookRowMapper implements RowMapper { @Override public Book mapRow(ResultSet rs, int rowNum) throws SQLException { Book book = new Book(); book.setId(rs.getLong("id")); book.setTitle(rs.getString("title")); book.setAuthor(rs.getString("author")); return book; } }
With this converter, we can then query a database using the JdbcTemplate and map each resulting row:
JdbcTemplate template = // create template... template.query("SELECT * FROM books", new BookRowMapper());
Apart from JDBC database management, Spring also uses templates for:
- Java Message Service (JMS)
- Java Persistence API (JPA)
- Hibernate (now deprecated)
- Transactions
6. Conclusion
In this tutorial, we looked at four of the most common design patterns applied in the Spring Framework.
También exploramos cómo Spring utiliza estos patrones para proporcionar funciones enriquecidas al tiempo que reduce la carga para los desarrolladores.
El código de este artículo se puede encontrar en GitHub.