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
Related
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"
I have 2 maven packages, both of them with spring boot dependencies.
CoreApplication and CustomerApplication. Both apps have Spring MVC controllers, views and static resources.
In CoreApplication I dont have any runner class annotated with #SpringBootApplication.
In CustomerApplication pom.xml I use CoreApplication as a dependency.
If I run the CustomerApplication #SpringBootApplication annotated runner class, it finds the controllers in CoreApplication, but not the views. It can serve requests like http://localhost:8080/core/index, but I get an error from thymeleaf. (org.thymeleaf.exceptions.TemplateInputException: Error resolving template "index")
Is it possible what I want to do? How can I have a Core module with all common app specific stuff and a Customer app for every customer with their own business logic?
Maybe you can try:
Annotate your CoreApplication module with #SpringBootApplication to let Spring manage and initialize your app as usual:
#SpringBootApplication
public class CoreApplication {
public static void main(String[] args) {
SpringApplication.run(CoreApplication.class, args);
}
}
And in your CustomerApplication's runner, you can put:
#SpringBootApplication
public class CustomerApplication {
public static void main(String[] args) {
new SpringApplicationBuilder()
.sources(CoreApplication.class, CustomerApplication.class)
.run(args);
}
}
This way Spring will initialize your two modules properly.
Playing around with Spring Boot + MVC with static HTML pages, while noticed this thing:
Firstly, what I have:
Index controller:
#Controller
public class IndexController {
#RequestMapping("/")
public String index() {
return "index.html";
}
#RequestMapping("/{path:[^\\.]+}/**")
public String forward() {
return "forward:/";
}
}
The Html file is:...\src\main\resources\static\index.html
So when my main application class is:
#SpringBootApplication
public class MyApplication extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Everything works well and in default path: localhost:8080\ I get index.html page content
But if I annotate Application class with #EnableWebMvc
#SpringBootApplication
#EnableWebMvc
public class MyApplication extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
I get exception: javax.servlet.ServletException: Could not resolve view with name 'index.html' in servlet with name 'dispatcherServlet'
But according to this spring doc it is a valid configuration.
Maybe someone can explain me why? Do I understand something wrong?
According to spring-boot's docs
The auto-configuration adds the following features on top of Spring’s defaults:
Static index.html support.
...
If you want to keep Spring Boot MVC features, and you just want to add
additional MVC configuration (interceptors, formatters, view
controllers etc.) you can add your own #Configuration class of type
WebMvcConfigurerAdapter, but without #EnableWebMvc. If you wish to
provide custom instances of RequestMappingHandlerMapping,
RequestMappingHandlerAdapter or ExceptionHandlerExceptionResolver you
can declare a WebMvcRegistrationsAdapter instance providing such
components.
So by adding #EnableWebMvc you just disable what spring-boot autoconfiguring for you. Namely static index.html support.
Actually I think when you choose to use spring boot you should use the default config of spring Boot. It means you just have to edit the file application.properties. Now if you use spring mvc, you have to provide your own servlet. So I think mixing up the to is not a good idea. Either you use spring Boot wiht no much config to do or you use spring mvc and you make all the necessary config.
According to Spring Boot MVC structure, you should locate your html file in the templates folder. Then will be visible for Spring Boot
src\main\resources\templates\index.html
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.
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();
}
}