HowTo Dependency-Injection with Lombok - java

I'm searching for a elegant way, to define fields in standard spring-service.
Without lombok our service looks like this:
#Service
public class ServiceA {
private final String applicationName;
private final SpecialHandler specialHandler;
private final ServiceC serviceC;
public ServiceA(final ConfigBean config,
final ServiceB serviceB,
final ServiceC serviceC) {
this.applicationName = config.getBaseConfig().getApplicationInfo().getName();
this.specialHander = serviceB.getSpecialForAppName(this.applicationName);
// PROBLEM: each direct dependency forces us to write more and more manual code
this.serviceC = serviceC;
}
}
Now, our team want to use the lombok-constructor only (so we can easily add other services). The service above will now look this:
#Service
#RequiredArgsConstructor
public class ServiceA {
private final ServiceC service;
// ^- with lombok, this is very pretty and simpel
private final ConfigBean config;
private final SpecialHandler specialHandler;
// ^- PROBLEM: these fields only used in the "createFields()"-method
// can we inline them somehow?
private String applicationName;
private SpecialHandler specialHanlder;
// ^- PROBLEM: these fields are not final anymore
// can we "make fields final again"?
#PostConstruct
public void createFields() { // maybe we can put parameters to the post-construct?
this.applicationName = this.config.getBaseConfig().getApplicationInfo().getName();
this.specialHander = this.serviceB.getSpecialForAppName(this.applicationName);
}
}
Question
How can I solve the issues (mentioned in the sourceCode-comments)?
Footnote
I saw this "problem" in many projects. The variants, which I mentioned above, are the only solutions I saw yet. Both solutions getting more ugly when raising the number of fields.

Lombok only writes boilerplate code for you. It means that, as soon as you have anything non trivial to do, you can't use it anymore.
So if you want to keep your fields computed in the constructor final, you will have to write the constructor by hand.
As far as I know, a #PostConstruct method can't accept any parameter either.
There are two possibles tracks to follow:
you can certainly use field-based or setter-based injection, using #Autowired annotation.
Pass an already constructed SpecialHandler to your service. You can do so, , rather than using #Service annotation, by creating it in a #Bean method inside a #Configuration class for example.

Related

Bean named 'documentProcessorApiService' is expected to be of type 'DocumentProcessorApiService' but was actually of type 'com.sun.proxy.$Proxy146'

