Decorate or Intercept Spring #Autowired Bean - spring

I am upgrading old Java EE application to Spring based solution. In the old applications there were custom annotations to create proxy inject proxy bean and intercept the method invocation [Interceptor classes implements MethodInterceptor) or (implements InvocationHandler), which used to perform some before and after execution stuff.
We have replaced those custom annotations with Spring marker interfaces like #Service, #Repository etc. and we are able to use #Autowire the bean instances. Now my question is how to intercept these autowired beans to perform per and post execution activities. One solution I can think is to use Spring AOP and use #Around pointcut. Just want to know is there any other and better alternative which can be used like
extending org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
Using BeanFactoryPostProcessor or BeanPostProcessor.
Using InstantiationAwareBeanPostProcessor

I have used this alternative instead of AOP. I have used Spring's bean pre & post processor call back. Below is the code snippet.
Application Context Provider, to get Spring beans statically
package com.appname.config;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* #author dpoddar
*
*/
#Component("applicationContextProvider")
public class ApplicationContextProvider implements ApplicationContextAware{
private static ApplicationContext ctx = null;
public static ApplicationContext getApplicationContext() {
return ctx;
}
#Override
#Autowired
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
ApplicationContextProvider.ctx = ctx;
}
/**
* Returns the Spring managed bean instance of the given class type if it exists.
* Returns null otherwise.
* #param beanClass
* #return
*/
public static <T extends Object> T getBean(Class<T> beanClass) {
return ctx.getBean(beanClass);
}
/**
* Returns the Spring managed bean instance of the given class type if it exists.
*
* #param <T>
* #param name
* #param beanClass
* #return
*/
public static <T extends Object> T getBean(String name,Class<T> beanClass) {
return ctx.getBean(name,beanClass);
}
}
Spring Bean Post Processor, InstantiationAwareBeanPostProcessor adds before and after initialization call backs
package com.appname.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.SpringProxy;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import com.appname.core.ExecutionContext;
import com.appname.core.di.FacadeService;
import com.appname.interceptors.BusinesServiceInterceptor;
import com.appname.interceptors.FacadeServiceInterceptor;
import com.appname.interceptors.RepositoryInterceptor;
import net.sf.cglib.proxy.Enhancer;
/**
* #author dpoddar
*
*/
#Component
public class AppSpringBeanPostProcessor extends AutowiredAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
private static Logger logger = LoggerFactory.getLogger(AppSpringBeanPostProcessor.class);
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
Class<?> clazz = bean.getClass();
AutowireCapableBeanFactory factory = ApplicationContextProvider.getApplicationContext().getAutowireCapableBeanFactory();
if(clazz.isAnnotationPresent(FacadeService.class)) {
//This is to instatiate InvocationHandler classes
//return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new FacadeServiceInterceptor(bean));
FacadeServiceInterceptor interceptor = new FacadeServiceInterceptor();
Enhancer e = new Enhancer();
e.setSuperclass(clazz);
e.setInterfaces(new Class[]{SpringProxy.class}); /// Identification Spring-generated proxies
e.setCallback(interceptor);
Object o = e.create();
factory.autowireBean( o ); //Autowire Bean dependecies to the newly created object
return o;
}else if(clazz.isAnnotationPresent(Service.class)) {
BusinesServiceInterceptor interceptor = new BusinesServiceInterceptor();
Enhancer e = new Enhancer();
e.setSuperclass(clazz);
e.setInterfaces(new Class[]{SpringProxy.class});
e.setCallback(interceptor);
Object o = e.create();
factory.autowireBean( o );
return o;
}else if(clazz.isAnnotationPresent(Repository.class)) {
ExecutionContext.newInstance();
RepositoryInterceptor interceptor = new RepositoryInterceptor();
Enhancer e = new Enhancer();
e.setSuperclass(clazz);
e.setInterfaces(new Class[]{SpringProxy.class});
e.setCallback(interceptor);
return e.create();
}else {
return bean;
}
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}

