1. Información general
La versión Spring 4.3 trajo algunas mejoras agradables en el contenedor central, el almacenamiento en caché, JMS, Web MVC y los submódulos de prueba del marco.
En esta publicación, discutiremos algunas de estas mejoras, que incluyen:
- Inyección de constructor implícito
- Compatibilidad con métodos de interfaz predeterminados de Java 8
- Resolución mejorada de dependencias
- Refinamientos de abstracción de caché
- Variantes de @RequestMapping compuestas
- @Requestscope, @Sessionscope, @Applicationscope Anotaciones
- @RequestAttribute y @SessionAttribute anotaciones
- Compatibilidad con versiones de bibliotecas / servidores de aplicaciones
- la clase InjectionPoint
2. Inyección de constructor implícito
Considere la siguiente clase de servicio:
@Service public class FooService { private final FooRepository repository; @Autowired public FooService(FooRepository repository) { this.repository = repository } }
Es un caso de uso bastante común, pero si olvida la anotación @Autowired en el constructor, el contenedor arrojará una excepción en busca de un constructor predeterminado, a menos que haga explícitamente el cableado.
Entonces, a partir de 4.3, ya no es necesario especificar una anotación de inyección explícita en un escenario de un solo constructor. Esto es particularmente elegante para las clases que no tienen ninguna anotación:
public class FooService { private final FooRepository repository; public FooService(FooRepository repository) { this.repository = repository } }
En Spring 4.2 y versiones posteriores, la siguiente configuración para este bean no funcionará, porque Spring no podrá encontrar un constructor predeterminado para FooService . Spring 4.3 es más inteligente y conectará automáticamente el constructor automáticamente:
De manera similar, puede haber notado que las clases de @Configuration históricamente no admitían la inyección de constructores. A partir de la versión 4.3, lo hacen y, naturalmente, también permiten omitir @Autowired en un escenario de un solo constructor:
@Configuration public class FooConfiguration { private final FooRepository repository; public FooConfiguration(FooRepository repository) { this.repository = repository; } @Bean public FooService fooService() { return new FooService(this.repository); } }
3. Compatibilidad con métodos de interfaz predeterminados de Java 8
Antes de Spring 4.3, los métodos de interfaz predeterminados no eran compatibles.
Esto no fue fácil de implementar porque incluso el introspector JavaBean de JDK no detectó métodos predeterminados como accesos. Desde Spring 4.3, los getters y setters implementados como métodos de interfaz predeterminados se identifican durante la inyección, lo que permite usarlos, por ejemplo, como preprocesadores comunes para las propiedades a las que se accede, como en este ejemplo:
public interface IDateHolder { void setLocalDate(LocalDate localDate); LocalDate getLocalDate(); default void setStringDate(String stringDate) { setLocalDate(LocalDate.parse(stringDate, DateTimeFormatter.ofPattern("dd.MM.yyyy"))); } }
Este bean ahora puede tener la propiedad stringDate inyectada:
Lo mismo ocurre con el uso de anotaciones de prueba como @BeforeTransaction y @AfterTransaction en los métodos de interfaz predeterminados. JUnit 5 ya admite sus anotaciones de prueba en los métodos de interfaz predeterminados, y Spring 4.3 sigue el ejemplo. Ahora puede abstraer la lógica de prueba común en una interfaz e implementarla en clases de prueba. Aquí hay una interfaz para casos de prueba que registra mensajes antes y después de las transacciones en las pruebas:
public interface ITransactionalTest { Logger log = LoggerFactory.getLogger(ITransactionalTest.class); @BeforeTransaction default void beforeTransaction() { log.info("Before opening transaction"); } @AfterTransaction default void afterTransaction() { log.info("After closing transaction"); } }
Otra mejora con respecto a las anotaciones @BeforeTransaction, @AfterTransaction y @Transactional es la relajación del requisito de que los métodos anotados deben ser públicos ; ahora pueden tener cualquier nivel de visibilidad.
4. Resolución mejorada de dependencias
La versión más reciente también presenta ObjectProvider , una extensión de la interfaz ObjectFactory existente con firmas útiles como getIfAvailable y getIfUnique para recuperar un bean solo si existe o si se puede determinar un solo candidato (en particular: un candidato principal en caso de múltiples frijoles a juego).
@Service public class FooService { private final FooRepository repository; public FooService(ObjectProvider repositoryProvider) { this.repository = repositoryProvider.getIfUnique(); } }
Puede utilizar dicho identificador de ObjectProvider para fines de resolución personalizada durante la inicialización, como se muestra arriba, o almacenar el identificador en un campo para una resolución tardía a pedido (como suele hacer con un ObjectFactory ).
5. Refinamientos de la abstracción de caché
La abstracción de caché se utiliza principalmente para almacenar en caché valores que consumen CPU y E / S. En casos de uso particulares, varios subprocesos (es decir, clientes) pueden solicitar una clave determinada en paralelo, especialmente al inicio. El soporte de caché sincronizado es una característica solicitada desde hace mucho tiempo que ahora se ha implementado. Suponga lo siguiente:
@Service public class FooService { @Cacheable(cacheNames = "foos", sync = true) public Foo getFoo(String id) { ... } }
Observe el atributo sync = true que le dice al marco que bloquee cualquier hilo concurrente mientras se calcula el valor. Esto asegurará que esta operación intensiva se invoque solo una vez en caso de acceso concurrente.
Spring 4.3 también mejora la abstracción del almacenamiento en caché de la siguiente manera:
- Las expresiones SpEL en anotaciones relacionadas con la caché ahora pueden referirse a beans (es decir, @ beanName.method () ).
- ConcurrentMapCacheManager y ConcurrentMapCache ahora admiten la serialización de entradas de caché a través de un nuevo atributo storeByValue .
- @Cacheable , @CacheEvict , @CachePut y @Caching ahora se pueden usar como meta-anotaciones para crear anotaciones compuestas personalizadas con anulaciones de atributos.
6. Variantes de @RequestMapping compuestas
Spring Framework 4.3 presenta las siguientes variantes compuestas a nivel de método de la anotación @RequestMapping que ayudan a simplificar las asignaciones para los métodos HTTP comunes y expresar mejor la semántica del método del controlador anotado.
- @GetMapping
- @PostMapping
- @PutMapping
- @DeleteMapping
- @PatchMapping
For example, @GetMapping is a shorter form of saying @RequestMapping(method = RequestMethod.GET). The following example shows an MVC controller that has been simplified with a composed @GetMapping annotation.
@Controller @RequestMapping("/appointments") public class AppointmentsController { private final AppointmentBook appointmentBook; @Autowired public AppointmentsController(AppointmentBook appointmentBook) { this.appointmentBook = appointmentBook; } @GetMapping public Map get() { return appointmentBook.getAppointmentsForToday(); } }
7. @RequestScope, @SessionScope, @ApplicationScope Annotations
When using annotation-driven components or Java Config, the @RequestScope, @SessionScope and @ApplicationScope annotations can be used to assign a component to the required scope. These annotations not only set the scope of the bean but also set the scoped proxy mode to ScopedProxyMode.TARGET_CLASS.
TARGET_CLASS mode means that CGLIB proxy will be used for proxying of this bean and ensuring that it can be injected in any other bean, even with a broader scope. TARGET_CLASS mode allows proxying not only for interfaces but classes too.
@RequestScope @Component public class LoginAction { // ... }
@SessionScope @Component public class UserPreferences { // ... }
@ApplicationScope @Component public class AppPreferences { // ... }
8. @RequestAttribute and @SessionAttribute Annotations
Two more annotations for injecting parameters of the HTTP request into Controller methods appeared, namely @RequestAttribute and @SessionAttribute. They allow you to access some pre-existing attributes, managed globally (i.e. outside the Controller). The values for these attributes may be provided, for instance, by registered instances of javax.servlet.Filter or org.springframework.web.servlet.HandlerInterceptor.
Suppose we have registered the following HandlerInterceptor implementation that parses the request and adds login parameter to the session and another query parameter to a request:
public class ParamInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { request.getSession().setAttribute("login", "john"); request.setAttribute("query", "invoices"); return super.preHandle(request, response, handler); } }
Such parameters may be injected into a Controller instance with corresponding annotations on method arguments:
@GetMapping public String get(@SessionAttribute String login, @RequestAttribute String query) { return String.format("login = %s, query = %s", login, query); }
9. Libraries/Application Servers Versions Support
Spring 4.3 supports the following library versions and server generations:
- Hibernate ORM 5.2 (still supporting 4.2/4.3 and 5.0/5.1 as well, with 3.6 deprecated now)
- Jackson 2.8 (minimum raised to Jackson 2.6+ as of Spring 4.3)
- OkHttp 3.x (still supporting OkHttp 2.x side by side)
- Netty 4.1
- Undertow 1.4
- Tomcat 8.5.2 as well as 9.0 M6
Furthermore, Spring 4.3 embeds the updated ASM 5.1 and Objenesis 2.4 in spring-core.jar.
10. InjectionPoint
The InjectionPoint class is a new class introduced in Spring 4.3 which provides information about places where a particular bean gets injected, whether it is a method/constructor parameter or a field.
The types of information you can find using this class are:
- Field object – you can obtain the point of injection wrapped as a Field object by using the getField() method if the bean is injected into a field
- MethodParameter – you can call getMethodParameter() method to obtain the injection point wrapped as a MethodParameter object if the bean is injected into a parameter
- Member – calling getMember() method will return the entity containing the injected bean wrapped into a Member object
- Class – obtain the declared type of the parameter or field where the bean in injected, using getDeclaredType()
- Annotation[] – by using the getAnnotations() method, you can retrieve an array of Annotation objects which represent the annotations associated with the field or parameter
- AnnotatedElement – call getAnnotatedElement() to get the injection point wrapped as an AnnotatedElement object
A case in which this class is very useful is when we want to create Logger beans based on the class to which they belong:
@Bean @Scope("prototype") public Logger logger(InjectionPoint injectionPoint) { return Logger.getLogger( injectionPoint.getMethodParameter().getContainingClass()); }
The bean has to be defined with a prototype scope so that a different logger is created for each class. If you create a singleton bean and inject in multiple places, the Spring will return the first encountered injection point.
Then, we can inject the bean into our AppointmentsController:
@Autowired private Logger logger;
11. Conclusion
In this article, we discussed some of the new features introduced with Spring 4.3.
We've covered useful annotations that eliminate boilerplate, new helpful methods of dependency lookup and injection and several substantial improvements within the web and caching facilities.
Puede encontrar el código fuente del artículo en GitHub.