Constructor Injection together with JDA and Spring Boot [duplicate] - java

This question already has answers here:
Why is my Spring #Autowired field null?
(21 answers)
Closed last year.
I am struggling with initializing JDA as the "addEventListeners" requires me to input the EventListeners.. However, my EventListeners have "injected constructors" (if that's the correct word) that is grabbing my Dao's.
I would gladly use #Autowire in my EventListeners, but it keeps on giving me a NullPointer.
I think the issue is that JDA extends their EventListener, which basically loads outside of Spring Boot, even though I've added the #Service annotation on the Listener.
You can see my problem below at:
.addEventListeners(new JDAEventListener())
Obviously, I am not able to do new JDAEventListener()as it requires the WixSubscriptionDao. However, I am not able to understand how to initiate the JDAEventListener without WixSubscriptionDao, as I need the Dao for further data handling.
public static void main(String[] args) throws InterruptedException, LoginException {
JDA jda = JDABuilder.createDefault("XXXXXXXXXXXXXXXXXXXXXX")
.enableIntents(GatewayIntent.GUILD_MESSAGES, GatewayIntent.GUILD_MEMBERS)
.setChunkingFilter(ChunkingFilter.ALL)
.setMemberCachePolicy(MemberCachePolicy.ALL)
.addEventListeners(new JDAEventListener())
.build();
DISCORD_CONSTANT.jda = jda.awaitReady();
setupDefault();
}
#Service
public class JDAEventListener implements EventListener {
private final WixSubscriptionDao wixSubscriptionDao;
public JDAEventListener(WixSubscriptionDao wixSubscriptionDao) {
this.wixSubscriptionDao = wixSubscriptionDao;
}
#Override
public void onEvent(#NotNull GenericEvent genericEvent) {
if (genericEvent instanceof ReadyEvent) {
System.out.println("ReadyEvent done");
ArrayList<WixSubscription> wixSubscriptions = wixSubscriptionDao.findAll();
System.out.println(wixSubscriptions.size());
}
}
I would love to do this, but as wrote above, the #Autowired Dao is giving me a NullPointer, even though it's defined in the SpringConfig. (The DAO works perfectly when using the constructor method)
#Service
public class JDAEventListener implements EventListener {
#Autowired
private WixSubscriptionDao wixSubscriptionDao;
public JDAEventListener() {
}
#Override
public void onEvent(#NotNull GenericEvent genericEvent) {
if (genericEvent instanceof ReadyEvent) {
System.out.println("ReadyEvent done");
ArrayList<WixSubscription> wixSubscriptions = wixSubscriptionDao.findAll();
System.out.println(wixSubscriptions.size());
}
}

I suggest that you create class annotated with #Component which implements CommandLineRunner interface. This means that, the run method will be executed when the application starts. Also, you can inject other Spring beans into it, like for example JDAEventListener beans.
#Component
public class JDAInitializer implements CommandLineRunner {
private final JDAEventListener jdaEventListener;
// Constructor injection
public JDAInitializer(JDAEventListener jdaEventListener) {
this.jdaEventListener = jdaEventListener;
}
#Override
public void run(String... args) throws Exception {
JDA jda = JDABuilder.createDefault("XXXXXXXXXXXXXXXXXXXXXX")
.enableIntents(GatewayIntent.GUILD_MESSAGES, GatewayIntent.GUILD_MEMBERS)
.setChunkingFilter(ChunkingFilter.ALL)
.setMemberCachePolicy(MemberCachePolicy.ALL)
.addEventListeners(jdaEventListener)
.build();
DISCORD_CONSTANT.jda = jda.awaitReady();
setupDefault();
}
...
}

Related

To call Strava API from SpringBoot application

I'm trying to develop my first java Spring Bboot app that calls Strava API and gets my activities for the given period of time.
I've registered my app on Strava's website and got client_id and client secret.
I've generated spring-swagger-codegen-api-client and awtowired the client to the app.
#Configuration
public class StravaIntegrationConfiguration {
#Bean
public ActivitiesApi stravaApi(){
return new ActivitiesApi(apiClient());
}
#Bean
public ApiClient apiClient(){
return new ApiClient();
}
}
Then I use this bean in AdapterClass
public class Adapter {
#Autowired
private static ActivitiesApi activitiesApi;
public static void getActivities(Integer before, Integer after, Integer page, Integer perPage) {
final List<SummaryActivity> loggedInAthleteActivities = activitiesApi.getLoggedInAthleteActivities(before, after, page, perPage);
System.out.println("ВСЕГО АКТИВНОСТЕЙ"+ loggedInAthleteActivities.size());
}
}
#SpringBootApplication
#Import(StravaIntegrationConfiguration.class)
public class App {
public static void main(String[] args) throws SQLException {
SpringApplication.run(App.class);
Adapter.getActivities(1636130496, 1635529296, 1, 30);
}
}
When I run this code I get NPE, because activitiesApi is null.
What is the problem? Please kindly advise.
Does it concern authentication? Could you advise also any code sample on how to make Strava authentication in my app?
It has nothing to do with Strava authentication. It is related to Spring context and Spring Beans and how to inject them. As already mentioned you can't autowire Spring-managed beans in static fields (it makes no sense actually). Having said that you need to fix that first:
#Component
public class Adapter {
#Autowired
private ActivitiesApi activitiesApi;
public void getActivities(Integer before, Integer after, Integer page, Integer perPage) {
final List<SummaryActivity> loggedInAthleteActivities = activitiesApi.getLoggedInAthleteActivities(before, after, page, perPage);
System.out.println("ВСЕГО АКТИВНОСТЕЙ"+ loggedInAthleteActivities.size());
}
}
Also, note that the method changed from a static one to an instance one and that the annotation #Component was added to the class. The reason is that a Spring-managed bean can only be injected into other Spring-managed beans.
Additionally, it seems to me that you are trying to do something after the Spring context has been initialized. One possible way to do this is creating a bean that implements the ApplicationListener interface:
#Component
public class StartupApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
private Adapter adapter;
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
adapter.getActivities(1636130496, 1635529296, 1, 30);
}
}
This means that you can and you should remove the line Adapter.getActivities(1636130496, 1635529296, 1, 30); from your main class:
#SpringBootApplication
#Import(StravaIntegrationConfiguration.class)
public class App {
public static void main(String[] args) throws SQLException {
SpringApplication.run(App.class);
}
}
Finally, and as a side note, please consider using constructor injection instead of field injection. It has a couple of advantages over field injection: making the class easier to unit test, allowing the objects to be immutable, explicitly definition of which dependencies are mandatory, etc... (you can read more at https://reflectoring.io/constructor-injection/).

KafkaListener and __listener #KafKaListener can't resolve 'null' with Aspect Annotation

I'm using #KafkaListener and I need a dynamic topic name so I use the SpEL '__listener' in order to do that
#PostConstruct
public void init() {
myProps= generateTopicDynamically();
}
#KafkaListener(topics = "#{__listener.myProps}")
public void listenerKafka(#Payload MyObject myObject) {
//Do something with my event
}
It works perfectly well.
The main issue is when I want to add another annotation that trigger some Aspect programmation
#MyCustomAnnotationToRecordPerformance
#KafkaListener(topics = "#{__listener.myProps}")
public void listenerKafka(#Payload MyObject myObject)
and here the aspect class
#Aspect
#Configuration
#Slf4j
public class MyCustomAnnotationToRecordPerformanceAspect {
#Pointcut("#annotation(MyCustomAnnotationToRecordPerformance)")
public void annotationMyCustomAnnotationToRecordPerformance() {
}
#Around("annotationMyCustomAnnotationToRecordPerformance()")
public Object doSomething(final ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
return proceedingJoinPoint.proceed();
}
}
I have this issue because Spring try to resolve __listener before #PostConstruct has been called.
Caused by: java.lang.IllegalArgumentException: #KafKaListener can't resolve 'null' as a String
at org.springframework.kafka.annotation.KafkaListenerAnnotationBeanPostProcessor.resolveAsString(KafkaListenerAnnotationBeanPostProcessor.java:648)
at org.springframework.kafka.annotation.KafkaListenerAnnotationBeanPostProcessor.resolveTopics(KafkaListenerAnnotationBeanPostProcessor.java:520)
at org.springframework.kafka.annotation.KafkaListenerAnnotationBeanPostProcessor.processListener(KafkaListenerAnnotationBeanPostProcessor.java:419)
at org.springframework.kafka.annotation.KafkaListenerAnnotationBeanPostProcessor.processKafkaListener(KafkaListenerAnnotationBeanPostProcessor.java:370)
at org.springframework.kafka.annotation.KafkaListenerAnnotationBeanPostProcessor.postProcessAfterInitialization(KafkaListenerAnnotationBeanPostProcessor.java:298)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:431)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1800)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:595)
... 41 common frames omitted
I tried to debug it
We can see lot of CGLIB reference, so bean has been already proxified, but all properties are null. So I supposed Autowired and PostConstruct method has not been called yet
For now, I tried to delay the processor that manage #KafkaListener, but I was not able to find where I can change that without have to redefine fully Kafka configuration
#EnableKafka import KafkaListenerConfigurationSelector that is DeferredImportSelector.
Here the comment on this class
A {#link DeferredImportSelector} implementation with the lowest order to import a {#link KafkaBootstrapConfiguration} as late as possible.
So I supposed it already delay as late as possible based on the comment
I test it with #Transactional, and I have the same issue.
#Transactional
#KafkaListener(topics = "#{__listener.myProps}")
public void listenerKafka(#Payload MyObject myObject)
Do have any idea about it?
The only alternative I see for now is split my class in 2 and create 2 beans.
KafkaListener method call the other bean. But I found very strange to have to do that.
Thanks in advance for you help.
I just tested it with #Transactional and it works as expected for me - I have confirmed that we already have a CGLIB proxy by the time we get to the #KafkaListener annotation BPP...
#SpringBootApplication
#EnableTransactionManagement
public class So69817946Application {
public static void main(String[] args) {
SpringApplication.run(So69817946Application.class, args);
}
#Bean
public NewTopic topic() {
return TopicBuilder.name("so69817946").partitions(1).replicas(1).build();
}
}
#Component
class listener {
public String getTopic() {
return "so69817946";
}
#Transactional
#KafkaListener(id = "so69817946", topics = "#{__listener.topic}")
public void listen(String in) {
System.out.println(in);
}
}
#Component
class TM extends AbstractPlatformTransactionManager {
#Override
protected Object doGetTransaction() throws TransactionException {
return new Object();
}
#Override
protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
}
#Override
protected void doCommit(DefaultTransactionStatus status) throws TransactionException {
}
#Override
protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
}
}
so69817946: partitions assigned: [so69817946-0]
And I can see the transaction interceptor in the call stack.
So, yes, an MCVE would be helpful.
Thanks to the help of Gary, I found the solution.
Once we have aspect, the class is proxified and properties became null in the CGLIB object.
We need to call getter in order to have the value from original object, not the proxified one
SpEL is able to read public getter that will be executed on the original object, and not the CGLIB one
So the solution was simply to create a public getter for my private
public String getMyProps(){
return this.myProps;
}
Thanks all.