Related

Is it possible to use transactional observers in quarkus with reactive sql client

I'm trying to figure out if it's possible to catch transaction events like it's described here, but with the reactive client?
If it's not possible I would appreciate if someone provide an example how it can be implement manually. I want to be able to add some business logic in my application before transaction start, before transaction commit and after transaction commit. And I think that events are best suited for such logic. Thanks in advance.
Finally, I've found a way how it can be implemented manually. Reactive hibernate has such a method in the Mutiny session implementation:
Uni<T> executeInTransaction(Function<Mutiny.Transaction, Uni<T>> work) {
return work.apply( this )
// only flush() if the work completed with no exception
.call( this::flush )
.call( this::beforeCompletion )
// in the case of an exception or cancellation
// we need to rollback the transaction
.onFailure().call( this::rollback )
.onCancellation().call( this::rollback )
// finally, when there was no exception,
// commit or rollback the transaction
.call( () -> rollback ? rollback() : commit() )
.call( this::afterCompletion );
}
So, as you can see, two methods (beforeCompletion and afterCompletion) are called in the chain, which allows us to add custom logic before and after transaction commit. Those methods execute contract implementations from the queue. I'll show you an example of the "before" event.
First of all, we should create some qualifier annotations.
The #Entity annotation we are going to use to attach event listeners for the specific entity event:
package com.example.annotation;
import java.io.Serial;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.enterprise.util.AnnotationLiteral;
import javax.inject.Qualifier;
import lombok.EqualsAndHashCode;
import com.example.model.BaseEntity;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Qualifier
#Retention(RUNTIME)
#Target({METHOD, PARAMETER, FIELD, TYPE})
#Documented
public #interface Entity {
Class<? extends BaseEntity> value();
#EqualsAndHashCode(callSuper = true)
final class Literal extends AnnotationLiteral<Entity> implements Entity {
#Serial
private static final long serialVersionUID = 2137611959567040656L;
private final Class<? extends BaseEntity> value;
private Literal(Class<? extends BaseEntity> value) {
this.value = value;
}
public static Literal of(Class<? extends BaseEntity> value) {
return new Literal(value);
}
#Override
public Class<? extends BaseEntity> value() {
return value;
}
}
}
Let's imagine we have Book and Author entities in our service and both of them extend a BaseEntity model/interface. This base entity is used here as some kind of qualifier, which will be used later.
And some annotations for CRUD actions, here as an example of the "create" action (the same is for the "update" action):
#Qualifier
#Retention(RUNTIME)
#Target({METHOD, PARAMETER, FIELD, TYPE})
#Documented
public #interface Create {
#EqualsAndHashCode(callSuper = true)
final class Literal extends AnnotationLiteral<Create> implements Create {
private static final long serialVersionUID = 2137611959567040656L;
public static final Literal INSTANCE = new Literal();
private Literal() {
}
}
}
Next we create the class, which will trigger events. In this example we register "pre-insert" and "pre-update" listeners:
package com.example.observer;
import java.io.Serial;
import java.lang.annotation.Annotation;
import java.util.List;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.spi.CDI;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.event.spi.*;
import org.hibernate.reactive.session.ReactiveSession;
import com.example.annotation.Entity;
import com.example.annotation.operation.Create;
import com.example.annotation.operation.Update;
import com.example.model.BaseEntity;
import com.example.observer.action.EventReactiveBeforeTransactionCompletionProcess;
/**
* {#inheritDoc}
* <p>Component, which registers 'pre' events</p>
*/
#Slf4j
#ApplicationScoped
public class TransactionProcessRegistrarEventListener implements PreInsertEventListener, PreUpdateEventListener {
#Serial
private static final long serialVersionUID = 6763048376606381859L;
/**
* {#inheritDoc}
*
* #param event event
*/
#Override
public boolean onPreInsert(PreInsertEvent event) {
return register(event, Create.Literal.INSTANCE);
}
/**
* {#inheritDoc}
*
* #param event event
*/
#Override
public boolean onPreUpdate(PreUpdateEvent event) {
return register(event, Update.Literal.INSTANCE);
}
/**
* Register processes
*
* #param event event
* #return result
*/
#SuppressWarnings("unchecked")
private boolean register(AbstractPreDatabaseOperationEvent event, Annotation qualifier) {
Class<? extends BaseEntity> clazz = (Class<? extends BaseEntity>) event.getEntity().getClass();
log.debug("registering '{}' instances. Entity: {}", event.getClass().getSimpleName(), clazz.getSimpleName());
final SessionImplementor session = event.getSession();
List<EventReactiveBeforeTransactionCompletionProcess> beforeProcesses = CDI.current()
.select(EventReactiveBeforeTransactionCompletionProcess.class, Entity.Literal.of(clazz), qualifier)
.stream().toList();
if (beforeProcesses.isEmpty())
log.debug("no 'before' processes found");
beforeProcesses.forEach(process -> {
process.setEvent(event);
((ReactiveSession) session).getReactiveActionQueue()
.registerProcess(process);
log.debug("process {} has been successfully registered", process.getClass().getSimpleName());
});
return false;
}
}
As you can see here we have a custom EventReactiveBeforeTransactionCompletionProcess interface, it will allow us to set current event to the hibernate process (and retrieve it later in the event). Let's create it:
package com.example.observer.action;
import org.hibernate.event.spi.AbstractPreDatabaseOperationEvent;
import org.hibernate.reactive.engine.ReactiveBeforeTransactionCompletionProcess;
public interface EventReactiveBeforeTransactionCompletionProcess extends ReactiveBeforeTransactionCompletionProcess {
<T extends AbstractPreDatabaseOperationEvent> void setEvent(T event);
<T extends AbstractPreDatabaseOperationEvent> T getEvent();
}
Now we have everything to create a custom hibernate integrator, which will allow us to register listeners.
package com.example.observer;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.boot.Metadata;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
import javax.enterprise.context.ApplicationScoped;
#Slf4j
#ApplicationScoped
public class EventListenerIntegrator implements Integrator {
/**
* {#inheritDoc}
*
* #param metadata The "compiled" representation of the mapping information
* #param sessionFactory The session factory being created
* #param serviceRegistry The session factory's service registry
*/
#Override
public void integrate(Metadata metadata,
SessionFactoryImplementor sessionFactory,
SessionFactoryServiceRegistry serviceRegistry) {
log.debug("registering {} integrator...", getClass().getSimpleName());
final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService(EventListenerRegistry.class);
eventListenerRegistry.appendListeners(EventType.PRE_INSERT, new TransactionProcessRegistrarEventListener());
eventListenerRegistry.appendListeners(EventType.PRE_UPDATE, new TransactionProcessRegistrarEventListener());
}
/**
* {#inheritDoc}
*
* #param sessionFactory The session factory being closed.
* #param serviceRegistry That session factory's service registry
*/
#Override
public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
// intentionally do nothing
}
}
And finally, the listener itself (you can retrieve the event here and the entity from it using parent getEvent method):
package com.example.observer.action.before.create.book.validator;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import io.quarkus.arc.Priority;
import io.quarkus.arc.Unremovable;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.event.spi.AbstractPreDatabaseOperationEvent;
import org.hibernate.reactive.session.ReactiveSession;
import com.example.annotation.Entity;
import com.example.annotation.operation.Create;
import com.example.model.Book;
import com.example.observer.action.EventReactiveBeforeTransactionCompletionProcess;
#Getter
#Setter
#Slf4j
#Priority(10)
#Entity(Book.class)
#Create
#Unremovable
#Dependent
public class BookValidator implements EventReactiveBeforeTransactionCompletionProcess {
private AbstractPreDatabaseOperationEvent event;
/**
* {#inheritDoc}
*
* #param session The session on which the transaction is preparing to complete.
*/
#Override
public CompletionStage<Void> doBeforeTransactionCompletion(ReactiveSession session) {
log.debug("validating, enriching or everything you want with the book entity...");
return CompletableFuture.completedStage(null);
}
}
That's all. If anyone knows a better realization, please let me know.

