I am doing a Spring Boot Project and using the OpenCSV library to parse some csvs into POJOs to be persisted to db.
OpenCSV uses the annotation @CsvCustomBindByName
to map a CSV field to a Java object.
The converter = DepartmentConverter.class
is a custom converter that is instantiated with:
Class<? extends AbstractBeanField<T,K>>.newInstance()
by the library, at runtime.
The problem is that because the custom field converter is instantiated reflectively by the OpenCSV library, it cant autowire beans because it is not registered in the Spring Context.
How can i make that dynamically instantiated converter be aware of the Spring context or the other way around. Some kind of interceptor? Thanks!
//Spring Managed class
public class Specialization {
@CsvCustomBindByName(required = true, converter = DepartmentConverter.class)
private Department department;
....
}
In my DepartmentConverter i need to use a Spring JPARepository to retrieve some data. DepartmentRepository can not be autowired.
@Component
public class DepartmentConverter extends AbstractBeanField<Department, String> {
@Autowired
private DepartmentRepository departmentRepository;
public DepartmentConverter() {
}
@Override protected Object convert(String val) throws CsvConstraintViolationException, ResourceNotFoundException {
//use departmentRepository
...
}
}
The newInstance()
call you're referring to is in the HeaderColumnNameMappingStrategy
class, which calls the instantiateCustomConverter()
method to do the newInstance()
call.
Create a subclass and override the method:
@Override
protected BeanField<T, K> instantiateCustomConverter(Class<? extends AbstractBeanField<T, K>> converter) throws CsvBadConverterException {
BeanField<T, K> c = super.instantiateCustomConverter(converter);
// TODO autowire here
return c;
}
As can be seen in this answer to Spring @Autowired on a class new instance, you can do the autowiring as follows:
autowireCapableBeanFactory.autowireBean(c);
So the subclass would be something like:
public class AutowiredConverterMappingStrategy extends HeaderColumnNameMappingStrategy {
private final AutowireCapableBeanFactory beanFactory;
public AutowiredConverterMappingStrategy(AutowireCapableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
protected BeanField<T, K> instantiateCustomConverter(Class<? extends AbstractBeanField<T, K>> converter) throws CsvBadConverterException {
BeanField<T, K> c = super.instantiateCustomConverter(converter);
this.beanFactory.autowireBean(c);
return c;
}
}
To use it, you'd need something like this:
@Component
class MyComponent {
@Autowired
private AutowireCapableBeanFactory beanFactory;
public <T> List<T> parseCsvToBean(Reader reader, Class<? extends T> type) {
return new CsvToBeanBuilder(reader)
.withType(type)
.withMappingStrategy(new AutowiredConverterMappingStrategy(this.beanFactory))
.build()
.parse();
}
}
That is of course just an example. Your CsvToBean
setup may be more complex, but the key part is the withMappingStrategy()
call, and that the code is itself in a Spring Bean, so it has access to the bean factory.