I am converting an existing Java application to Spring Boot.
I'm a Spring Boot newbie. I know Spring Boot has support for application.properties, which is great for new apps. But this is a legacy app and I need an interim solution for now - without completely redesigning it. This app needs to call some initializer with a property file on startup. There is prod.properties and another one for tests - test.properties.
the prod file needs to be loaded from a specific location on disk (/mydir/prod.properties), while test.properties comes from tests' classpath.
also, may legacy classes require this "property class" to be initialized on their creation (they reference it in their static initializers, oh geez...) - so this init needs to happen before Spring Components are loaded.
what would be the easiest solution?
I came up with this overly-verbose-boiler-plate solution. it works, but maybe there is an easier way?
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.inject.Inject;
#SpringBootApplication
public class DemoApplication implements CommandLineRunner {
#Inject
private Initializer initializer;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
initializer.init();
}
}
public interface Initializer {
void init();
}
#Component
public class ProdInitializer implements Initializer {
#Override
public void init() {
System.out.println("PROD init - will load properties file from /myfolder/ and call some init class with it");
}
}
// and this is from TESTS section:
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
#Primary
#Component
public class TestInitializer implements Initializer {
#Override
public void init() {
System.out.println("TEST init - will load properties file from classpath and call some init class with it");
}
}
I tried having just one initializer with an additional test/resources/application.properties file with just 1 value in it (legacy property file location) hoping that Spring Boot would load application.properties from PROD code, then load the only value present in test/resources/application.properties and override the corresponding value in main/resources/application.properties, but no luck - apparently, Spring Boot ignores main/resources/application.properties for tests if test/resources/application.properties is present. so I would have to duplicate all settings from main/resources/application.properties in test/resources/application.properties with just one property being different.
Related
This question already has answers here:
Which ApplicationContext implementation used in simple spring boot application?
(3 answers)
Closed 2 years ago.
In Spring Framework we can choose the type of application context from the image below:
But which one is implemented by default by spring boot?
Does it depend on which starter dependencies we choose when create project?
It depends on the starter projects you use. For regular projects Spring Boot uses the AnnotationConfigApplicationContext and for web projects
the AnnotationConfigServletWebServerApplicationContext.
See also the output of
#SpringBootApplication
public class DummyApplication implements ApplicationContextAware {
public static void main(String[] args) {
SpringApplication.run(DummyApplication.class, args);
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) {
System.out.println(applicationContext.getClass().getName());
}
}
Technically it does not directly depends on the starter , but depends on the value of WebApplicationType you configure to run the application :
public static void main(String[] args) throws Exception {
SpringApplication app = new SpringApplication(FooApplication.class);
app.setWebApplicationType(WebApplicationType.SERVLET);
app.run(args);
}
If you do not configure it , the default value will be deduced by checking if certain classes exist in the classpath.
There are 3 types of WebApplicationType will are REACTIVE , SERVLET and NONE.
And based on its value , it will choose which type of application context to be created for. See this for the logic.
For REACTIVE , it will create AnnotationConfigReactiveWebServerApplicationContext
For SERVLET , it will create AnnotationConfigServletWebServerApplicationContext
For NONE, it will create AnnotationConfigApplicationContext
So it is possible that even you use certain starter , but changing the WebApplicationType value will cause different context type to be used.
We can see the output using the following code as well
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
#SpringBootApplication
public class Application implements CommandLineRunner {
#Autowired
private ApplicationContext applicationContext;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
public void run(String... args) throws Exception {
System.out.println(applicationContext.getDisplayName());
}
}
I used the spring-boot-starter-web dependency , so it printed org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
On including spring-boot-starter it prints org.springframework.context.annotation.AnnotationConfigApplicationContext
So depending one the spring-boot starter dependency , spring is choosing the implementation of ApplicationContext
I'd like to test my Spring Boot command line application. I would like to mock certain beans (which I was able to do by annotating #ContextConfiguration(classes = TestConfig.class) at the top of my test class. In TestConfig.class, I override the beans that I would like to mock. I'd like Spring Boot to find the rest of the components. This seems to work.
The problem is that when I run the test, the entire application starts up as normal (ie. the run() method is called).
#Component
public class MyRunner implements CommandLineRunner {
//fields
#Autowired
public MyRunner(Bean1 bean1, Bean2 bean2) {
// constructor code
}
#Override
public void run(String... args) throws Exception {
// run method implementation
}
I've tried to override the MyRunner #Bean and put it in TestConfig.class, but that doesn't seem to work. I understand that I'm loading the regular application context, but that's what I'd like to do (I think?) since I would like to re-use all (or most) of the #Component I created in my Application, and only mock a tiny subset.
Any suggestions?
EDIT:
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
The answer was simpler than I thought. Add the MockBean in
#TestConfiguration
public class TestConfig {
#MockBean
private MyRunner myRunner;
}
We can use the #MockBean to add mock objects to the Spring application context. The mock will replace any existing bean of the same type in the application context.
So MyRunner.run() is never called but I can still use all the other beans in my application.
CommandLineRunners are ordinary beans with one exception:
After the application context is loaded, spring boot finds among all its beans the beans that implement this interface and calls their run method automatically.
Now, I would like you to ask to do the following:
Remove ContextConfiguration from the test and place a breakpoint in constructor of MyRunner. The test should look like this:
#RunWith(SpringRunner.class) // if you're on junit 4, adjust for junit 5 if you need
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class MyTest {
#Autowired
private MyRunner myRunner;
#Test
public void testMe() {
System.out.println("hello");
}
}
Run the test and make sure that myRunner is loaded and its run method is called
Now mock this class with MockBean annotation:
#RunWith(SpringRunner.class) // if you're on junit 4, adjust for junit 5 if you need
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class MyTest {
#MockBean
private MyRunner myRunner;
#Test
public void testMe() {
System.out.println("hello");
}
}
Run the test. Make sure that run method is not running. Your Application Context now should contain a mock implementation of your component.
If the above works, then the problem is with TestConfig and ContextConfiguration annotation. In general when you run without ContextConfiguration you give spring boot test engine a freedom to mimic the application context started as if its a real application (with autoconfigurations, property resolution, recursive bean scanning and so forth).
However if you put ContextConfiguration, spring boot test doesn't work like this - instead it only loads the beans that you've specified in that configuration. No Autoconfigurations, no recursive bean scanning happens for example.
Update
Based on OP's comment:
It looks like the MyRunner gets loaded when you put #ContextConfiguration because of component scanning. Since you have an annotation #Component placed on MyRunner class it can be discovered by Spring boot engine.
In fact there is a "dangerous" mix of two types of beans definitions here:
1. The beans defined with #Bean in #Configuration annotation
2. The beans found during component scanning.
Here is the question for you: If you don't want to mimic the application startup process and instead prefer to load only specific beans, why do you use #SpringBootTest at all? Maybe you can achieve the goal with:
#RunWith(SpringRunner.class)
#ContextConfiguration(YourConfig.class)
public class MyTest {
...
}
One way you could do this is to have 2 classes with the main method, one which sets up the "normal" context, and another that sets up the "mock" context:
Normal App Context, uses the usual Application
#SpringBootApplication(scanBasePackages = "com.example.demo.api")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public Foo foo() {
return new Foo("I am not mocked");
}
#Bean
public Bar bar() {
return new Bar("this is never mocked");
}
}
Add another Application class that overrides the normal context with the mocked one
#SpringBootApplication(scanBasePackageClasses = {MockApplication.class, Application.class})
#Component
public class MockApplication {
public static void main(String[] args) {
SpringApplication.run(MockApplication.class, args);
}
#Bean
public Foo foo() {
return new Foo("I am mocked");
}
}
When you run Application.main Foo will be "I am not mocked", when you run MockApplication.main() it will be "I am mocked"
How do I configure the use of a properties file using Java DSL and the Main object?
According to this page I should be able to call something like:
main.setPropertyPlaceholderLocations("example.properties");
However that simply doesn't work. It seems that option wasn't added until Camel 2.18 and I'm running 2.17.1.
What was the original way to set a properties file to use when letting the application run in a standalone form?
Some backstory:
I'm trying to convert from Spring to Java DSL. During that conversion I was attempting to have my Camel application run on its own. I know that is achieved using main.run();.
I had things "functioning" when using the CamelContext, but that cannot run on its own. So I know using the following will work in that case:
PropertiesComponent pc = new PropertiesComponent();
pc.setLocation("classpath:/myProperties.properties");
context.addComponent("properties", pc);
Is there some way I can tell the main to use that setup? Or is there something else needed?
You can use the following snippet:
PropertiesComponent pc = new PropertiesComponent();
pc.setLocation("classpath:/myProperties.properties");
main.getCamelContexts().get(0).addComponent("properties", pc);
Also, if you are using camel-spring, you could use org.apache.camel.spring.Main class, it should use the property placeholder from your application context.
Since you are mentioning you are in the process to move from Spring XML to Java Config here's a minimum application that is using properties and injecting it into a Camel route (it's really properties management in Spring injected into our Camel route bean):
my.properties:
something=hey!
Main class:
package camelspringjavaconfig;
import org.apache.camel.spring.javaconfig.CamelConfiguration;
import org.apache.camel.spring.javaconfig.Main;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
#Configuration
#ComponentScan("camelspringjavaconfig")
#PropertySource("classpath:my.properties")
public class MyApplication extends CamelConfiguration {
public static void main(String... args) throws Exception {
Main main = new Main();
main.setConfigClass(MyApplication.class); // <-- passing to the Camel Main the class serving as our #Configuration context
main.run(); // <-- never teminates
}
}
MyRoute class:
package camelspringjavaconfig;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
#Component
public class MyRoute extends RouteBuilder {
#Autowired
Environment env; //<-- we are wiring the Spring Env
#Override
public void configure() throws Exception {
System.out.println(env.getProperty("something")); //<-- so that we can extract our property
from("file://target/inbox")
.to("file://target/outbox");
}
}
I am trying to write a service-oriented application.
I have a been called memory defined as such:
package com.example.assets;
//imports ignored
#Resource
public class Memory {
}
And I have a service been called memoryHandler defined as such:
package com.example.service;
//imports ignored
#Service
public class MemoryHandler {
#Autowired
private Memory memory;
public void execute() {
//do something with memory
}
}
There is also another class, which is a BeanFactoryPostProcessor:
package com.example.service;
//imports ignored
#Component
public class PostProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.getBeansOfType(MemoryHandler.class, false, true);
}
}
Which prematurely looks up the bean, memoryHandler, leaving it instantiated` but not autowired. However, I want the bean to be autowired before it is fetched by the factory.
In my Main class I have written:
package com.example.service;
//imports ignored
public class Main {
public static void main(String[] args) {
final ApplicationContext context = new ClassPathXmlApplicationContext("/context.xml");
context.getBean(MemoryHandler.class).execute();
}
}
I get a NullPointerException on the line I work with the memory. I replaced the above declaration with a setter injection, and upon tracing realized that the injection never occurs.
I have changed the annotations on both components to Service, Repository, and Component and also have tried replacing Autowired with Resource to no avail.
What am I missing here? I have read all the questions that have shown up in my search for an answer, and none of them helps me (I got the tip about using Resouce annotations there).
Needless to say, I have not missed annotation configuration for my beans:
<context:annotation-config/>
<context:component-scan base-package="com.example"/>
Moreover, autowiring works just fine when autowired beans are defined in the XML configuration file, and not via annotations.
I am using Spring 3.2.3.RELEASE.
Changing the implementation of the PostProcessor is the key:
Instead of:
public class PostProcessor implements BeanFactoryPostProcessor {
I will have to write:
public class PostProcessor implements ApplicationContextAware {
This ensures that the context will be fully populated before it is post-processed, which in my case works just fine. But I am wondering if there is another way to do this, using the usual BeanFactoryPostProcessor interface?
I was wondering is there an annotations based method for starting a spring application?
i.e. replacing this below:
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
User user = (User)ctx.getBean("user");
user.createUser();
}
With an annotations based method for getting the context and then Autowiring in the bean?
I don't think you can do that, after all someone would have to understand and process that annotation. If Spring has not initialized yet, who would?
There is an anotation in spring: called #ContextConfiguration
Very helpfull for testing.
You need to extend one of the spring abstract classes created for test support(testNG or JUnit).
(e.g. AbstractTransactionalTestNGSpringContextTests tor testNG or AbstractTransactionalJUnit4SpringContextTests for JUnit)
Then you just use the #ContextConfiguration annotation(for Class, interface (including annotation type), or enum declaration)
some example code for junit test:
#RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext will be loaded from "/applicationContext.xml" and "/applicationContext-test.xml"
// in the root of the classpath
#ContextConfiguration(locations={"/applicationContext.xml", "/applicationContext-test.xml"})
public class MyTest {
// class body...
}
please read:
http://static.springsource.org/spring/docs/2.5.x/reference/testing.html
You cannot avoid that ClassPathApplicationContextXml code but you can avoid that ctx.getBean("user") by doing as below. Ideally what it does is it asks the xml to scan the packages where spring want to inject things. The only thing you should not here is that i have declared my main class as spring Component, since springs annotations work on spring recognized classes and hence i am making my main as spring recognized class. The loading of xml using Classpathapplicationcontext cannot be avoided.
package com.spring.sample;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;
import com.spring.sample.component.Sample;
#Component
public class SampleMain {
#Autowired
Sample testSample;
static ApplicationContext appCtx = new ClassPathXmlApplicationContext("META-INF/webmvc-application.xml");
public static void main(String[] args){
SampleMain sampleMain = appCtx.getBean(SampleMain.class);
sampleMain.invokeSample();
}
private void invokeSample(){
testSample.invokeSample();
}
}