Spring Boot scans test folder on start-up - java

I have a Spring Boot app:
#SpringBootApplication
#EnableAutoConfiguration
public class MySystem extends SpringBootServletInitializer {
public static void main(final String... args) {
SpringApplication.run(MySystem.class, args);
}
}
The project layout is like this:
MyApp/
src/main/java
src/test/java
src/integration-test/java
In src/integration-test/java I have a Spring configuration:
#Configuration
static class BootstrapIntegrationTestConfig {
#Bean
public DataSource h2DataSource() {
...
}
}
Unfortunately, when I start my Spring Boot app the src/integration-test/java folder will also be scanned and the BootstrapIntegrationTestConfig is loaded.
How can I prevent my Spring Boot app from scanning the src/integration-test/java?
I'm working with Eclipse and Gradle and I already tried to exclude test and integration-test from bin, but it didn't work:
import org.gradle.plugins.ide.eclipse.model.SourceFolder
eclipse.classpath.file {
beforeMerged { cp ->
cp.entries.clear()
}
whenMerged { cp ->
cp.entries.findAll { it instanceof SourceFolder && it.path.startsWith("src/integration-test/") }*.output = "integration-test-bin"
cp.entries.findAll { it instanceof SourceFolder && it.path.startsWith("src/test/") }*.output = "test-bin"
}
}

Related

How to dynamically load Spring controllers

I have a Spring Boot application that hosts a REST API.
Depending on which files get deployed, I want to be able to have it load additional controllers from what is essentially a "plugin" JAR file.
For example, I'd love to be able to do something like this:
java -jar myapp.jar -Dplugins.directory=/opt/myapp/plugins
Is this possible?
Note: these would not be loaded on the fly; once deployed, the set of plugins will remain fixed. I want one application jar that remains the same in every deployment, and the behavior of the application will be determined by the plugins that are deployed alongside it.
it may not 100% Satisfy your demand.
I have two suggestion.
the easy one.
java -jar stackoverflow-1.0-SNAPSHOT.jar --spring.profiles.active=prod
and put different value "#Profile" on your controller.
#RestController
#Profile("prod")
public class URLOneController {
#PostMapping(value = "/url", consumes="application/json", produces="application/json")
public ResponseEntity<HttpStatus> insertClaim(#RequestBody String messageBody) {
return new ResponseEntity<>(HttpStatus.OK);
}
}
second suggestion ,dynamic load beanDefiniton.
#Configuration
#ConditionalOnProperty(name="external.controller.enable",havingValue = "true")
public class ExternalClassDefinitionProcessor implements
BeanDefinitionRegistryPostProcessor {
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
Class<?> aClass = null;
try {
aClass = contextClassLoader.loadClass("com.jin.learn.demo.UrlOneController");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(aClass);
beanDefinitionBuilder.addPropertyReference("personDao", "personDao");
BeanDefinition personManagerBeanDefinition = beanDefinitionBuilder
.getRawBeanDefinition();
registry.registerBeanDefinition("UrlOneController",
personManagerBeanDefinition);
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory
beanFactory) throws BeansException {
}
}
package your controller into normal jar(not use spring-boot-maven-plugin )
run your app like this command line
java -Dloader.path="lib/,config/,/home/jin/Desktop/abc/target/abc-1.0-SNAPSHOT.jar" -jar stackoverflow-1.0-SNAPSHOT.jar --external.controller.enable=true
the extra contorller in abc-1.0-SNAPSHOT.jar and your main app is stackoverflow-1.0-SNAPSHOT.jar
tips:
stackoverflow-1.0-SNAPSHOT.jar should package zip format .
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layout>ZIP</layout>
</configuration>
</plugin>

Spring Boot - How to start my Spring Boot Application from sister module in a multi-module project?

