Injecting a service in a springboot aws lambda - java

I'm building a lambda based on this code
The uppercaseService is "injected" like this:
#Component("uppercaseFunction")
public class UppercaseFunction implements Function<UppercaseRequest, UppercaseResponse> {
private final UppercaseService uppercaseService;
public UppercaseFunction(final UppercaseService uppercaseService) {
this.uppercaseService = uppercaseService;
}
This works fine until I try to inject another service inside UppercaseService.
#Service
public class UppercaseService {
#Autowired
MyService myService;
public String uppercase(final String input) {
myService.doSomething();
return input.toUpperCase(Locale.ENGLISH);
}
}
AWS console returns:
"errorMessage": "Error creating bean with name 'uppercaseService':
Unsatisfied dependency expressed through field 'myService'
This service works in a non lambda context. The class is present in the .jar built with maven package.
I tried the solution # https://www.profit4cloud.nl/blog/just-spring-enabled-aws-lambdas without success.

You have to initialize your MyService bean first. Since your MyService come from external service which very likely to have different package than your own package
Either directly:
#SpringBootApplication
public class UpperFunctionApplication {
#Bean
public MyService myService() {
return new MyService(); // You must provide code to construct new MyService bean
}
public static void main(String[] args) throws Exception {
SpringApplication.run(UpperFunctionApplication.class, args);
}
}
or via componentscan:
#SpringBootApplication(scanBasePackageClasses = {UpperFunctionApplication.class, MyService.class})
public class UpperFunctionApplication {
#Bean
public MyService myService() {
return new MyService(); // You must provide code to construct new MyService bean
}
public static void main(String[] args) throws Exception {
SpringApplication.run(UpperFunctionApplication.class, args);
}
}

Related

Spring dynamic #Bean registration with SpringApplicationBuilder failing in test

Trying to register beans dynamically via SpringApplicationBuilder class and it's working when running the app, but when trying to execute the test and trying to verify that the beans are defined in the context, they fail for the dynamic bean. Feel like I have to use another "magical" annotation for the tests for them to properly load the dynamic beans.
This is the code used and if you run the tests you will see that both cases will fail. BarService will fail also because FooService is registered dynamically via builder, but if you would remove the dependency it will pass the BarService test.
SpringApp.java
class FooService {
}
#Component
class BarService {
private final FooService fooService;
BarService(FooService fooService) {
this.fooService = fooService;
}
}
#SpringBootApplication
public class SpringApp {
public static void main(String[] args) {
new SpringApplicationBuilder()
.sources(SpringApp.class)
.initializers((ApplicationContextInitializer<GenericApplicationContext>) context -> {
context.registerBean(FooService.class);
})
.run(args);
}
}
SpringAppTest.java
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = SpringApp.class)
public class SpringAppTest {
#Autowired
ApplicationContext context;
#Test
public void barService() {
Assert.assertNotNull("The barService should not be null", context.getBean(BarService.class));
}
#Test
public void contextLoads() {
Assert.assertNotNull("The fooService should not be null", context.getBean(FooService.class));
}
}
First solution
The main error here is that the test does not have the initalization logic that the main method has. Solution is to extract the logic from the initializers method
class MyInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
#Override
public void initialize(GenericApplicationContext context) {
System.out.println("Called initialize");
context.registerBean(FooService.class);
}
}
and use it in main
public static void main(String[] args) {
new SpringApplicationBuilder()
.sources(SpringApp.class)
.initializers(new MyInitializer())
.run(args);
}
and then use the MyInitializer in the test file through #ConextConfiguration
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = SpringApp.class, initializers = MyInitializer.class)
public class SpringAppTest {
// ...
}
Second (better) solution
Now, this can be cumbersome as we need to reference this initializer in every test, but there is an even better solution. We can create a specific Spring file resources/META-INF/spring.factories and put inside of it a reference to the initializer:
org.springframework.context.ApplicationContextInitializer=com.acme.orders.MyInitializer
After that, we can simplify both the main method
#SpringBootApplication
public class SpringApp {
public static void main(String[] args) {
SpringApplication.run(SpringApp.class, args);
}
}
and the tests, so that they don't need to always import the initializer.
#RunWith(SpringRunner.class)
#SpringBootTest
public class SpringAppTest {
// ...
}
Now both the main run process and the tests will have access to all the beans.

How to use #Autowired on field variable when class is not a Bean? [duplicate]