Sharing an instance of a class across a spring boot application

I have a particular class used to interface with a service that requires initialization. In the application lifecycle, the only place this makes sense is in the start of the application because the rest of the spring application cannot run without it. I had the idea to do this:
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
try {
MyRequiredService mrs = new MyRequiredService();
mrs.connect(); // This will throw if it fails
run(MyApplication.class, args);
} catch(MyException e) {
System.out.println("Failed to connect to MyRequiredService!");
}
}
}
This will launch the service and attempt to connect but I have one big problem. How do I pass this class around the application? I need it's functions in the service endpoints I am writing.
I didn't see anything obvious and searching "passing class instance in spring boot application" turns up a bunch of unrelated topics.
Is there a smart, clean way to do this in spring boot? I apologize for a contrived example. The names of the service are unique enough I didn't want to violate any agreements.
You can make Spring do this for you. First, you need to annotate your class with #Service, so Spring will pick it up when scanning for classes.
Then, define an init() method and annotate it with #PostConstruct. Spring will instantiate your MyRequiredService class and call init()
#Service
public class MyRequiredService {
#PostConstruct
public void init() {
connect();
}
public void connect() {
// ...
}
}
You could call connect() from the constructor, but I don't like to define objects that may throw exceptions out of the constructor.
And then, you can use MyRequiredService in some other class by injecting it via the #Autowired annotation:
#Component
public class MyOtherClass {
private final MyRequiredService service;
public MyOtherClass(final MyRequiredService service) {
this.service = service;
}
// Other methods here.
}
This has the same overall effect as what you're trying to do above. If MyRequiredService fails, the application will not start up.
Make it a bean. Then it will be in the ApplicationContext which then you can pass to your desired other classes through the constructor
#Configuration
public class ApplicationConfiguration
{
#Bean
public MyRequiredService myRequiredService()
{
MyRequiredService mrs = new MyRequiredService();
try {
mrs.connect(); // This will throw if it fails
return mrs;
} catch(MyException e) {
log.error("Failed to connect to MyRequiredService!");
throw new IllegalStateException("MyRequiredService failed connection. Stopping startup");
}
}
#Bean
public SomeOtherService someOtherService(MyRequiredService mrs) {
return new SomeOtherService(mrs);
}
}
IMHO Instead of catching the error and logging it. I would throw it and stop the application from starting, but to keep with your example I added the throw IllegalStateException after the log.
Doing it this way Spring will create your MyRequiredService bean in the ApplicationContext then you can see I added as a parameter needed by the bean below that. Spring will grab that bean out of the ApplicationContext and supply it to the bean. If Spring doesn't find the bean in the ApplicationContext it will throw an error and stop the application from startup.
a class implements BeanFactoryPostProcessor which is init before normal bean
#Configuration
public class MyRequiredService implements BeanFactoryPostProcessor,
PriorityOrdered, InitializingBean {
#Override
public int getOrder() {
return Integer.MIN_VALUE;
}
public void connect() {
// ...
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
#Override
public void afterPropertiesSet() throws Exception {
connect();
}
}

Injecting mock before Spring's post-construct phase

Basically, the question is in the title.
I faced a problem that in post-construct phase my bean (that is autowired in the bean that is going through post-construct phase right now) is already mocked, but all the behavior described by Mockito.when() doesn't work, all the calls return null.
While searching I found this solution.
But is it possible to make it work without using any 3rd party libraries?
Test class:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#ContextConfiguration(classes = TestApplicationConfiguration.class)
public class ServiceTest {
#Autowired
#Qualifier("test")
private PCLPortType pclPortType;
#MockBean
private ClearingHelper сlearingHelper;
#MockBean
private OrganizationCacheRepository organizationCacheRepository;
#Before
public void setup() throws Exception{
OperationResultWithOrganizationSystemIdMappingList res = new OperationResultWithOrganizationSystemIdMappingList();
when(clearingHelper.getOrgIdSystemIdMapping(any(Keycloak.class))).thenReturn(res);
}
#Test
public void test() throws Exception{
pclPortType.call("123");
}
}
Test config:
#TestConfiguration
public class TestApplicationConfiguration {
#Bean(name = "test")
public PCLPortType pclPortTypeForTest() throws JAXBException {
...
}
#Bean
public Keycloak keycloak() {
return Mockito.mock(Keycloak.class);
}
}
Component where I want to get mocked beans:
#Component
public class OrganizationCacheJob {
private static final Logger logger =
LogManager.getLogger(OrganizationCacheJob.class);
private final ObjectFactory<Keycloak> factory;
private final ClearingHelper clearingHelper;
private final OrganizationCacheRepository organizationCacheRepository;
#Autowired
public OrganizationCacheJob(ObjectFactory<Keycloak> factory,
ClearingHelper clearingHelper,
OrganizationCacheRepository organizationCacheRepository) {
this.factory = factory;
this.clearingHelper = ClearingHelper;
this.organizationCacheRepository = organizationCacheRepository;
}
#PostConstruct
public void updateCacheRepository() {
doUpdateCacheRepository();
}
#Scheduled(cron = "${organization.cache.schedule}")
public void start() {
logger.info("Starting update organization cache.");
doUpdateCacheRepository();
logger.info("Job finished.");
}
private void doUpdateCacheRepository() {
try {
Keycloak keycloak = factory.getObject();
OperationResultWithOrganizationSystemIdMappingList orgIdSystemIdMapping = clearingHelper.getOrgIdSystemIdMapping(keycloak);
if (orgIdSystemIdMapping != null) {
orgIdSystemIdMapping.getContent().forEach(o -> organizationCacheRepository.saveOrgIdsSystemsIdsMappings(o.getOrgId(), o.getId()));
logger.debug("Was saved {} orgIds", orgIdSystemIdMapping.getContent().size());
}
} catch (Exception e) {
logger.error("Error fetching whole mapping for org and systems ids. Exception: {}", e);
}
}
}
So, in post-construct phase of OrganizationCacheJob I want to get res when calling clearingHelper, but instead I get null.
ClearingHelper is a regular Spring bean marked as a #Component with public methods.
Ahh ok I just realized - when you start your test case, whole env is up and running first, then you advance to testing phase. So, translating to your case - first you got injection and post-constructs called, then #Before method is done, thus the result.
So as you can see, code says more than all the words you could put in your original post.
If it is possible for you, use spies insteed of mocks. If it is not possible to construct that, you will have to redesign your tests to not rely on post construct.
In this case, since you want the same post-construct behavior for every test case, provide own factory method for given mock (like you did with keycloak) and move when-doReturn there. It will be guaranteed that it will happen before post construct.

Spring Boot: how to inject dependencies into a class called by a library?

I'm using Kinesis Client Library (KCL) and Spring boot. To use KCL, I have to implement a class (I named it RecordProcessor) for interface IRecordProcessor. And KCL will call this class and process records from kinesis. But when I tried to use dependency injection, I found it was not succeeded.
Here's the snippet for RecordProcessor:
#Component
public class RecordProcessor implements IRecordProcessor {
#Autowired
private SingleRecordProcessor singleRecordProcessor;
#Override
public void initialize(String shardId) {
...
}
#Override
public void processRecords(List<Record> records, IRecordProcessorCheckpointer checkpointer) {
...
}
}
I use Class SingleRecordProcessor to process single each record from kinesis. And this is my SingleRecordProcessor class snippet:
#Component
public class SingleRecordProcessor {
private Parser parser;
private Map<String, Table> tables;
public SingleRecordProcessor() {
}
#Autowired
private void setParser(Parser parser) {
this.parser = parser;
}
#Autowired
private void setTables(Map<String, Table> tables) {
this.tables = tables;
}
public void process(String record) {
...
}
}
I want to let spring framework automatically inject the SingleRecordProcessor instance into the class and use it. But I found that the field singleRecordProcessor is null.
Any idea why the dependency injection is failed? Or is it impossible to inject dependencies into a class which is called by other framework (in this case it's KCL)? Any suggestions will be appreciated! Really need some help please!!
[UPDATE]:
Sorry for not expressing the error clearly. The error was NullPointerException. I tried to inject singleRecordProcessor and call method process() on it. I think the injection was not successful so the instance singleRecordProcessor is null and there comes the NullPointerException.
More information is as follows:
I have a major class called Application
#SpringBootApplication
public class Application{
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application.class);
application.addListeners(new ApplicationPidFileWriter("./app.pid"));
ConfigurableApplicationContext ctx = application.run(args);
}
}
And I have the MainProcessor class which will call KCL.
#Service
public final class MainProcessor {
#EventListener(ApplicationReadyEvent.class)
public static void startConsumer() throws Exception {
init();
IRecordProcessorFactory recordProcessorFactory = new RecordProcessorFactory();
Worker worker = new Worker(recordProcessorFactory, kinesisClientLibConfiguration);
...
worker.run(); // this line will call KCL library and eventually call ProcessorRecord class.
}
}
[UPDATE2]
RecordProcessorFactory only has one method like this
#Component
public class RecordProcessorFactory implements IRecordProcessorFactory {
#Autowired
RecordProcessor recordProcessor;
#Override
public IRecordProcessor createProcessor() {
return recordProcessor;
}
}
It creates a new RecordProcessor instance for KCL to use it.
You should autowire an instance of this into your MainProcessor:
#Component
public class RecordProcessorFactory {
#Lookup IRecordProcessor createProcessor() { return null; }
}
Spring will instantiate a RecordProcessorFactory for you, and replace the implementation of createProcessor() in it with one that will return a new IRecordProcessor each time it's called. Both the factory and the processors will be Spring beans - which is what you want.

Categories