I have a multi-module project with two projects: backend and client. The backend is a normal Spring Boot Rest API, nothing special. The client module is just a Java Library using the Rest API.
The backend has packaging of "war" as the backend as it uses JSPs, too and needs to be deployed to a servlet container. The backend is still easily testable with #SpringBootTest.
Now I want to have some integration tests inside the client module using the backend module as a sandbox server.
To use all the backend classes in the client module I added
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<attachClasses>true</attachClasses>
</configuration>
</plugin>
and configured the backend as a test dependency in client with classes
In my client/src/test/java I have a helper class which starts up the backend module
#Configuration
public class SandboxServer {
#Bean
public ConfigurableApplicationContext backend() {
return
new SpringApplicationBuilder(BackendApplication.class)
.sources(SandboxServerConfig.class)
.run("spring.profiles.active=sandbox")
}
}
The profile "sandbox" is used to setup a test database etc. But I had more problems. First problem was regarding the document root, so I configured it:
public class SandboxServerConfig
implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Override
public void customize(TomcatServletWebServerFactory factory) {
factory.setDocumentRoot(new File("../backend/src/main/webapp"));
}
}
But it still does not work as Spring is not picking up backend/src/main/resources/application.properties
That might be correct as it is not in the root classpath of the client module.
So it does not really work. I guess it is not possible to just start up the sibling module in an Integration test.
How can I achieve to start up the sibling spring boot module for integration testing? What is the best practice for szenarios like this?
You can override the application.properties location using TestPropertySource like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = BlaApplication.class)
#TestPropertySource(locations="/path/to/backend/src/main/resources/application.properties")
public class ExampleApplicationTests {
}
I found a much more solid solution. In my sibling Project "frontend" I have a Component which is starting up the backend server in integration mode if and only if it is not already running.
Benefits:
The real WAR is tested
You can start the WAR before in your IDE and let the tests run fast
If you run it with maven it is started up before all tests only once
No build configuration needed (like pre-integration in maven)
process is seperated from Junit runtime so no hassle with complex setups.
Drawbacks:
You need to build the package before you can run any integration test in the frontend. But hey, you should build your package before you test it. That's what integration test is all about.
And here is my SandboxServerProcess.class.
import org.springframework.stereotype.Component;
import javax.annotation.*;
import javax.annotation.*;
import java.io.*;
import java.net.*;
import java.util.*;
#Component
#Profile("integration")
public class SandboxServerProcess {
private static final String WAR = "../backend/target/backend.war";
private final static int PORT = 8081;
private boolean startedByMe;
#PostConstruct
public void start() throws Exception {
if (isStarted()) {
return;
}
testWarExists();
packagedWar("start");
if (waitForStartup()) {
startedByMe = true;
return;
}
throw new RuntimeException("Sandbox Server not started");
}
private void testWarExists() {
File file = new File(WAR);
if (!file.exists()) {
throw new RuntimeException("WAR does not exist:" + file.getAbsolutePath());
}
}
#PreDestroy
public void stop() throws IOException {
if (startedByMe) {
packagedWar("stop");
}
}
private void packagedWar(String command) throws IOException {
ProcessBuilder builder = new ProcessBuilder();
builder.environment().put("MODE", "service");
builder.environment().put("SPRING_PROFILES_ACTIVE", "integration");
builder.environment().put("APP_NAME", "backend");
builder.environment().put("PID_FOLDER", "./");
builder.environment().put("LOG_FOLDER", "./");
List<String> commands = new ArrayList<>();
commands.add(WAR);
commands.add(command);
builder.command(commands);
builder.inheritIO();
builder.redirectErrorStream(true);
builder.start();
}
private boolean isStarted() {
try {
Socket socket = new Socket();
InetSocketAddress sa = new InetSocketAddress("localhost", PORT);
socket.connect(sa, 500);
logger.warn("SandboxServer is started");
return true;
} catch (IOException e) {
return false;
}
}
private boolean waitForStartup() throws InterruptedException {
for (int i = 1; i < 30; i++) {
if (isStarted()) {
return true;
}
logger.warn("SandboxServer not yet ready, tries: " + i);
Thread.sleep(1000);
}
return false;
}
}

Maven doesn't initialize the spring context property when running integration tests

Issue : When running integration tests from maven (mvn verify) the spring application context is not initialized properly, it doesn't take in consideration my custom ApplicationContextInitializer class.
Test Class :
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {MainApplication.class}, initializers = CustomContextInitializer.class)
#WebIntegrationTest
public class ApplicationIT {
// Running a SOAPUI suite as a JUnit Test
#Test
public void TestGateway() throws Exception {
SoapUITestCaseRunner runner = new SoapUITestCaseRunner();
runner.setProjectFile("../gateway/src/test/resources/soapui/gateway-soapui.xml");
runner.run();
}
}
MainApplication class :
#Configuration
#ComponentScan(basePackages = {
// different packages here (not relevant)
})
#EnableAutoConfiguration
public class MainApplication {
public static void main(String[] args) throws Exception {
new SpringApplicationBuilder(MainApplication.class)
.initializers(new CustomContextInitializer())
.run(args);
}
}
CustomContextInitiliazer class (for adding custom .properties files to the spring environment application context) :
public class CustomContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>{
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment env = applicationContext.getEnvironment();
try {
Resource[] res = new PathMatchingResourcePatternResolver().getResources("classpath*:/*.properties");
for (Resource re : res) {
env.getPropertySources().addFirst(new ResourcePropertySource(re));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Results :
1) Everything works on when I start and run the application (either from IDE or by invoking mvn exec).
2) Integration tests run ok when started from IDE.
3) Integration tests throw error when invoked via maven verify because the custom properties files are not loaded into spring context environment. The result is the same as if I wouldn't have written initializers = CustomContextInitializer.class in the test class and tried to run the tests from IDE.
I think your code is correct, but your .properties files may be at the wrong place. Make sure they are under <project>/src/main/resources or that you have configured a custom resource folder in maven. If they reside under <project>/src/main/java they will not be part of the classpath as far as maven is concerned.