How to Mock HikariDataSource and DataSourceProperties?

How can I mock HikariDataSource and DataSourceProperties using springboot and Junit5 and Mockito?
I found one possible solution but it seems to be rather very lengthy approach to test a single method, you can check here
I am trying to unit test one of the method 'ping' in my class but getting exception:
java.lang.NullPointerException
at com.my.app.configuration.DbConfigurationTest.shouldReturnPing(DbConfgiurationTest.java:37)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:532)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:171)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
Test Class:
import com.my.app.config.DbConfiguration;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mockito;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.test.context.TestConfiguration;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
#TestConfiguration
class DbConfigurationTest
{
#InjectMocks static DbConfiguration dbConfiguration;
DataSourceProperties dataSourceProperties= Mockito.mock(DataSourceProperties.class);
#Test
public void shouldReturnPing()
{
// expect
boolean expectedResult = true;
when(dbConfiguration.dataSourceProperties()).thenReturn(dataSourceProperties);
when(dbConfiguration.dataSource(dataSourceProperties).isRunning()).thenReturn(true);
boolean actualResult = dbConfiguration.ping();
assertThat(actualResult, is(equalTo(expectedResult)));
}
}
Method class which needs to be tested:
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* Database configuration. Only properties override is required, everything else comes from spring boot auto-config.
*/
#Configuration
public class DbConfiguration
{
/**
* Overrides the auto-configured bean in order to specify a different prefix for the DataSourceProperties
*
* #return A DataSourceProperties bean
*/
#Bean
#Primary
#ConfigurationProperties("my.datasource")
public DataSourceProperties dataSourceProperties()
{
return new DataSourceProperties();
}
/**
* Overrides the {#link javax.sql.DataSource} bean creation from
* {#link org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration}
* to specify a different prefix for the {#link org.springframework.boot.context.properties.ConfigurationProperties}
*
* #param dataSourceProperties The properties to configure the datasource
* #return A {#link com.zaxxer.hikari.HikariDataSource} instance
*/
#Bean
#ConfigurationProperties("my.datasource.configuration")
public HikariDataSource dataSource(final DataSourceProperties dataSourceProperties)
{
return dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
/**
* Ping the service.
*
* #return the boolean
*/
public boolean ping()
{
return dataSource(dataSourceProperties()).isRunning();
}
}
How can I successfully test the 'ping' method? I tried to create mock but failed.
Thanks in advance :)
I would suggest moving the ping() method to a separate class and autowire the DataSource there. For example:
#Component
public class DatabaseHealthCheck {
public DatabaseHealthCheck(HikariDataSource dataSource) {
this.dataSource = dataSource;
}
public boolean ping() {
return dataSource.isRunning();
}
}
And now you can test it like this:
// Mock only the DataSource, either with #Mock or like this
HikariDataSource dataSource = mock(HikariDataSource.class);
DatabaseHealthCheck healthCheck = new DatabaseHealthCheck(dataSource);
when(dataSource.isRunning()).thenReturn(true);
assertThat(healthCheck.ping()).isTrue();

