Invocation of Spring Cloud AWS Messaging package causes dependent beans to be null - spring-boot

I have a Spring Boot project that I'm using to receive events from an Amazon SQS queue. I've been using the Spring Cloud AWS project to make this easier.
The problem is this: the Spring Boot application starts up just fine, and appears to instantiate all the necessary beans just fine. However, when the method that is annotated with SqsListener is invoked, all the event handler's dependent beans are null.
Another thing that's important to note: I have two methods of propagating the event: 1) thru a POST web service call, and 2) thru the Amazon SQS. If I choose to run the event as a POST call with the same data in the POST body, it works just fine. The injected dependencies are only ever null whenever the SQSListener method is invoked by the SimpleMessageListenerContainer.
Classes:
#Service("systemEventsHandler")
public class SystemEventsHandler {
// A service that this handler depends on
private CustomService customService;
private ObjectMapper objectMapper;
#Autowired
public SystemEventsHandler(CustomService customService, ObjectMapper objectMapper) {
this.matchStatusSvc = matchStatusSvc;
this.objectMapper = objectMapper;
}
public void handleEventFromHttpCall(CustomEventObject event) {
// Whenever this method is called, the customService is
// present and the method call completes just fine.
Assert.notNull(objectMapper, "The objectMapper that was injected was null");
customService.handleEvent(event);
}
#SqsListener(value = "sqsName", deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS)
private void handleEventFromSQSQueue(#NotificationMessage String body) throws IOException {
// Whenever this method is called, both the objectMapper and
// the customService are null, causing the invocation to
// fail with a NullPointerException
CustomEventObject event = objectMapper.readValue(body, CustomEventObject.class);
matchStatusSvc.scoresheetUploaded(matchId);
}
}
The controller (for when I choose to run the event as a POST). As stated above, it works just fine whenever I run it as a POST call.
#RestController
#RequestMapping("/events")
public class SystemEventsController {
private final SystemEventsHandler sysEventSvc;
#Autowired
public SystemEventsController(SystemEventsHandler sysEventSvc) {
this.sysEventSvc = sysEventSvc;
}
#RequestMapping(value = "", method = RequestMethod.POST)
public void handleCustomEvent(#RequestBody CustomEventObject event) {
sysEventSvc.handleEventFromHttpCall(event);
}
}
Pertinent config:
#Configuration
public class AWSSQSConfig {
#Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer(AmazonSQSAsync amazonSQS) {
SimpleMessageListenerContainer msgListenerContainer = simpleMessageListenerContainerFactory(amazonSQS).createSimpleMessageListenerContainer();
msgListenerContainer.setMessageHandler(queueMessageHandler(amazonSQS));
return msgListenerContainer;
}
#Bean
public SimpleMessageListenerContainerFactory simpleMessageListenerContainerFactory(AmazonSQSAsync amazonSQS) {
SimpleMessageListenerContainerFactory msgListenerContainerFactory = new SimpleMessageListenerContainerFactory();
msgListenerContainerFactory.setAmazonSqs(amazonSQS);
msgListenerContainerFactory.setMaxNumberOfMessages(10);
msgListenerContainerFactory.setWaitTimeOut(1);
return msgListenerContainerFactory;
}
#Bean
public QueueMessageHandler queueMessageHandler(AmazonSQSAsync amazonSQS) {
QueueMessageHandlerFactory queueMsgHandlerFactory = new QueueMessageHandlerFactory();
queueMsgHandlerFactory.setAmazonSqs(amazonSQS);
QueueMessageHandler queueMessageHandler = queueMsgHandlerFactory.createQueueMessageHandler();
return queueMessageHandler;
}
#Bean(name = "amazonSQS", destroyMethod = "shutdown")
public AmazonSQSAsync amazonSQSClient() {
AmazonSQSAsyncClient awsSQSAsyncClient = new AmazonSQSAsyncClient(new DefaultAWSCredentialsProviderChain());
return awsSQSAsyncClient;
}
}
Other info:
Spring boot version: Dalston.RELEASE
Spring cloud AWS version:
1.2.1.RELEASE
Both the spring-cloud-aws-autoconfigure and spring-cloud-aws-messaging packages are on the classpath
Any thoughts?

As spencergibb suggested in his comment above, changing the method's visibility from private to public worked.

Related

Mockito Spy quartz MethodInvokingJobDetailFactoryBean target job bean failed

