Is there a way to use core java/reflection to load create dependency objects and set it to other as a dependency?
I could do it, but the facing issues while dealing with nested beans.
Ultimately, I am not interested to use spring for the simple usage.
Any help much appreciated.
I generally use Spring or Micronaut frameworks but there may be times when manual DI makes sense - e.g. an AWS Lambda in Java which needs to be as slick / minimal and / run as quickly as possible.
You mentioned dealing with nested beans was a pain, the solution I went for is to create a single class to hold all the instances e.g. AppContext
which has a private constructor, a single static instance which is accessible like:
public class AppContext {
private static AppContext appContext;
public static AppContext getInstance() {
if (appContext == null) {
appContext = new AppContext();
}
return appContext;
}
private AppContext () {}
}
Now we can start adding our classes (Beans). If we were writing an application which e.g. performs file uploads we may have classes for service, mappers, DAO, etc:
private AppProperties appProperties;
private ObjectMapper objectMapper;
private UploadService uploadService;
private UploadMapper uploadMapper;
private UploadDao uploadDao;
private S3Client s3Client;
Each of these fields will have a no-args accessor method e.g.
public AppProperties getProperties() {
if (this.appProperties == null) {
this.appProperties = new AppProperties();
}
return this.appProperties;
}
public ObjectMapper getObjectMapper() {
if (this.objectMapper == null) {
return JsonMapper
.builder()
.addModule(new JavaTimeModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.build();
}
return this.objectMapper;
}
public UploadService getUploadService() {
if (this.uploadService == null) {
this.uploadService = new UploadService(getUploadDao(), getUploadMapper());
}
return this.uploadService;
}
}
All these instances are lazy loaded and some classes are very simple e.g. AppProperties
and ObjectMapper
but others e.g. UploadService
requires 2 injected classes/interfaces UploadDao
/ UploadMapper
, these will have further methods too e.g.
public UploadDao getUploadDao() {
if (this.uploadDao == null) {
this.uploadDao = new UploadDaoS3(getS3Client());
}
return this.uploadDao;
}
public S3Client getS3Client() {
if (this.s3Client == null) {
S3ClientBuilder s3ClientBuilder = S3Client.builder();
// additional config ...
this.s3Client = s3ClientBuilder.build();
}
return this.s3Client;
}
public UploadMapper getUploadMapper() {
if (this.uploadMapper == null) {
this.uploadMapper = new UploadMapperImpl();
}
return this.uploadMapper;
}
As you can see the UploadDao
, UploadMapper
are interfaces and UploadDaoS3
and UploadMapperImpl
are the implementations.
To access any of these instances (normally the high level service class) is as simple as:
UploadService service = AppContext.getInstance().getUploadService();
service.uploadFile( ... );
Note, all the examples use constructor injection but we can of course use setter injection too for optional fields.
This solution is manual and reflection free but will work if speed / absolute minimum number of libraries is important.