I am wondering if this will get me into trouble or if it is a bad idea:
#Configuration
public class MyConfig {
#Bean
public SomeBean1 someBean1() {
return ...
}
#Bean
public SomeBean2 someBean2() {
return ...
}
}
public class Main {
public static void main(String[] args) throws Throwable {
ApplicationContext ctx = new AnnotationConfigApplicationContext(HubBrokerConfig.class);
MyConfig conf = ctx.getBean(MyConfig.class);
conf.someBean1().doSomething();
conf.someBean2().doSomething();
}
}
You may wonder why I would do as above and not:
public class Main {
public static void main(String[] args) throws Throwable {
ApplicationContext ctx = new AnnotationConfigApplicationContext(HubBrokerConfig.class);
ctx.getBean(SomeBean1.class)).doSomething();
ctx.getBean(SomeBean2.class)).doSomething();
}
}
I do not like the second method as much, as it does not catch as many errors at compile-time. For example if I do ctx.getBean(SomeNonBean.class) I would not get compile-time errors. Also if someBean1() were private, the compiler would catch the error.
The preferred method would be to have
#Autowired
private SomeBean1 somebean1;
#Autowired
private SomeBean2 somebean2;
This is even cleaner, makes testing simpler, and avoids issues such as unnecessarily instantiating more copies than necessary.
Related
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.
I am trying to intercept a call to JdbcOperations using byte buddy in conjunction with spring.
I have two classes.
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args) throws Exception{
premain(null, ByteBuddyAgent.install());
SpringApplication.run(Application.class, args);
}
public static void premain(String arg, Instrumentation instrumentation) throws Exception {
new AgentBuilder.Default()
.type(ElementMatchers.is(JdbcOperations.class))
.transform(new AgentBuilder.Transformer() {
#Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) {
return builder.method(named("queryForList"))
.intercept(FixedValue.value(Collections.emptyList()));
}
}).installOn(instrumentation);
}
}
And
DemoRunner.java
#Component
public class DemoRunner implements CommandLineRunner {
private final JdbcOperations jdbcOperations;
public DemoRunner(JdbcOperations jdbcOperations) {
this.jdbcOperations = jdbcOperations;
}
#Override
public void run(String... args) throws Exception {
List<Map<String,Object>> resultSet = jdbcOperations.queryForList("SELECT * FROM COUNTRY");
for(Map<String, Object> result : resultSet) {
System.out.println(result);
}
}
}
I can see that the code runs, but it doesn't return the fixed value like I expected it to. Does anybody have any idea where I am going wrong?
With ElementMatchers.is(JdbcOperations.class), you are loading the JdbcOperations class. Without retransformation, this class will not be instrumented since it is already loaded when the agent is installed.
Rather use ElementMatchers.named("pkg.of.JdbcOperations").
I want to test the getCitation() method using jUnit:
#Singleton
public class QuotesLoaderBean {
Properties quotes;
Properties names;
#PostConstruct
public void init() {
InputStream quotesInput = this.getClass().getClassLoader().getResourceAsStream("quotes.properties");
InputStream namesInput = this.getClass().getClassLoader().getResourceAsStream("names.properties");
quotes = new Properties();
names = new Properties();
try {
quotes.load(quotesInput);
names.load(namesInput);
} catch (IOException ex) {
Logger.getLogger(QuotesLoaderBean.class.getName()).log(Level.SEVERE, null, ex);
}
}
public Citation createCitation(String quote) {
Citation citation = new Citation();
citation.setQuote(quote);
citation.setWho(getName());
return citation;
}
public Citation getCitation() {
Citation citation = new Citation();
citation.setQuote(getQuote());
citation.setWho(getName());
return citation;
}
In the Test File I want to Inject the Singleton and use it in the test method. But then I get the NullPointerException:
public class QuoteServiceTest {
#Inject
QuotesLoaderBean quotesLoaderBean;
public QuoteServiceTest() {
}
#BeforeClass
public static void setUpClass() {
}
#AfterClass
public static void tearDownClass() {
}
#Before
public void setUp() {
}
#After
public void tearDown() {
}
#Test
public void whenGetQuote_thenQuoteShouldBeReturned() {
quotesLoaderBean.getQuote();
}
}
The test method is not finished, nut I just want to show the exception that occurs when I call a method from the Singleton. In another service class i can easily inject the class and call the methods.
Injection is handled by a DI-enabled container in execution time.
When you deploy your entire application, a container is set and injection works fine.
When executing unit tests, none of the services are launched, and any #Inject will end up with the variable set to null, because no container will be launched either.
So, in order to test your code, you may want to build the service inside setUp method:
public class QuotesServiceTest {
QuotesLoaderBean quotesLoaderBean;
// ...
#Before
public void setUp() {
quotesLoaderBean = new QuotesLoaderBean();
// call init method after construction
quotesLoaderBean.init();
}
// ...
}
This might have been coded wrongly, but any idea how it should be done is appreciated.
I have this class TestClass which needs to inject many service class. Since I can't use #BeforeClass on #Autowired objects, I result on using AbstractTestExecutionListener. Everything was working as expected but when I'm on #Test blocks, all objects are evaluated null.
Any idea how to solve this?
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { ProjectConfig.class })
#TestExecutionListeners({ TestClass.class })
public class TestClass extends AbstractTestExecutionListener {
#Autowired private FirstService firstService;
// ... other services
// objects needs to initialise on beforeTestClass and afterTestClass
private First first;
// ...
// objects needs to be initialised on beforeTestMethod and afterTestMethod
private Third third;
// ...
#Override public void beforeTestClass(TestContext testContext) throws Exception {
testContext.getApplicationContext().getAutowireCapableBeanFactory().autowireBean(this);
first = firstService.setUp();
}
#Override public void beforeTestMethod(TestContext testContext) throws Exception {
third = thirdService.setup();
}
#Test public void testOne() {
first = someLogicHelper.recompute(first);
// ...
}
// other tests
#Override public void afterTestMethod(TestContext testContext) throws Exception {
thirdService.tearDown(third);
}
#Override public void afterTestClass(TestContext testContext) throws Exception {
firstService.tearDown(first);
}
}
#Service
public class FirstService {
// logic
}
For starters, having your test class implement AbstractTestExecutionListener is not a good idea. A TestExecutionListener should be implemented in a stand-alone class. So you might want to rethink that approach.
In any case, your current configuration is broken: you disabled all default TestExecutionListener implementations.
To include the defaults, try the following configuration instead.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = ProjectConfig.class)
#TestExecutionListeners(listeners = TestClass.class, mergeMode = MERGE_WITH_DEFAULTS)
public class TestClass extends AbstractTestExecutionListener {
// ...
}
Regards,
Sam (author of the Spring TestContext Framework)
I have #Autowired service which has to be used from within a static method. I know this is wrong but I cannot change the current design as it would require a lot of work, so I need some simple hack for that. I can't change randomMethod() to be non-static and I need to use this autowired bean. Any clues how to do that?
#Service
public class Foo {
public int doStuff() {
return 1;
}
}
public class Boo {
#Autowired
Foo foo;
public static void randomMethod() {
foo.doStuff();
}
}
You can do this by following one of the solutions:
Using constructor #Autowired
This approach will construct the bean requiring some beans as constructor parameters. Within the constructor code you set the static field with the value got as parameter for constructor execution. Sample:
#Component
public class Boo {
private static Foo foo;
#Autowired
public Boo(Foo foo) {
Boo.foo = foo;
}
public static void randomMethod() {
foo.doStuff();
}
}
Using #PostConstruct to hand value over to static field
The idea here is to hand over a bean to a static field after bean is configured by spring.
#Component
public class Boo {
private static Foo foo;
#Autowired
private Foo tFoo;
#PostConstruct
public void init() {
Boo.foo = tFoo;
}
public static void randomMethod() {
foo.doStuff();
}
}
You have to workaround this via static application context accessor approach:
#Component
public class StaticContextAccessor {
private static StaticContextAccessor instance;
#Autowired
private ApplicationContext applicationContext;
#PostConstruct
public void registerInstance() {
instance = this;
}
public static <T> T getBean(Class<T> clazz) {
return instance.applicationContext.getBean(clazz);
}
}
Then you can access bean instances in a static manner.
public class Boo {
public static void randomMethod() {
StaticContextAccessor.getBean(Foo.class).doStuff();
}
}
What you can do is #Autowired a setter method and have it set a new static field.
public class Boo {
#Autowired
Foo foo;
static Foo staticFoo;
#Autowired
public void setStaticFoo(Foo foo) {
Boo.staticFoo = foo;
}
public static void randomMethod() {
staticFoo.doStuff();
}
}
When the bean gets processed, Spring will inject a Foo implementation instance into the instance field foo. It will then also inject the same Foo instance into the setStaticFoo() argument list, which will be used to set the static field.
This is a terrible workaround and will fail if you try to use randomMethod() before Spring has processed an instance of Boo.
The easiest way to create a static context is naturally, when the application starts up. This will prevent the need for an unnatural implementation with an additional class.
#SpringBootApplication
public class MyApplication {
private static ApplicationContext appContext;
public static void main(String[] args) {
appContext = SpringApplication.run(MyApplication.class, args);
}
public static ApplicationContext getAppContext() {
return appContext;
}
}
Then, anywhere you need to access a bean statically, you can use the ApplicationContext to get the instance of the class.
public class Boo {
public static void randomMethod() {
MyApplication.getAppContext()
.getBean(Foo.class).doStuff();
}
}
Regards..
It sucks but you can get the bean by using the ApplicationContextAware interface. Something like :
public class Boo implements ApplicationContextAware {
private static ApplicationContext appContext;
#Autowired
Foo foo;
public static void randomMethod() {
Foo fooInstance = appContext.getBean(Foo.class);
fooInstance.doStuff();
}
#Override
public void setApplicationContext(ApplicationContext appContext) {
Boo.appContext = appContext;
}
}
This builds upon #Pavel's answer, to solve the possibility of Spring context not being initialized when accessing from the static getBean method:
#Component
public class Spring {
private static final Logger LOG = LoggerFactory.getLogger (Spring.class);
private static Spring spring;
#Autowired
private ApplicationContext context;
#PostConstruct
public void registerInstance () {
spring = this;
}
private Spring (ApplicationContext context) {
this.context = context;
}
private static synchronized void initContext () {
if (spring == null) {
LOG.info ("Initializing Spring Context...");
ApplicationContext context = new AnnotationConfigApplicationContext (io.zeniq.spring.BaseConfig.class);
spring = new Spring (context);
}
}
public static <T> T getBean(String name, Class<T> className) throws BeansException {
initContext();
return spring.context.getBean(name, className);
}
public static <T> T getBean(Class<T> className) throws BeansException {
initContext();
return spring.context.getBean(className);
}
public static AutowireCapableBeanFactory getBeanFactory() throws IllegalStateException {
initContext();
return spring.context.getAutowireCapableBeanFactory ();
}
}
The important piece here is the initContext method. It ensures that the context will always get initialized. But, do note that initContext will be a point of contention in your code as it is synchronized. If your application is heavily parallelized (for eg: the backend of a high traffic site), this might not be a good solution for you.
Use AppContext. Make sure you create a bean in your context file.
private final static Foo foo = AppContext.getApplicationContext().getBean(Foo.class);
public static void randomMethod() {
foo.doStuff();
}