Spring 6, Quartz, and a SimpleTrigger based scheduled task.
#Component
#Slf4j
public class Greeting {
public void sayHello() {
log.debug("Hello at {}:", LocalDateTime.now());
}
}
Quartz config:
#Configuration
class QuartzConfig{
#Bean
MethodInvokingJobDetailFactoryBean greetingJobDetailFactoryBean() {
var jobFactory = new MethodInvokingJobDetailFactoryBean();
jobFactory.setTargetBeanName("greeting");
jobFactory.setTargetMethod("sayHello");
return jobFactory;
}
#Bean
public SimpleTriggerFactoryBean simpleTriggerFactoryBean() {
SimpleTriggerFactoryBean simpleTrigger = new SimpleTriggerFactoryBean();
simpleTrigger.setJobDetail(greetingJobDetailFactoryBean().getObject());
simpleTrigger.setStartDelay(1_000);
simpleTrigger.setRepeatInterval(5_000);
return simpleTrigger;
}
#Bean
public SchedulerFactoryBean schedulerFactoryBean() {
var factory = new SchedulerFactoryBean();
factory.setTriggers(
simpleTriggerFactoryBean().getObject(),
cronTriggerFactoryBean().getObject()
);
return factory;
}
And I tried to use awaitility to check the invocations.
#SpringJUnitConfig(value = {
QuartzConfig.class,
Greeting.class
})
public class GreetingTest {
#Autowired
Greeting greeting;
Greeting greetingSpy;
#BeforeEach
public void setUp() {
this.greetingSpy = spy(greeting);
}
#Test
public void whenWaitTenSecond_thenScheduledIsCalledAtLeastTenTimes() {
await()
.atMost(Duration.ofSeconds(10))
.untilAsserted(() -> verify(greetingSpy, atLeast(1)).sayHello());
}
}
Running the tests, it is failed.
org.awaitility.core.ConditionTimeoutException: Assertion condition defined as a com.example.demo.GreetingTest
Wanted but not invoked:
greeting.sayHello();
-> at com.example.demo.GreetingTest.lambda$whenWaitTenSecond_thenScheduledIsCalledAtLeastTenTimes$0(GreetingTest.java:36)
Actually, there were zero interactions with this mock.
within 10 seconds.
In the jobDetailFactorBean, I used jobFactory.setTargetBeanName("greeting"); to setup the target beans here, it should pass the Greeting bean directly.
Updated: resolved myself, check here.
You're creating a spy that in no way interacts with the actual code:
#BeforeEach
public void setUp() {
this.greetingSpy = spy(greeting);
}
This would have to be injected into the Spring context as a bean and used everywhere, where greeting is used. Spring actually provides such functionality: #SpyBean.
Instead of autowiring a greeting and wrapping it with a spy that does not interact with anything in the context, replace the #Autowired with #SpyBean annotation. Thanks to that a spy bean will be created and injected within the Spring context:
#SpyBean
Greeting greeting;
I created a commit in GitHub repository, where you can see the whole code - the test passes. I had to add the cronTriggerFactoryBean() method to the configuration as it is omitted in your question.
If you cannot use Spring Boot, you can create the spy within Spring context yourself using configuration:
static class Config {
#Bean
#Primary
Greeting greeting() {
return spy(new Greeting());
}
}
Thanks to that when you inject the bean, it will be possible to act on it with Mockito (remember to include the Config class in the #SpringJUnitConfig annotation).
I created another commit in the GitHub repository - the test passes. You can see the whole code there.

Spring AOP with prototype beans

I am using Spring AOP to fire metrics in our application. I have created an annotation #CaptureMetrics which has an #around advice associated with it. The advice is invoked fine from all the methods tagged with #CaptureMetrics except for a case when a method is invoked on a prototype bean.
The annotation has #Target({ElementType.TYPE, ElementType.METHOD})
PointCut expression:
#Around(value = "execution(* *.*(..)) && #annotation(captureMetrics)",
argNames = "joinPoint,captureMetrics")
Prototype bean creation
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public DummyService getDummyServicePrototypeBean(int a, String b) {
return new DummyService(a, b);
}
DummyService has a method called dummyMethod(String dummyString)
#CaptureMetrics(type = MetricType.SOME_TYPE, name = "XYZ")
public Response dummyMethod(id) throws Exception {
// Do some work here
}
When dummyService.dummyMethod("123") is invoked from some other service, the #Around advice is not called.
Config class
#Configuration
public class DummyServiceConfig {
#Bean
public DummyServiceRegistry dummyServiceRegistry(
#Value("${timeout}") Integer timeout,
#Value("${dummy.secrets.path}") Resource dummySecretsPath) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Map<String, String> transactionSourceToTokens = mapper.readValue(
dummySecretsPath.getFile(), new TypeReference<Map<String, String>>() {
});
DummyServiceRegistry registry = new DummyServiceRegistry();
transactionSourceToTokens.forEach((transactionSource, token) ->
registry.register(transactionSource,
getDummyServicePrototypeBean(timeout, token)));
return registry;
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public DummyService getDummyServicePrototypeBean(int a, String b) {
return new DummyService(a, b);
}
}
Singleton Registry class
public class DummyServiceRegistry {
private final Map<String, DummyService> transactionSourceToService = new HashMap<>();
public void register(String transactionSource, DummyService dummyService) {
this.transactionSourceToService.put(transactionSource, dummyService);
}
public Optional<DummyService> lookup(String transactionSource) {
return Optional.ofNullable(transactionSourceToService.get(transactionSource));
}
}
Any advice on this please?
Note:
The prototype Dummy service is used to call a third party client. It is a prototype bean as it has a state that varies based on whose behalf it is going to call the third party.
A singleton registry bean during initialization builds a map of {source_of_request, dummyService_prototype}. To get the dummyService prototype it calls getDummyServicePrototypeBean()
The configuration, registry and prototype dummy bean were correct.
I was testing the flow using an existing integration test and there instead of supplying a prototype Bean, new objects of DummyService were instantiated using the new keyword. It wasn't a spring managed bean.
Spring AOP works only with Spring managed beans.