This question already has answers here:
Injecting beans into a class outside the Spring managed context
(8 answers)
Closed 3 years ago.
Suppose I have
#Service
public class myService () {
public void sayHello() {
System.out.println("Hello");
}
}
public class myTestClass() {
#Autowired
private myService thisService;
public void transferHello() {
thisService.sayHello();
}
}
public class Application() {
public static void main(String[] args) {
SpringApplication.run(PilContainerApplication.class, args);
myTestClass thisTest = new myTestClass();
thisTest.transferHello();
}
}
Since myTestClass() is not a bean like service/controller, thisService would have a null reference when I use myTestClass thisTest = new myTestClass();.
I was wondering how to overcome this.. I tried using public static myService thisService and it said cannot use #Autowired on static fields.
Thank you
When a class cannot be changed to be annotated with a Spring stereotype, #Bean is a very good alternative :
#Configuration
public class MyBeansConfiguration {
#Bean
public MyTestClass getMyTestClass(MyService myService) {
return new MyTestClass(myService);
}
}
You can inject it now in your Application class :
public class PilContainerApplication {
#Autowired
MyTestClass myTestClass;
#PostConstruct
public void init(){
myTestClass.transferHello();
}
public static void main(String[] args) {
SpringApplication.run(PilContainerApplication.class, args);
}
}
Note that beans are instances of class and are injected in other instances of class that depend on them. So you don't have access to the injected beans in the static main() method. But you have access to it in an instance method annotated with #PostConstruct that will be executed when the dependencies were injected in the current bean.
Side note : classes have to start with an uppercase. I did it in the provided code.
if you want inject bean B without marking bean A via some annotation, or xml definition, you can use SpringBeanAutowiringSupport
public class A {
#Autowired
private class B b;
public A{
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
}
}

How to run #SpringBootApplication without controller

How to run code from class with #SpringBootApplication annotation. I want to run my code without calling to controller and get info from terminal not web browser. I tried to call weatherService in #SpringBootApplication but I've got a application failed start with description
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| weatherClientApplication
↑ ↓
| weatherService defined in file [C:\Users\xxx\IdeaProjects\weatherclient\target\classes\com\xxx\restapiclient\service\WeatherService.class]
└─────┘
#SpringBootApplication
public class WeatherClientApplication {
private WeatherService weatherService;
public WeatherClientApplication(WeatherService weatherService) {
this.weatherService = weatherService;
}
private static final Logger log = LoggerFactory.getLogger(WeatherClientApplication.class);
public static void main(String[] args) {
SpringApplication.run(WeatherClientApplication.class, args);
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder){
return builder.build();
}
#Bean
public CommandLineRunner run(RestTemplate restTemplate) throws Exception {
return args -> {
log.info(weatherService.getTemperatureByCityName("Krakow"));
};
}
}
#Service
public class WeatherService {
private RestTemplate restTemplate;
public WeatherService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public String getTemperatureByCityName(String cityName) {
String url = "http://api.openweathermap.org/data/2.5/weather?q=" + cityName + "&APPID=" + API_KEY + "&units=metric";
Quote quote = restTemplate.getForObject(url, Quote.class);
return String.valueOf(quote.getMain().getTemp());
}
}
You can do this by using main method and by using ApplicationContext, In this approach you don't need any CommandLineRunner
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(WeatherClientApplication.class, args);
WeatherService service = (WeatherService)context.getBean("weatherService");
service. getTemperatureByCityName("cityname");
}
1) What you want is implementing  CommandLineRunner and define the entry point of your application in the public void run(String... args) method defined in this interface.
2) As said by Spring you have a cycle : break it with a injection outside the constructor.
Such as :
#SpringBootApplication
public class WeatherClientApplication implements CommandLineRunner{
#Autowired
private WeatherService weatherService;
//...
#Override
public void run(String... args) {
log.info(weatherService.getTemperatureByCityName("Krakow"));
}
//...
}
Generally constructor injection should be favored over field or setter injection but in your case, that is acceptable.
You are creating a cycle as you are injecting a service in the #SpringBootApplication itself. Constructor injection means that nothing can really happen until the class is built but that service is going to be created later on.
Don't use field injection on your #SpringBootApplication as it represents the root context. Your CommandLineRunner injects a RestTemplate but you are not using it. If you replace that by the WeatherService and remove the constructor injection, things should work just fine.
I am glad you find the weather application useful by the way :)

SpringBoot application doesn't autowire field

