I have this ApplicationContextProvider class defined along with the MyApplication.java (entry point where the application is run):
package com.company.my.app;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
#Component
public class ApplicationContextProvider implements ApplicationContextAware {
private ApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public ApplicationContext getContext() {
return applicationContext;
}
}
Have the package restapi with two classes in it (Greeting is just a class to hold data):
package com.company.my.app.restapi;
import com.company.my.app.ApplicationContextProvider;
import io.micrometer.core.instrument.Counter;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class GreetingController {
private static final Logger LOG = LoggerFactory.getLogger(GreetingController.class);
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value="name", defaultValue="World") String name) {
ApplicationContextProvider acp = new ApplicationContextProvider();
ApplicationContext context = acp.getContext();
if (context == null) LOG.info("app context is NULL");
Counter bean = context.getBean(Counter.class);
bean.increment();
return new Greeting(counter.incrementAndGet(),
String.format(template, name));
}
}
Finally the MyApplication class is:
package com.company.my.app;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.binder.MeterBinder;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Counter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#SpringBootApplication
public class MyApplication {
#Bean
public MeterBinder exampleMeterBinder() {
return (meterRegistry) -> Counter.builder("my.counter")
.description("my simple counter")
.register(meterRegistry);
}
#Configuration
public class CounterConfig {
#Bean
public Counter simpleCounter(MeterRegistry registry) {
return registry.counter("my.counter");
}
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
When I run the app and call http://localhost:8081/greeting in my browser, it crashes printing app context is NULL. How do I get the application context? I need it to retrieve the simple counter bean.
tl;dr: You don't need the context; there's a better way.
ApplicationContextAware is an artifact from much older versions of Spring, before many of the now-standard features were available. In modern Spring, if you need the ApplicationContext, just inject it like any other bean. However, you almost certainly shouldn't interact with it directly, especially for getBean, which should be replaced with injecting whatever you were getting.
In general, when you need a Spring bean, you should declare it as a constructor parameter. (If you have multiple constructors, you need to annotate one with #Autowired, but if there's only a single constructor, Spring is smart enough to know to use it.) If you're using Lombok, you can use #Value to automatically write the constructor, and Groovy and Kotlin have similar features.
In the specific case of Micrometer, which you're showing here, it is not conventional to declare individual metrics as beans because they are fine-grained tools intended to apply to specific code paths. (Some services might have 10 separate metrics to track various possible scenarios.) Instead, you inject the MeterRegistry and select the counters or other metrics that you need as part of your constructor. Here, your controller class should look like this. (I've eliminated the duplicate AtomicLong, but you could add it back in as you showed if there's a specific reason you need it.)
#RestController
public class GreetingController {
private static final Logger LOG = LoggerFactory.getLogger(GreetingController.class);
private static final String template = "Hello, %s!";
private final Counter counter;
public GreetingController(MeterRegistry meterRegistry) {
counter = meterRegistry.counter("my.counter");
}
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value="name", defaultValue="World") String name) {
counter.increment();
long count = (long) counter.count();
return new Greeting(count, String.format(template, name));
}
}
Related
I have specific instances that I need to use for autowiring in an AnnotationConfigWebApplicationContext. Below is the code that demonstrates the error. My case is much more complicated, of course, where the instances are determined at runtime. I discover the instances at spring configuration time which is why if the below coded worked, it would be fine. I tried but it throws NoSuchBeanDefinitionException even though ctx.getBean is able to find it. What's the right way to accomplish this?
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
class Scratch {
static class MyClass {
}
#Configuration
static class TestConfig {
#Autowired
private MyClass myClass;
}
public static void main(String[] args) {
MyClass requiredInstance = new MyClass();
final AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.refresh(); // Throws "BeanFactory not initialized..." on next line if this not here
ctx.getBeanFactory().registerSingleton("myClass", requiredInstance);
// Also tried: ((DefaultListableBeanFactory)ctx.getAutowireCapableBeanFactory()).registerSingleton("myClass", requiredInstance);
System.out.println(ctx.getBean(MyClass.class)); // Outputs "Scratch$MyClass#1804f60d"
ctx.register(TestConfig.class);
ctx.refresh(); // Throws "NoSuchBeanDefinitionException: No qualifying bean of type 'Scratch$MyClass' available:"
}
}
Found a solution here https://blog.pchudzik.com/201705/dynamic-beans/ . I had seen BeanFactoryPostProcessor mentioned elsewhere but they didn't show how to create the instance. This example showed using the factory mechanism that is what I needed.
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
class Scratch {
static class MyClass {
}
#Configuration
static class TestConfig {
#Autowired
private MyClass myClass;
}
public static void main(String[] args) {
final AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(TestConfig.class);
ctx.register(DynamicBeanFactoryProcessor.class);
ctx.refresh();
System.out.println(ctx.getBean("myClass")); // Outputs "Scratch$MyClass#1804f60d"
System.out.println(ctx.getBean(MyClass.class)); // Outputs "Scratch$MyClass#1804f60d"
}
#Configuration
public static class DynamicBeanFactoryProcessor implements BeanFactoryPostProcessor {
public <T> T createInstance(Class<T> c) {
return c.cast(new MyClass()); // Create dynamic instance here
}
#Bean
public DynamicBeanFactoryProcessor dynamicBeanFactoryProcessor() {
return new DynamicBeanFactoryProcessor();
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
((BeanDefinitionRegistry)beanFactory).registerBeanDefinition("myClass",
BeanDefinitionBuilder.genericBeanDefinition(MyClass.class)
.setFactoryMethodOnBean("createInstance", "dynamicBeanFactoryProcessor")
.addConstructorArgValue(MyClass.class)
.getBeanDefinition());
}
}
}
Bean creation is supposed to be a Singleton in the spring container, correct? I am migrating a Spring configuration file to Spring Boot with Annotations. I have the below code, but it seems to call the "mySingletonBean()" every time that it is used within another bean creation. It is my understanding that the #Bean annotation is supposed to be Singleton by default. Am I creating my Beans correctly?
#Bean
public SomeBean mySingletonBean() {
SomeBean mybean = new SomeBean();
mybean.setName = "Name";
return mybean;
}
#Bean
public Bean1 bean1() {
Bean1 bean1 = new Bean1();
bean1.setBean(mySingletonBean());
return bean1;
}
#Bean
public Bean2 bean2() {
Bean2 bean2 = new Bean2();
bean2.setBean(mySingletonBean());
return bean2;
}
Spring is proxying your application context class, and takes care of all the context related stuff like instantiating, caching etc. of the beans.
Run this small test, feel free to debug to see what class the configuration class became:
package stackoverflow;
import java.util.Arrays;
import java.util.Date;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.assertTrue;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = Test.MyContext.class)
public class Test {
#Autowired
private ApplicationContext applicationContext;
#Autowired
#Qualifier("myStringBean")
private String myStringBean;
#Autowired
private SomeBean someBean;
#Configuration
static class MyContext {
#Bean
public String myStringBean() {
return String.valueOf(new Date().getTime());
}
#Bean
public SomeBean mySomeBean() {
return new SomeBean(myStringBean());
}
}
#org.junit.Test
public void test() {
assertTrue(myStringBean == applicationContext.getBean("myStringBean"));
assertTrue(myStringBean == someBean.getValue());
System.out.println(Arrays.asList(applicationContext.getBean("myStringBean"), myStringBean, someBean.getValue()));
}
static class SomeBean {
private String value;
public SomeBean(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
}
Spring Boot is smart framework and method mySingletonBean() will starts only once time, don't worry about it. U can start debug mode and see .
We are currently using Spring Boot to connect to a mocked local instance of Amazon SQS. The application itself is working when run, but we would like to try and test the SQS Config class, if possible and if it makes sense.
Here is the configuration class. All properties are pulled from the typical application.properties file when the Spring application itself is run.
import com.amazonaws.services.sqs.AmazonSQSAsync;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.aws.core.env.ResourceIdResolver;
import org.springframework.cloud.aws.messaging.core.QueueMessagingTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class AWSSQSConfig {
#Value("${aws.sqs.endpoint}")
private String AWSSqsEndpoint;
// Producer QueueMessageTemplate
#Bean
public QueueMessagingTemplate queueMessagingTemplate(AmazonSQSAsync amazonSqs, ResourceIdResolver resourceIdResolver) {
if (!AWSSqsEndpoint.isEmpty())
amazonSqs.setEndpoint(AWSSqsEndpoint);
return new QueueMessagingTemplate(amazonSqs, resourceIdResolver);
}
}
Here is the test class. We are attempting to pass the configuration in via TestPropertySource, but they don't actually seem to get to the AWSSQSConfig class. AWSSqsEndpoint inside the instance of the class is always NULL.
import com.amazonaws.services.sqs.AmazonSQSAsync;
import com.lonewolf.formsbuilder.config.AWSSQSConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.aws.core.env.ResourceIdResolver;
import org.springframework.cloud.aws.messaging.core.QueueMessagingTemplate;
import org.springframework.test.context.TestPropertySource;
import static org.junit.Assert.assertNotNull;
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
#TestPropertySource(properties = {
"cloud.aws.region.static=us-east-1",
"cloud.aws.credentials.accessKey=zzzzz",
"cloud.aws.credentials.secretKey=zzzzzz",
"aws.sqs.endpoint = http://localhost:9324",
"aws.sqs.requestQueue = CreateSchemaRequest",
"aws.sqs.responseQueue = CreateSchemaResponse"
})
public class AWSSQSConfigTests {
#Mock
private AmazonSQSAsync amazonSqs;
#Mock
private ResourceIdResolver resourceIdResolver;
#Test
public void contextLoads() {
AWSSQSConfig config = new AWSSQSConfig();
QueueMessagingTemplate queueMessagingTemplate = config.queueMessagingTemplate(amazonSqs, resourceIdResolver);
assertNotNull("The response body must not be null", queueMessagingTemplate);
}
}
Is this a chicken and the egg situation, where the spring framework actually needs to run first to inject those config values? Do we need an integration test here instead?
EDIT with working solution...
Using the accepted answer, here is my working test! I was able to remove my dependency of the Spring framework.
#RunWith(MockitoJUnitRunner.class)
public class AWSSQSConfigTests {
#Mock
private AmazonSQSAsync amazonSqs;
#Mock
private ResourceIdResolver resourceIdResolver;
#InjectMocks
private AWSSQSConfig config;
#Before
public void setup() {
ReflectionTestUtils.setField(config, "AWSSqsEndpoint", "http://fake");
}
#Test
public void contextLoads() {
QueueMessagingTemplate queueMessagingTemplate = config.queueMessagingTemplate(amazonSqs, resourceIdResolver);
assertNotNull("The response body must not be null", queueMessagingTemplate);
}
}
Have you tried injecting mock to your class (or autowire it), and then setting that field it using ReflectionTestUtils? This is a nice test utils class that Spring provides that allows you to do something like what you want without doing code modifications.
I mean something like this:
#InjectMocks
private AWSSQSConfig awssqsConfig;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(awssqsConfig, "AWSSqsEndpoint", "putYourEndpointHere");
}
I try to use Springs own Dependency Injection in a Junit test case:
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.binarisinformatik.api.AppConfig;
import org.binarisinformatik.satzrechner.SatzRechner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=AppConfig.class)
//#SpringApplicationConfiguration(classes = {AppConfig.class})
public class SatzRechnerTest {
#Autowired
private SatzRechner satzRechner; //SUT
#Before
public void setUp() {
// AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SatzRechnerTest.class);
//satzRechner=context.getBean(SatzRechner.class);
}
#Test
public void addiere_satz_4komma6_zu_zahlwert_10() {
assertThat("Addition von \"4,6\" ergibt nicht 10!",
satzRechner.summe("4,6"), is(equalTo(10)));
}
Im testing a class names SatzRechner in which Spring should also autowire some variables. Here is my Class under test:
#Component
public class SatzRechner {
#Autowired //#Inject
private Rechner taschenRechner;
#Autowired
private Zahlenfabrik zahlenfabrik;
public Integer summe(String zeichenSatz) {
return taschenRechner.summe(zahlenfabrik.erzeugeZahlen(zeichenSatz));
}
}
And AppConfig.class which is using as Configurationfile looks like that:
#Configuration
#ComponentScan(value={"org.binarisinformatik"})
public class AppConfig {
}
What is here the problem?
If you want to use a Spring configuration class, this one must have beans definitions. Please find an example below :
Test class:
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.binarisinformatik.api.AppConfig;
import org.binarisinformatik.satzrechner.SatzRechner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=AppConfig.class)
public class SatzRechnerTest {
#Autowired
private SatzRechner satzRechner;
#Test
public void addiere_satz_4komma6_zu_zahlwert_10() {
assertThat("Addition von \"4,6\" ergibt nicht 10!",
satzRechner.summe("4,6"), is(equalTo(10)));
}
}
Configuration class :
You have to declare #Bean annotated methods. These beans are managed by Spring container.
#Configuration
public class AppConfig {
// Beans present here will be injected into the SatzRechnerTest class.
#Bean
public SatzRechner satzRechner() {
return new SatzRechner();
}
#Bean
public Rechner taschenRechner() {
return new TaschenRechner();
}
#Bean
public Zahlenfabrik zahlenfabrik() {
return new Zahlenfabrik();
}
}
Note : I let you properly handle returned types here and beans parameters (if present in your context).
There are two things you have to ensure before you run the test case successfully:
1) Classes SatzRechner, Rechner & Zahlenfabrik should be under "org.binarisinformatik" package
2) Classes Rechner & Zahlenfabrik should also be annotated with #Component as SatzRechner.
This test is failing but I don't know why or how to fix it. If I hit a break point and call mockingContext.getBean(Repository.class), it does return my mock object, but for some reason the ProductionCode returned in createProductionCodeWithMock still has the real Repository autowired into it. I can only suspect that I need to do something special/extra to have an effect on autowired beans, but I don't know what it is. Why isn't this test passing, and how can I make it pass?
For what it's worth, I'm well aware of all the answers to this question. I still want to know why this test isn't working. I am using Spring 3.x.
Here's the test:
package mavensnapshot;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class ProductionCodeTest {
#Test
public void testReplaceRepositoryWithMock() {
Repository mockRepository = mock(Repository.class);
ProductionCode productionCode = createProductionCodeWithMock(mockRepository);
productionCode.doSomething();
verify(mockRepository).save();
}
private ProductionCode createProductionCodeWithMock(Repository mockRepository) {
GenericApplicationContext mockingContext = new GenericApplicationContext();
mockingContext.getBeanFactory().registerSingleton(Repository.class.getName(), mockRepository);
mockingContext.setParent(new ClassPathXmlApplicationContext("/beans.xml"));
return mockingContext.getBean(ProductionCode.class);
}
}
Here's my production code:
package mavensnapshot;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("/beans.xml");
ProductionCode productionCode = context.getBean(ProductionCode.class);
productionCode.doSomething();
}
}
package mavensnapshot;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class ProductionCode {
#Autowired
private Repository repository;
public void doSomething() {
repository.save();
}
}
package mavensnapshot;
import org.springframework.stereotype.Service;
#Service
public class Repository {
public void save() {
System.out.println("production code");
}
}