Spring Disable #Transactional from Configuration java file

I have a code base which is using for two different applications. some of my spring service classes has annotation #Transactional. On server start I would like to disable #Transactional based on some configuration.
The below is my configuration Class.
#Configuration
#EnableTransactionManagement
#PropertySource("classpath:application.properties")
public class WebAppConfig {
private static final String PROPERTY_NAME_DATABASE_DRIVER = "db.driver";
#Resource
private Environment env;
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getRequiredProperty(PROPERTY_NAME_DATABASE_DRIVER));
dataSource.setUrl(url);
dataSource.setUsername(userId);
dataSource.setPassword(password);
return dataSource;
}
#Bean
public PlatformTransactionManager txManager() {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
if(appName.equqls("ABC")) {
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_NEVER);
}else {
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
}
CustomDataSourceTransactionManager txM=new CustomDataSourceTransactionManager(def);
txM.setDataSource(dataSource());
return txM;
}
#Bean
public JdbcTemplate jdbcTemplate() {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource());
return jdbcTemplate;
}
}
I am trying to ovveried methods in DataSourceTransactionManager to make the functionality. But still it is trying to commit/rollback the transaction at end of transaction. Since there is no database connection available it is throwing exception.
If I keep #Transactional(propagation=Propagation.NEVER), everything works perfectly, but I cannot modify it as another app is using the same code base and it is necessary in that case.
I would like to know if there is a to make transaction fully disable from configuration without modifying #Transactional annotation.
I'm not sure if it would work but you can try to implement custom TransactionInterceptor and override its method that wraps invocation into a transaction, by removing that transactional stuff. Something like this:
public class NoOpTransactionInterceptor extends TransactionInterceptor {
#Override
protected Object invokeWithinTransaction(
Method method,
Class<?> targetClass,
InvocationCallback invocation
) throws Throwable {
// Simply invoke the original unwrapped code
return invocation.proceedWithInvocation();
}
}
Then you declare a conditional bean in one of #Configuration classes
// assuming this property is stored in Spring application properties file
#ConditionalOnProperty(name = "turnOffTransactions", havingValue = "true"))
#Bean
#Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor(
/* default bean would be injected here */
TransactionAttributeSource transactionAttributeSource
) {
TransactionInterceptor interceptor = new NoOpTransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource);
return interceptor;
}
Probably you gonna need additional configurations, I can't verify that right now

Vertx instance variable is null in Spring context

I defined a Spring Boot App as a Verticle as follows:
#SpringBootApplication
public class SpringAppVerticle extends AbstractVerticle {
private Vertx myVertx;
#Override
public void start() {
SpringApplication.run(SpringAppVerticle.class);
System.out.println("SpringAppVerticle started!");
this.myVertx = vertx;
}
#RestController
#RequestMapping(value = "/api/hello")
public class RequestController {
#RequestMapping(method = RequestMethod.GET, produces = "application/json")
public void getEcho() {
JsonObject message = new JsonObject()
.put("text", "Hello world!");
myVertx.eventBus().send(EchoServiceVerticle.ADDRESS, message, reply -> {
JsonObject replyBody = (JsonObject) reply.result().body();
System.out.println(replyBody.encodePrettily());
});
}
}
}
I have a second non-Spring Verticle that is basically a echo service:
public class EchoServiceVerticle extends AbstractVerticle {
public static final String ADDRESS = "echo-service";
#Override
public void start() {
System.out.println("EchoServiceVerticle started!");
vertx.eventBus().consumer(EchoServiceVerticle.ADDRESS, message -> {
System.out.println("message received");
JsonObject messageBody = (JsonObject) message.body();
messageBody.put("passedThrough", "echo-service");
message.reply(messageBody);
});
}
}
The problem is that I get a nullpointer at line myVertx.eventbus().send in SpringAppVerticle class as the myVertx variable is null.
How do I properly instantiate a Vertx variable in a Spring context in order that I can exchange message between my both verticles?
My project can be found here: https://github.com/r-winkler/vertx-spring
The reason of the exception is the following:
SpringAppVerticle bean that is created during spring init is another object than starts the spring boot application. So you have two objects, one that has start() method invoked and another one that doesn't. Second one actually handles requests. So what you need is to register verticles as spring beans.
For samples of vertx/spring interoperability please refer to vertx examples repo.
P.S. I've created a pull request to your repo to make your example work.