Spring #Autowired null problem on weblogic 12c

Spring's #Autowired value is null. My application is deployed on weblogic 12c.
I have pasted relevant files below along with explanations.
I tried changing to #Component
DriverServlet.java: Set in web.xml. Called by weblogic. Initializes
application context.
package com.clarksburg.services.configuration;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
public class DriverServlet extends HttpServlet {
public void init() throws ServletException {
// Call the AppConfig class to scan all packages
AbstractApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println("Driver Servlet initialized;");
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
public void destroy() {
// do nothing.
}
}
AppConfig.java: Does component scanning.
package com.clarksburg.services.configuration;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan(basePackages = { "com.clarksburg.services" })
public class AppConfig {
private static final Logger logger = LogManager.getLogger(AppConfig.class.getName());
public AppConfig() {
logger.debug("App Config called by driver");
// TODO Auto-generated constructor stub
}
// Put Other Application configuration here.
}
The class 'AsynchAQMessageSvcImpl' implements 'AsynchAQMessageSvc' which was generated using WSDL. It's wired to a OSB proxy on weblogic and get's called whenever the proxy gets a JMS message on a AQ queue.
It's annotated with #Service. I tried to autowire ProcessWsPayload in it but when i use it i get a null pointer exception. Printing it's value is also null.
package com.clarksburg.services.messaging;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import com.clarksburg.services.wsimport.AsynchAQMessageRequestType;
import com.clarksburg.services.wsimport.AsynchAQMessageResponseType;
import com.clarksburg.services.wsimport.AsynchAQMessageSvc;
import com.clarksburg.services.services.ProcessWsPayload;
#Service
#WebService(name = "AsynchAQMessageSvc", targetNamespace = "http://www.clarksburg.com/AsynchAQMessageSvc")
#SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public class AsynchAQMessageSvcImpl implements AsynchAQMessageSvc {
#Autowired
ProcessWsPayload processWsPayload;
private static final Logger logger = LogManager.getLogger(AsynchAQMessageSvcImpl.class.getName());
#Override
#WebMethod(operationName = "GetAsynchAQMessage", action = "http://www.clarksburg.com/AsynchAQMessageSvc")
#WebResult(name = "AsynchAQResponse", targetNamespace = "http://www.clarksburg.com/AsynchAQMessageSvc", partName = "response")
public AsynchAQMessageResponseType getAsynchAQMessage(
#WebParam(name = "AsynchAQRequest", targetNamespace = "http://www.clarksburg.com/AsynchAQMessageSvc", partName = "request")
AsynchAQMessageRequestType request) {
logger.debug("processWsPayload is " +processWsPayload);
processWsPayload.processWsPayload(request);
// new ProcessWsPayload().processWsPayload(request);
AsynchAQMessageResponseType response = new AsynchAQMessageResponseType();
response.setServiceResponse("Success");
return response;
}
}
ProcessWsPayload.java: It's called (successfully) by 'AsynchAQMessageSvcImpl'. It's annotated as #Component.
package com.clarksburg.services.services;
import java.math.BigInteger;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import com.clarksburg.services.configuration.JDBCConfig;
#Component
public class ProcessWsPayload {
private static final Logger logger = LogManager.getLogger(ProcessWsPayload.class.getName());
#Autowired
JDBCConfig jdbcConfig;
/*
* This method is called by a JMS listener. It is being invoked successfully.
* However the value of 'jdbcConfig' is null
*/
public void processWsPayload(wsPayload request) {
BigInteger wsAuditId = request.getAuditId();
system.out.println("The value of JDBCConfig is " +jdbcConfig);
try {
String SQL = "UPDATE AUDIT_TBL SET ws_descriptions = ? WHERE ws_audit_id = ?";
JDBCConfig.jdbcTemplate().update(SQL, "Success with soap send", wsAuditId);
} catch (Exception e) {
logger.debug("Failed to update table with success");
logger.error(e);
}
}
I turned on Spring's TRACE logs and have pasted lines pertaining to Autowiring below.
Spring's relevant autowired logs during deployment:-
InjectionMetadata - Registered injected element on class [com.multiplan.services.messaging.AsynchAQMessageSvcImpl]: AutowiredFieldElement for com.multiplan.services.services.ProcessWsPayload com.multiplan.services.messaging.AsynchAQMessageSvcImpl.processWsPayload
InjectionMetadata - Processing injected element of bean 'AsynchAQMessageSvcImpl': AutowiredFieldElement for com.multiplan.services.services.ProcessWsPayload com.multiplan.services.messaging.AsynchAQMessageSvcImpl.processWsPayload
InjectionMetadata - Registered injected element on class [com.multiplan.services.services.ProcessWsPayload]: AutowiredFieldElement for com.multiplan.services.configuration.JDBCConfig com.multiplan.services.services.ProcessWsPayload.jdbcConfig
InjectionMetadata - Processing injected element of bean 'processWsPayload': AutowiredFieldElement for com.multiplan.services.configuration.JDBCConfig com.multiplan.services.services.ProcessWsPayload.jdbcConfig
AutowiredAnnotationBeanPostProcessor - Autowiring by type from bean name 'processWsPayload' to bean named 'JDBCConfig'
AutowiredAnnotationBeanPostProcessor - Autowiring by type from bean name 'AsynchAQMessageSvcImpl' to bean named 'processWsPayload'

Spring validation for RequestBody parameters bound to collections in Controller methods

I have
An Entity:
package org.ibp.soq;
public class MyEntity {
private String field1;
private String field2;
//..getters and setters
}
Validator for the entity:
package org.ibp.soq;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
#Component
public class MyEntityValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MyEntity.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
MyEntity myEntity = (MyEntity) target;
// Logic to validate my entity
System.out.print(myEntity);
}
}
and
The REST controller with bulk PUT method:
package org.ibp.soq;
import java.util.List;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/myEntity")
public class MyEntityRestResource {
#Autowired
private MyEntityValidator myEntityValidator;
#InitBinder
protected void initBinder(final WebDataBinder binder) {
binder.addValidators(this.myEntityValidator);
}
#RequestMapping(method = RequestMethod.PUT)
public void bulkCreate(#RequestBody #Valid List<MyEntity> myEntities) {
// Logic to bulk create entities here.
System.out.print(myEntities);
}
}
When I make a PUT request to this resource with following request body:
[
{
"field1": "AA",
"field2": "11"
},
{
"field1": "BB",
"field2": "22"
}
]
The error I get is:
"Invalid target for Validator [org.ibp.soq.MyEntityValidator#4eab617e]: [org.ibp.soq.MyEntity#21cebf1c, org.ibp.soq.MyEntity#c64d89b]"
I can understand that this is because MyEntityValidator "supports" single MyEntity validation, not validation for ArrayList<MyEntity>.
MyEntityValidator works perfectly if I have single MyEntity object in request body and a corresponding controller method with #RequestBody #Valid MyEntity myEntity parameter.
How can the validator setup I have used, be extended for supporting validation of collection of MyEntity's ?
The solution is to create a custom Validator for Collection and a #ControllerAdvice that registers that Validator in the WebDataBinders.
Validator:
import java.util.Collection;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
/**
* Spring {#link Validator} that iterates over the elements of a
* {#link Collection} and run the validation process for each of them
* individually.
*
* #author DISID CORPORATION S.L. (www.disid.com)
*/
public class CollectionValidator implements Validator {
private final Validator validator;
public CollectionValidator(LocalValidatorFactoryBean validatorFactory) {
this.validator = validatorFactory;
}
#Override
public boolean supports(Class<?> clazz) {
return Collection.class.isAssignableFrom(clazz);
}
/**
* Validate each element inside the supplied {#link Collection}.
*
* The supplied errors instance is used to report the validation errors.
*
* #param target the collection that is to be validated
* #param errors contextual state about the validation process
*/
#Override
#SuppressWarnings("rawtypes")
public void validate(Object target, Errors errors) {
Collection collection = (Collection) target;
for (Object object : collection) {
ValidationUtils.invokeValidator(validator, object, errors);
}
}
}
ControllerAdvice:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
/**
* Controller advice that adds the {#link CollectionValidator} to the
* {#link WebDataBinder}.
*
* #author DISID CORPORATION S.L. (www.disid.com)
*/
#ControllerAdvice
public class ValidatorAdvice {
#Autowired
protected LocalValidatorFactoryBean validator;
/**
* Adds the {#link CollectionValidator} to the supplied
* {#link WebDataBinder}
*
* #param binder web data binder.
*/
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.addValidators(new CollectionValidator(validator));
}
}
As you might have guessed this cannot be achieved using Spring Validation. Spring Validation implements Bean Validation(JSR 303/349) as opposed to Object validation. Unfortunately a collection is not a Java Bean. You have two options
Wrap your list inside a Java Bean
Call the validator manually in your bulk create method myEntityValidator. validate(targetObject, errors).
Actually, this can be achieved using Spring Validation and JSR303.
Expose a MethodValidationPostProcessor bean.
Annotate your controller class with #Validated (org.springframework.validation.annotation.Validated)
Use the JSR303 validation annotations on your MyEntity fields/methods.
Annotate your RequestBody argument with #Valid (you've already done this in your example).
Add an #ExceptionHandler method to handle MethodArgumentNotValidException. This can be done in the controller or in a #ControllerAdvice class.