I do have ServiceImpl which looks like this:
#Service
#RequiredArgsConstructor
public class ServiceAImpl implements ServiceA {
private final String fieldA;
#Override
public boolean isFieldA(String text){
return fieldA.equals(text);
}
And I would like to inject a field value to fieldA in an Application.java from application.yml like this:
#EnableSwagger2
#SpringBootApplication
public class Application {
#Value("${fieldA}")
private String fieldA;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ServiceA serviceA() {
return new ServiceAImpl(fieldA);
}
But I receive the following error when running SpringBoot app:
Error creating bean with name 'serviceAImpl' defined in URLNo qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Do you have any solution for that?
You annotated your class with #Service and defined it manually as a bean with the #Bean annotation. I do think the second is the way you planned to use it.
The #Service annotation will make this class get picked up by Spring's component scan and additionally create an instance of it.
Of course it tries to resolve the parameters and fails when it tries to find a matching "bean" for the String field because there is no simple String bean (and should not :) ).
Remove the #Service annotation and everything should work as expected.
Try this
#Service
public class ServiceAImpl implements ServiceA {
private final String fieldA;
#Autowire
public ServiceAImpl(#Value("${fieldA}") String fieldA){
this.fieldA = fieldA;
}
#Override
public boolean isFieldA(String text){
return fieldA.equals(text);
}
}
and this
#EnableSwagger2
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
You should not use #Service and #Bean for the same class!
Spring is not so smart :)
You should annotate your bean like:
#RequiredArgsConstructor
public class ServiceAImpl {
#Value("${fieldA}")
private final String something;
...
But I'm not sure it will work with the #RequiredFieldsConstructor, it would be simpler for you write down the constructor annotated with #Autowired and using the #Value annotation for the String parameter:
#Autowired
public ServiceAImpl(#Value("${aProp}") String string) {
You're using two bean declaration mechanisms:
You're registering your bean using #Service
You're registering a bean using #Bean
This means that your service will be created twice. The one defined using #Bean works properly, since it uses the #Value annotation to inject the proper value in your service.
However, the service created due to #Service doesn't know about the #Value annotation and will try to find any bean of type String, which it can't find, and thus it will throw the exception you're seeing.
Now, the solution is to pick either one of these. If you want to keep the #Bean configuration, you should remove the #Service annotation from ServiceAImpl and that will do the trick.
Alternatively, if you want to keep the #Service annotation, you should remove the #Bean declaration, and you should write your own constructor rather than relying on Lombok because this allows you to use the #Value annotation within the constructor:
#Service
public class ServiceAImpl implements ServiceA {
private final String fieldA;
/**
* This constructor works as well
*/
public ServiceAImpl(#Value("${fieldA}") String fieldA) {
this.fieldA = fieldA;
}
#Override
public boolean isFieldA(String text){
return fieldA.equals(text);
}
}
If you want to declare ServiceAImpl as a Spring bean in your Java Configuration file, you should remove the #Service annotation from the class declaration. These annotations doesn't work well together.
ServiceAImpl.java
import org.springframework.beans.factory.annotation.Autowired;
public class ServiceAImpl implements ServiceA {
private final String fieldA;
#Autowired
public ServiceAImpl(String fieldA) {
this.fieldA = fieldA;
}
#Override
public boolean isFieldA(String text) {
return fieldA.equals(text);
}
}
Application.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
#SpringBootApplication
public class Application {
#Value("${fieldA}")
private String fieldA;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ServiceA serviceA() {
return new ServiceAImpl(fieldA);
}
}
Your application.properties
fieldA=value
The below implementation works well for me. You have two issues, first you have to choose between #Service and #Bean and the other issue I've seen in your code was the #Value annotation, you have to use only to inject a value from the properties.
#SpringBootApplication
public class TestedValueApplication {
#Autowired
void printServiceInstance(ServiceA service) {
System.out.println("Service instance: " + service);
System.out.println("value==value? " + service.isFieldA("value"));
}
public static void main(String[] args) {
SpringApplication.run(TestedValueApplication.class, args);
}
#Bean
public ServiceA serviceA(#Value("${fieldA}") String fieldA) {
return new ServiceAImpl(fieldA);
}
}
Service:
public class ServiceAImpl implements ServiceA {
private String fieldA;
ServiceAImpl(String fieldA) {
this.fieldA = fieldA;
}
public boolean isFieldA(String text) {
return fieldA.equals(text);
}
}
application.properties:
fieldA=value

Using Jersey's Dependency Injection in a Standalone application

I have a interface here
interface Idemo{
public int getDemo(int i);
}
And it's one implementation
class DemoImpl implements Idemo{
#Override
public int getDemo(int i){
return i+10;
}
}
And there is a class which has a dependency on Idemo
class Sample{
#Inject
Idemo demo;
public int getSample(int i){
return demo.getDemo(i);
}
}
Now say I want to test Sample class
public class SampleTest extends JerseyTest {
#Inject
Sample s;
#Override
protected Application configure() {
AbstractBinder binder = new AbstractBinder() {
#Override
protected void configure() {
bind(Demo.class).to(Idemo.class);
bind(Sample.class).to(Sample.class); //**doesn't work**
}
};
ResourceConfig config = new ResourceConfig(Sample.class);
config.register(binder);
return config;
}
#Test
public void test_getSample() {
assertEquals(15, s.getSample(5)); //null pointer exception
}
}
Here the Sample instance is not getting created and s remains null.I suppose this is because by the time the execution reaches line where binding is specified this test class has already been created.But I am not sure.With Spring Autowired instead of jersey CDI the same works
Had Sample been a resource/controller class the test framework would create an instance of it with no need to inject it but is it possible to test any other non-web class using Jersey DI ?
The reason it works with Spring is that the test class is managed by the Spring container by using #RunWith(SpringJUnit4ClassRunner.class). The runner will inject all managed objects into the test object. JerseyTest is not managed this way.
If you want, you can create your own runner, but you need to understand a bit how HK2 (Jersey's DI framework) works. Take a look at the documentation. Everything revolves around the ServiceLocator. In a standalone, you might see something like this to bootstrap the DI container
ServiceLocatorFactory factory = ServiceLocatorFactory.getInstance();
ServiceLocator locator = factory.create(null);
ServiceLocatorUtilities.bind(locator, new MyBinder());
Then to get the service, do
Service service = locator.getService(Service.class);
In the case of the test class, we don't need to gain any access to the service object, we can simply inject the test object, using the ServiceLocator:
locator.inject(test);
Above, test is the test class instance that gets passed to us in our custom runner. Here is the example implementation of a custom runner
import java.lang.annotation.*;
import org.glassfish.hk2.api.*;
import org.glassfish.hk2.utilities.*;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.*;
public class Hk2ClassRunner extends BlockJUnit4ClassRunner {
private final ServiceLocatorFactory factory = ServiceLocatorFactory.getInstance();
private Class<? extends Binder>[] binderClasses;
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public static #interface Binders {
public Class<? extends Binder>[] value();
}
public Hk2ClassRunner(Class<?> cls) throws InitializationError {
super(cls);
Binders bindersAnno = cls.getClass().getAnnotation(Binders.class);
if (bindersAnno == null) {
binderClasses = new Class[0];
}
}
#Override
public Statement methodInvoker(FrameworkMethod method, final Object test) {
final Statement statement = super.methodInvoker(method, test);
return new Statement() {
#Override
public void evaluate() throws Throwable {
ServiceLocator locator = factory.create(null);
for (Class<? extends Binder> c : binderClasses) {
try {
ServiceLocatorUtilities.bind(locator, c.newInstance());
} catch (InstantiationException | IllegalAccessException ex) {
throw new RuntimeException(ex);
}
}
locator.inject(test);
statement.evaluate();
locator.shutdown();
}
};
}
}
In the runner, the methodInvoker is called for every test method, so we are creating a fresh new set of objects for each test method called.
Here is a complete test case
#Binders({ServiceBinder.class})
#RunWith(Hk2ClassRunner.class)
public class InjectTest {
public static class Service {
#Inject
private Demo demo;
public void doSomething() {
System.out.println("Inside Service.doSomething()");
demo.doSomething();
}
}
public static class Demo {
public void doSomething() {
System.out.println("Inside Demo.doSomething()");
}
}
public static class ServiceBinder extends AbstractBinder {
#Override
protected void configure() {
bind(Demo.class).to(Demo.class);
bind(Service.class).to(Service.class);
}
}
#Inject
private Service service;
#Test
public void testInjections() {
Assert.assertNotNull(service);
service.doSomething();
}
}
I was facing the same situation but in the context of running some integrations test that needs to have some of the singletons that my application have already defined.
The trick that I found is the following. You just need to create a normal test class or a standalone that use the DropwizardAppRule
In my case, I use JUnit as I was writing some integration test.
public class MyIntegrationTest{
//CONFIG_PATH is just a string that reference to your yaml.file
#ClassRule
public static final DropwizardAppRule<XXXConfiguration> APP_RULE =
new DropwizardAppRule<>(XXXApplication.class, CONFIG_PATH);
}
The #ClassRule will start your application like is said here . That
means you will have access to everything and every object your application needs to start. In my case, I need to get access to a singleton for my service I do that using the #Inject annotation and the #Named
public class MyIntegrationTest {
#ClassRule
public static final DropwizardAppRule<XXXConfiguration> APP_RULE =
new DropwizardAppRule<>(XXXAplication.class, CONFIG_PATH);
#Inject
#Named("myService")
private ServiceImpl myService;
}
Running this will set to null the service as #Inject is not working because we don't have at this point anything that put the beans into the references. There is where this method comes handy.
#Before
public void setup() {
ServiceLocator serviceLocator =((ServletContainer)APP_RULE.getEnvironment().getJerseyServletContainer()).getApplicationHandler().getServiceLocator();
//This line will take the beans from the locator and inject them in their
//reference, so each #Inject reference will be populated.
serviceLocator.inject(this);
}
That will avoid creating other binders and configurations outside of the existing on your application.
Reference to the ServiceLocator that DropwizardAppRule creates can be found here

Categories

Resources