How to Create or configure Rest Template using #Bean in Spring Boot

I want to define RestTemplate as an application bean using #Bean annotation in my configuration class in a spring boot application.
I am calling 4 rest services in different places in my application flow. Currently I am creating RestTemplate every time every request. Is there a way I can define that as application bean using #Bean and inject that using #Autowired?
Main reason for this question is I can able to define RestTemplate using #Bean but when I inject it with #Autowired I am loosing all defined interceptors (Interceptors are not getting called.)
Configuration Class
#Bean(name = "appRestClient")
public RestTemplate getRestClient() {
RestTemplate restClient = new RestTemplate(
new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add(new RestServiceLoggingInterceptor());
restClient.setInterceptors(interceptors);
return restClient;
}
Service Class
public class MyServiceClass {
#Autowired
private RestTemplate appRestClient;
public String callRestService() {
// create uri, method response objects
String restResp = appRestClient.getForObject(uri, method, response);
// do something with the restResp
// return String
}
}
It seems my Interceptors are not getting called at all with this configuration. But RestTemplate is able to make a call to the REST service and get a response.
Answer for Spring boot 2.*.* version.
I am using Spring boot 2.1.2.RELEASE and I also added RestTemplate in my project in a class where mail method exists.
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.setConnectTimeout(Duration.ofMillis(300000))
.setReadTimeout(Duration.ofMillis(300000)).build();
}
and Used in my service or other classes like this
#Autowired
RestTemplate res;
and in methods
HttpEntity<String> entity = new HttpEntity<>(str, headers);
return res.exchange(url, HttpMethod.POST, entity, Object.class);
Judging form the name of the interceptor, I'm guessing you're doing some logging in it? You could of missed logging level configuration. I created a small application to check weather your configuration works, using 1.3.6.RELEASE version.
In this class I define the RestTemplate bean and the interceptor with logging.
package com.example;
// imports...
#SpringBootApplication
public class TestApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(TestApplication.class);
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Bean(name = "appRestClient")
public RestTemplate getRestClient() {
RestTemplate restClient = new RestTemplate(
new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
// Add one interceptor like in your example, except using anonymous class.
restClient.setInterceptors(Collections.singletonList((request, body, execution) -> {
LOGGER.debug("Intercepting...");
return execution.execute(request, body);
}));
return restClient;
}
}
For logging to work, I also have to set the correct debug level in application.properties.
logging.level.com.example=DEBUG
Then I create a service where I inject this RestTemplate.
#Service
public class SomeService {
private final RestTemplate appRestClient;
#Autowired
public SomeService(#Qualifier("appRestClient") RestTemplate appRestClient) {
this.appRestClient = appRestClient;
}
public String callRestService() {
return appRestClient.getForObject("http://localhost:8080", String.class);
}
}
And also an endpoint to test this out.
#RestController
public class SomeController {
private final SomeService service;
#Autowired
public SomeController(SomeService service) {
this.service = service;
}
#RequestMapping(value = "/", method = RequestMethod.GET)
public String testEndpoint() {
return "hello!";
}
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String test() {
return service.callRestService();
}
}
By performing a GET request to http://localhost:8080/test I should expect to get the String hello! getting printed (the service makes a call to http://localhost:8080 which returns hello! and sends this back to me). The interceptor with logger also prints out Intercepting... in the console.
Edd's solution won't work if you're using Spring Boot 1.4.0 or later. You will have to use RestTemplateBuilder to get this working. Here is the example
#Bean(name="simpleRestTemplate")
#Primary
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){
RestTemplate template = restTemplateBuilder.requestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()))
.interceptors(logRestRequestInterceptor) //This is your custom interceptor bean
.messageConverters(new MappingJackson2HttpMessageConverter())
.build();
return template;
}
Now you can autowire the bean into your service class
#Autowired
#Qualifier("simpleRestTemplate")
private RestTemplate simpleRestTemplate;
Hope this helps

Resources