Spring boot application without Tomcat: how to "daemonize"

When I removed REST part from my basic Spring Boot application:
#Configuration
#EnableAutoConfiguration(exclude = {EmbeddedServletContainerAutoConfiguration.class,
WebMvcAutoConfiguration.class}) // Do not start Tomcat yet
#ComponentScan
public class ViewsAggregatorApplication {
public static void main(String[] args) throws InterruptedException, IllegalAccessException {
SpringApplication.run( MyApplication.class, args);
}
}
I have found out that my application quits immediately. I however intent to have inside AMQP listener and sender, so I'd like it to stay running. What is the best way to do so?

SpringBoot WEB-INF not loaded

I have a spring boot application. Packaged as a war file, such that the contents are as follows
static
org - springframework - boot -loader - SpringClasses
META-INF
- MANIFEST.MF
- maven -my.group.id - my-artifact-id - pom.xml & pom.properties
WEB-INF
- lib (contains all jars)
- classes (my main application's classes)
- some other stuff
- web.xml
- main-servlet.xml
Where web.xml and main-servlet.xml are the configuration xml's
I tried, from the springBoot application doing as follows:
#EnableWebMvc
#EnableAutoConfiguration
#Configuration
#ImportResource({ "classpath:main-servlet.xml"})
public class FakeAppBooter {
public static void main(String args[]) {
SpringApplication.run(FakeAppBooter.class, args);
System.out.println("Test");
}
public DispatcherServlet mvcDispatcherServlet() {
XmlWebApplicationContext ctx = new XmlWebApplicationContext();
ctx.setConfigLocation("classpath:main-servlet.xml");
DispatcherServlet dispatcherServlet = new DispatcherServlet(ctx);
return dispatcherServlet;
}
#Bean
public ServletRegistrationBean mvcServletRegistrationBean() {
ServletRegistrationBean bean = new ServletRegistrationBean();
bean.setServlet(mvcDispatcherServlet());
ArrayList<String> list = new ArrayList<>();
list.add("/");
bean.setUrlMappings(list);
return bean;
}
}
However on startup I get :
Caused by: java.io.FileNotFoundException: class path resource [main-servlet.xml] cannot be opened because it does not exist
at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:172)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:329)
... 25 more
I need these to define the servlets for my application.
what should I do?
Solution for your Question:
While you are using SpringBoot Never care about your web.xml, Because SpringBoot itself will initialize your web Container through EmbeddedServletContainer preferably TomcatEmbeddedServletContainer. Hence ignore your web.xml configuration and it resolves your first question
You haven't placed your main-servlet.xml in proper classpath. Copy your main-servlet.xml and paste it in src folder of your project.
Change your code accordingly
import java.util.ArrayList;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
//Place your Imports Appropriately
/**
* #author Praveen
*
*/
#EnableWebMvc
#EnableAutoConfiguration(exclude=DataSourceAutoConfiguration.class)
#ImportResource({ "classpath:main-servlet.xml"})
#ComponentScan
public class FakeAppBooter {
private ApplicationContext ctx;
public static void main(String[] args) {
SpringApplication.run(FakeAppBooter.class, args);
System.out.println("Test>>>>");
}
public DispatcherServlet mvcDispatcherServlet() {
ctx = new ClassPathXmlApplicationContext();
((AbstractRefreshableConfigApplicationContext) ctx).setConfigLocation("classpath:main-servlet.xml");
DispatcherServlet dispatcherServlet = new DispatcherServlet();
return dispatcherServlet;
}
#Bean
public ServletRegistrationBean mvcServletRegistrationBean() {
ServletRegistrationBean bean = new ServletRegistrationBean();
bean.setServlet(mvcDispatcherServlet());
ArrayList<String> list = new ArrayList<>();
list.add("/");
bean.setUrlMappings(list);
return bean;
}
}
main-servlet.xml
I have kept the main-servlet.xml in a very simple manner.
main-servlet.xml file
Successful Log Below
Successful Log

Categories

Resources