3

I am implementing AWS lambda function creating handler using Spring Cloud function AWS Adapter SpringBootRequestHandler. The functional bean registered in GenericApplicationContext is invoked, but autowiring of component class is giving NullPointer Exception.

I have tried @ComponentScan for base package at Spring Application.

Application class:

@Slf4j
@SpringBootApplication
@ComponentScan({ "com.poc.evthub" })
@EnableConfigurationProperties(EventHubProperties.class)
public class EventHubServerlessApplication implements ApplicationContextInitializer<GenericApplicationContext> {

    public EventHubServerlessApplication() {
    }

    public static void main(String[] args) throws Exception {
        FunctionalSpringApplication.run(EventHubServerlessApplication.class, args);
    }

    @Bean
    public KinesisEventFunction ingestEvents() {
        return new KinesisEventFunction();
    }

    @Override
    public void initialize(GenericApplicationContext context) {

        log.debug("========  initialize  ========");

        context.registerBean("ingestEvents", FunctionRegistration.class,
                () -> new FunctionRegistration<Function<KinesisEvent, ApiResponse>>(ingestEvents())
                        .type(FunctionType.from(KinesisEvent.class).to(ApiResponse.class).getType()));
    }
}

Handler:

public class KinesisEventHandler extends SpringBootRequestHandler<KinesisEvent, ApiResponse> {

}

Functional Bean:

package com.poc.evthub.function;

import com.amazonaws.services.lambda.runtime.events.KinesisEvent;
import com.poc.evthub.beans.ApiResponse;
import com.poc.evthub.constant.Constants;
import com.poc.evthub.service.IngestionServiceFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.function.Function;

@Slf4j
@Component
public class KinesisEventFunction implements Function<KinesisEvent, ApiResponse> {

    private Context context = null;

    @Autowired
    private IngestionServiceFactory ingestionServiceFactory;

    @Autowired
    @Qualifier("targetExecutionContext")
    public void setContext(Context context) {
        log.info("Context: {}", context);
        this.context = context;
    }

    @Override
    public ApiResponse apply(final KinesisEvent kinesisEvent) {

        log.info("KinesisEventFunction apply called...");

        String sourceDomain = System.getenv(Constants.SYSENV.SOURCE_DOMAIN);
        log.info("Source Domain = {}", sourceDomain);

        if(null == kinesisEvent || null == kinesisEvent.getRecords()) {
            log.error("Event contains no data. {}", System.lineSeparator());
            //TODO build response NOT FOUND
            return null;
        }
        else
            log.info("Received {}  records from {}. {}",
                    kinesisEvent.getRecords().size(),                
 kinesisEvent.getRecords().get(0).getEventSourceARN(),
                    System.lineSeparator());

        log.info("ingestionServiceFactory = {}",ingestionServiceFactory);
        ingestionServiceFactory.ingest();

        return null;
    }
}

Complete code and pom is uploaded at: https://github.com/rjavaria/eventhub-serverless

KinesisEventFunction apply called... Also able to read the environment value(source_domain) from lambda, and receiving Kinesis event record.

But @Autowired ingestionServiceFactory is null. I am injecting this component bean to delegate the business logic.

What is missed here, so spring is not able to inject this component bean?

Thanks in advance!

rjavaria
  • 31
  • 3
  • I'd suspect `@ComponentScan`. Not sure it includes `com.poc.evthub.service`. – lexicore Aug 25 '19 at 11:05
  • Thanks lexicore. ComponentScan not a problem, Components are auto-wired, if i don't register the function as Functional bean and don't implement ApplicationContextInitializer. Works fine if it is run as SpringApplication, and not FunctionalSpringApplication and not implementing ApplicationContextInitializer. public static void main(String[] args) throws Exception { SpringApplication.run(EventHubServerlessApplication.class, args); } – rjavaria Aug 26 '19 at 01:55

1 Answers1

0

You could try manual injection of your IngestionServiceFactory bean into your function class via constructor or setter injection.

In your function class, add a constructor and remove @Autowired from your IngestionServiceFactory field, like:

...
public class KinesisEventFunction implements Function<KinesisEvent, ApiResponse> {

...
    // No @Autowired here...
    private final IngestionServiceFactory ingestionServiceFactory;
...
    // The new constructor here...
    public KinesisEventFunction(final IngestionServiceFactory pIngestionServiceFactory) {
        this.ingestionServiceFactory = pIngestionServiceFactory;
    }
...

}

Then in your main class (the one implementing ApplicationContextInitializer<GenericApplicationContext>), pass the reference to the factory when registering the function bean, like:

...
public class EventHubServerlessApplication implements ApplicationContextInitializer<GenericApplicationContext> {

...
    @Autowired
    private IngestionServiceFactory ingestionServiceFactory;
...
    @Bean
    public KinesisEventFunction ingestEvents() {
        return new KinesisEventFunction(this.ingestionServiceFactory);
    }

    @Override
    public void initialize(GenericApplicationContext context) {

        log.debug("========  initialize  ========");

        context.registerBean("ingestEvents", FunctionRegistration.class,
                () -> new FunctionRegistration<>(ingestEvents())
                        .type(FunctionType.from(KinesisEvent.class).to(ApiResponse.class).getType()));
    }
}

Or, as you are already manually registering the function bean with context.registerBean(), you could just:

...
public class EventHubServerlessApplication implements ApplicationContextInitializer<GenericApplicationContext> {

...
    @Autowired
    private IngestionServiceFactory ingestionServiceFactory;
...
    @Override
    public void initialize(GenericApplicationContext context) {

        log.debug("========  initialize  ========");

        context.registerBean("ingestEvents", FunctionRegistration.class,
                () -> new FunctionRegistration<>(new KinesisEventFunction(this.ingestionServiceFactory))
                        .type(FunctionType.from(KinesisEvent.class).to(ApiResponse.class).getType()));
    }
}

Please, let me know if it works!

JAVA
  • 1
  • 1
  • not OP, but I'm in pretty much the same situation, and the setter-injected `IngestionServiceFactory` is coming in null for me. any clues as to what's happening? – Lawrence Lee Oct 14 '19 at 22:40