Spring autowired...pass source class to autowired class

Caught up in a weird requirement. I need to attach unique error id to log4j message and return that message id back to interface.So, I though lets create a spring service, like this
public class LoggingService {
protected static Logger logger = LoggerFactory.getLogger(LoggingService.class);
public String debug(String debug_msg)
{
String uniqueMsgId = generateUniqueId();
logger.debug(concatIdWithMsg(uniqueMsgId, debug_msg));
return uniqueMsgId;
}
}
and autowired this to wherever i need it.
public class LoginLogoutController {
#Autowired
LoggingService logger;
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String getLoginPage()
{
logger.debug("Login page requested");
}
}
Although it worked fine, but the source class in logger msg is LoggingService which is obvious. What i want is to pass the class in which LoggingService is autowired so that the logger message shows the original source of problem. I tried somehow to change the service
but got no further idea how to pass source class
public class LoggingService<T> {
protected static Logger logger = null;
Class<T> sourceClass;
public void construct(Class<T> sourceClass)
{
this.sourceClass = sourceClass;
logger = LoggerFactory.getLogger(sourceClass);
}
public String debug(String debug_msg)
{
String uniqueMsgId = generateUniqueId();
logger.debug(concatIdWithMsg(uniqueMsgId, debug_msg));
return null;
}
}
I used this mechanism to inject a logger.
Create this annotation..
/**
* Indicates InjectLogger of appropriate type to
* be supplied at runtime to the annotated field.
*
* The injected logger is an appropriate implementation
* of org.slf4j.Logger.
*/
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Retention(RUNTIME)
#Target(FIELD)
#Documented
public #interface InjectLogger {
}
Now lets define a class that actually does the job of injecting the logger implementation.
/**
* Auto injects the underlying implementation of logger into the bean with field
* having annotation <code>InjectLogger</code>.
*
*/
import java.lang.reflect.Field;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.util.ReflectionUtils;
import static org.springframework.util.ReflectionUtils.FieldCallback;
public class LoggerInjector implements BeanPostProcessor {
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
public Object postProcessBeforeInitialization(final Object bean,
String beanName) throws BeansException {
ReflectionUtils.doWithFields(bean.getClass(), new FieldCallback() {
public void doWith(Field field) throws IllegalArgumentException,
IllegalAccessException {
// make the field accessible if defined private
ReflectionUtils.makeAccessible(field);
if (field.getAnnotation(InjectLogger.class) != null) {
Logger log = LoggerFactory.getLogger(bean.getClass());
field.set(bean, log);
}
}
});
return bean;
}
}
Using it is even simpler. Just add the Logger annotation created above to the Log field in the required class.
import org.slf4j.Logger;
public class Demo {
#InjectLogger
private Logger log;
public void doSomething() {
log.info("message");
log.error("Lets see how the error message looks...");
}
}
Why dont you use Spring AOP. AOP provides you much accessibility and features, and you can exploit its interesting features later also, when your application becomes heavy. Spring AOP

Resources