I have been battling this problem for a few days, using other suggestions I've found to no avail. I've tried using #EnableAspectJAutoProxy(proxyTargetClass = true) on the SpringApplication entry and the Configuration classes. I have spring.aop.auto.true and spring.aop.proxy-target-class.true in my application yml file and still the same error. I have tried setting it as an interface with no luck as well. I have this as the Component that is being complained about
#Component
#EnableRetry
#Slf4j
public class DocumentProcessorApiService {
private final RestTemplate documentsApiRestTemplate;
private final DocumentsApiProperties documentsApiProperties;
private final MulesoftProperties mulesoftProperties;
private static final String DOCUMENT_SEARCH_PATH = "/documents/_search";
private static final String PROPS_NAME_VALUE_MAP_FIELD = "propsNameValueMap";
private static final String DOCUMENT_TYPE_FIELD = "Document Type";
private static final String UW_ERROR_REPORT_TYPE = "UW ERROR RPT";
#Autowired
public DocumentProcessorApiService(#Qualifier("documentsApiRestTemplate") RestTemplate documentsApiRestTemplate, DocumentsApiProperties documentsApiProperties,
MulesoftProperties mulesoftProperties) {
this.documentsApiRestTemplate = documentsApiRestTemplate;
this.documentsApiProperties = documentsApiProperties;
this.mulesoftProperties = mulesoftProperties;
}
and this is what is using it as Autowired as well
#Service
#Slf4j
public class LifeWorkflowEventListener {
private final EventConverter eventConverter;
private final JmsService jmsService;
private final DocumentProcessorApiService documentProcessorApiService;
private final LifeApplicationService lifeApplicationService;
#Value("${kafka.consumer.workflow-event.action-event-type:}")
String actionEventType;
#Autowired
public LifeWorkflowEventListener(#Qualifier("lifeWorkflow") EventConverter eventConverter,
JmsService jmsService,
DocumentProcessorApiService documentProcessorApiService,
LifeApplicationService lifeApplicationService) {
this.eventConverter = eventConverter;
this.jmsService = jmsService;
log.info("documentProcessorApiService {}", documentProcessorApiService.toString());
this.documentProcessorApiService = documentProcessorApiService;
this.lifeApplicationService = lifeApplicationService;
}
I appreciate any ideas I have not tried yet, or letting me know if I'm doing something else wrong.
Thank you
This happens because Spring thinks that it has to wrap methods in DocumentProcessorApiService, for example, to handle transactions. In order to do that, it creates a proxy. If you have an interface, Spring can make the proxy return true when it's asked "are you assignment compatible with type XYZ".
Steps to fix:
Rename DocumentProcessorApiService to DocumentProcessorApiServiceImpl. Rename the file manually and fix the compile error instead of refactoring; see below.
Create an interface DocumentProcessorApiService
Make DocumentProcessorApiServiceImpl implement that interface
If you used refactoring for the rename, replace DocumentProcessorApiServiceImpl everywhere with DocumentProcessorApiService except for the name of the class itself.
Add an entry in a #Configuration bean to let Spring know how to create DocumentProcessorApiService
This works. If it doesn't, then there must be a mistake elsewhere. In this case, ask a new question with the changed code and the exact error message.
See also: Spring error "Bean named 'x' is expected to be of type 'y', but was actually of type [com.sun.proxy.$Proxy]"

My annotation #Value return null even it being used and called into component annotated classes

I'm using Spring and need some help:
I want to set one API key using application.properties instead of hardcoding it, but it always returns null. IntelliJ evaluates it correctly to the value I've set in the file.
I've already read other questions here and almost all solutions are saying that Spring can only "inject" those value anotations in managed classes, like Components, Beans, etc. That's what (think) I did and still got null!
Everything else is working as I intended. Any direction is appreciated!
My application.properties
api.someapiservice.key=08e...f
Class that uses the properties value:
#Component
public class ApiClient implements ApiClientInterface {
#Value("${api.someapiservice.key}")
private String API_KEY;
public ApiClient () {
System.out.println(API_KEY); //Returns null after spring log info: Initialized JPA EntityManagerFactory for persistence unit 'default'
...
}
Class that uses ApiClient:
#Component
public class SomeService {
private final SomeRepository someRepository;
private final ApiClient apiClient;
public PlaylistService(SomeRepository someRepository , ApiClient apiClient ) {
this.SomeRepository = SomeRepository;
this.apiClient = ApiClient;
}
Field injection can't possibly happen until after the instance is already constructed, so your #Value (or #Autowired) fields will always be null in the constructor. Move the #Value to a constructor parameter instead.
If you want to know what is the value of your #Value field on start up. You can use #PostConstruct annotation, or you can move #Value annotation on your class constructor.
private String API_KEY;
public ApiClient(#Value("${api.test.value}") String key) {
this.API_KEY= key;
System.out.println(this.API_KEY);
}
or using #PostConstruct Annotation
#Value("${api.someapiservice.key}")
private String API_KEY;
#PostConstruct
public void init() {
System.out.println(this.API_KEY);
}

Spring CGLib proxy - class variables become null

I have a filter service whose methods are profiled through the aspect. As an example I will give you a piece of code where I have a problem
#Service
public class FilterService extends AbstractService {
private static final Logger log = LoggerFactory.getLogger(FilterService.class);
#Autowired
//Proxy to profiling class
private FilterService self;
private final ItemsRepository itemsRepository;
private final Map<String, EnumFilter> enumFilters;
public FilterService(ReadWriteLock readWriteLock,
ItemsRepository itemsRepository,
CategoryRepository categoryRepository, ItemsMapper itemsMapper,
CharacteristicsRepository characteristicsRepository,
List<EnumFilter> enumFilters) {
super(readWriteLock.readLock());
this.itemsRepository = itemsRepository;
this.enumFilters = enumFilters.stream().collect(Collectors.toMap(EnumFilter::getId, y -> y));
}
#Profileable
public ItemsViewShared filterItems(#Nullable String categoryId,
#NotNull Set<String> ids,
#NotNull Lang lang,
#NotNull SortType sortType,
#NotNull FilterInfo filterInfo) {
try {
this.readLock.lock();
final ItemsViewShared itemsViewResponse = new ItemsViewShared(); //in this line inspector show this = FilterService
List<Filter> allFilters = self.initNonSpecificFilters(lang, filterInfo); //problem is here
//some code...
#Profileable
private List<Filter> initNonSpecificFilters(#NotNull Lang lang, #NotNull FilterInfo filterInfo) {
final List<NumericFilter> allNumericNonSpecific = NumericFilter.getAllNonSpecific(lang, filterInfo);
//in this line enumFilters - null
final List<EnumOptionFilter> allEnumNonSpecific = enumFilters.values().stream()
.flatMap(x -> x.getAllOptions(lang, filterInfo).stream())
.collect(Collectors.toList());
As i know, by default, If the class does not inherit the interface with at least one method, the CGlib proxy will be used and Cglib works through inheritance.
The problem is this: when I call the filterItems method from the controller, the debugger shows in this method that this - FilterService.
Further in this method another method of this class which too should be profiled is caused. In order for the proxy to work, I need self autowired. After that I called my method via self.initNonSpecificFilters and in the debugger I already see that this - FilterService$$EnhancerBySpringCGLIB and all my variables in my class is null, so I get null pointer exception.
Why so, if CGLIb seems to work through inheritance? And why in the first method(filterItems) this - was a class without CGlib, but when you call from it another method (filterItems -> initNotSpecificFilters), cglib already appears.
The problem is that dynamic proxies, no matter whether JDK interface proxies or CGLIB class proxies, only inherit
public methods (JDK, CGLIB) or
protected and package-scoped methods (CGLIB only).
Furthermore, proxies do not inherit any instance variable values because that is not their purpose, they only wrap around methods and call the originals plus maybe before and after aspect advices or interceptors.
Now your situation is as follows:
Your method initNonSpecificFilters(..) is private. I.e. if you call it upon self, you will actually still call the original method (because it is not wrapped), but the proxy's members have no values, of course.
BTW, the fact that the method is private is also why the Spring AOP aspect would not kick in for that method if you had a pointcut targeting it (with AspectJ it would be different).
Search the Spring manual for the term self-invocation, the behaviour is nicely documented.
The solution to your problem is to make the method non-private.

Is it possible to add qualifiers in #RequiredArgsConstructor(onConstructor = #__(#Autowired))?

If I wanted to use the annotation #Qualifier on a constructor dependency injection, I would have something like the following:
public class Example {
private final ComponentExample component;
#Autowired
public Example(#Qualifier("someComponent") ComponentExample component) {
this.component = component;
}
}
I know Lombok's annotations to reduce boilerplate code and don't have to include a constructor would be as follows: #RequiredArgsConstructors(onConstructor=#__(#Inject)) but this only works with properties without qualifiers.
Anyone know if it is possible to add qualifiers in #RequiredArgsConstructor(onConstructor = #__(#Autowired))?
EDIT:
It is FINALLY POSSIBLE to do so! You can have a service defined like this:
#Service
#RequiredArgsConstructor
public class SomeRouterService {
#NonNull private final DispatcherService dispatcherService;
#Qualifier("someDestination1") #NonNull private final SomeDestination someDestination1;
#Qualifier("someDestination2") #NonNull private final SomeDestination someDestination2;
public void onMessage(Message message) {
//..some code to route stuff based on something to either destination1 or destination2
}
}
Provided that you have a lombok.config file like this in the root of the project:
# Copy the Qualifier annotation from the instance variables to the constructor
# see https://github.com/rzwitserloot/lombok/issues/745
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier
This was recently introduced in latest lombok 1.18.4, I wrote about it in my blogpost, and I am proud to say I was one of the main driving forces pushing for the implementation of the feature.
The blog post where the issue is discussed in detail
The original issue on github
And a small github project to see it in action
You may use spring trick to qualify field by naming it with desired qualifier without #Qualifier annotation.
#RequiredArgsConstructor
public class ValidationController {
//#Qualifier("xmlFormValidator")
private final Validator xmlFormValidator;
I haven't test whether the accepted answer works well, but instead of create or edit lombok's config file, I think the cleaner way is rename the member variable to which name you want to qualifier.
// Error code without edit lombok config
#Service
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class Foo {
#Qualifier("anotherDao") UserDao userDao;
}
Just remove #Qualifier and change your variable's name
// Works well
#Service
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class Foo {
UserDao anotherDao;
}

How to configure providers with custom parameters?

My class depends on some services which needs to take few parameters and then make network call, currently I am passing those parameters and then creating those services via a factory injected into my class. I need to inject those services as a dependency instead, I know that I can create providers for them but in most of the examples I see that the providers are often bound to the fixed values like serveraddres etc. but I need to give then values during run time.
Below is my example code:
public SomeClass {
private final SomeFactory someFactory;
#Inject
SomeClass(SomeFactory factory) {
someFactory = factory;
}
public Foo getFoo(String fooId) {
FooService fooService = someFactory.getFooService(fooId);
return fooService.getFoo();
}
}
What I need to do is:
public SomeClass {
private final FooService fooService;
#Inject
SomeClass(FooService fooService) {
this.fooService = fooService;
}
public Foo getFoo(String fooId) {
return fooService.getFoo();
}
}
Update 1
Making the use case more clear:
#Provides
#RequestScoped
public SomeService provideSomeService(Dep1 dep1, String code) throws IOException {
return new SomeService.Builder()
.withApplicationName("Foo")
.setCode(code)
.build();
}
Here, code can be null by default and when needed I can give some value in it.
Can I somehow pass arguments to the provider before its created?
If you have a binding for your value (here, code is a String without a binding annotation), then your Update 1 is exactly what the code would look like.
In practice, there are a few differences:
Constants like int and String values are generally annotated with a binding annotation, either #Named or a custom annotation.
If you need to inject a value into an object graph after Guice initialization, but have a deep enough object graph that dependency injection is still a good idea, you can create a child injector. This way you can make a #Named("code") String accessible within one action or object, but not across your entire Guice application.
If your value for code is dynamic enough that it can't be provided through Guice as a key of its own, then you'll have to pass it in using a factory of some sort. For a Builder-based object, I'd say that your SomeFactory implementation is the best that I would come up with in your case.
If you don't need to use a Builder, and can let Guice create the object based on your fields or constructor parameters, you can code-generate a Factory.
Guice can generate a factory for you through FactoryModuleBuilder, in a feature known as "assisted injection".
Google's other tool, AutoFactory, will code-generate a factory implementation that works in both Guice and Dagger. (It's bundled as "Auto", which includes a model object generator called AutoValue that also generates annotation implementations.)
I put a small demonstration of a child injector and assisted injection in my other SO answer here.
The best approach here is to parameterize the module and pass the parameter through to a provider that you create at runtime:
public class MyModule extends AbstractModule {
private final String code;
public MyModule(String code) {
this.code = code;
}
#Override public void configure() {
Provider<Dep1> depProvider = getProvider(Dep1.class);
bind(SomeService.class)
.toProvider(() -> new SomeService.Builder()
.withApplicationName("Foo")
.withDep(depProvider.get())
.setCode(code)
.build())
.in(RequestScoped.class);
}
}

Categories

Resources