Spring boot AMQP and Spring Hadoop together ends up with missing EmbeddedServletContainerFactory bean - spring-boot

I have two small apps, one uses spring-boot-starter-amqp, other uses spring-data-hadoop-boot. I can run them separately without any problems.
When I join them together, app start fails with exception: org.springframework.context.ApplicationContextException: Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletContainerFactory bean.
My main class is pretty much generic and it works fine for both of them separately:
#PropertySource("file:conf/app.properties")
#SpringBootApplication
public class Job {
public static void main(String[] args) throws Exception {
SpringApplication.run(Job.class, args);
}
}
I am at lost here. AFAIK #SpringBootApplication contains all annotations needed, including auto configuration and components scanning. I've had no need to configure web environment as I am not using it. Why do I need to do it when both dependencies are in class path, and how do I fix it?
UPDATE
I dug a little bit in the Spring Boot code. Main problem is that SpringApplication.deduceWebEnvironment() automatically detects what kind of environment should be configured based on existence of certain classes in class path.
For web environment two classes are being checked. When both of them are in class path, web environment is detected which requires proper configuration, obviously.
javax.servlet.Servlet
org.springframework.web.context.ConfigurableWebApplicationContext
spring-boot-starter-amqp:1.3.1.RELEASE contains ConfigurableWebApplicationContext, and spring-data-hadoop-boot:2.3.0.RELEASE-cdh5 contains Servlet (in native Hadoop libs).
Now, when run alone, one of above classes is missing in both cases, which results in web environment not being set.
But when I use both of them - both classes can be found. Web environment is detected, false positive, and it requires configuration, which I am not able (and don't want) to provide.
So question now is - can I force non web environment, even when I have those classes in class path? Or is there any other way to solve the issue? (other than excluding them from Gradle dependencies)

Solved.
Following this question: How to prevent spring-boot autoconfiguration for spring-web?
I run application as follows.
#PropertySource("file:conf/app.properties")
#SpringBootApplication
public class Job {
public static void main(String[] args) throws Exception {
new SpringApplicationBuilder(Job.class).web(false).run(args);
}
}
Answers to above question also suggested to use property spring.main.web_environment=false or annotation #EnableAutoConfiguration(exclude = WebMvcAutoConfiguration.class). Both solutions haven't worked for me. Only programmatic solution works in my case.

Related

Cucumber configuration error in IntelliJ: Please annotate a glue class with some context configuration

So I recently started working on Cucumber and have been facing this issue.
This is the hierarchy of my module
As you can see this is submodule in my Spring Boot application (AcceptanceTests), so there are no main methods in it.
This is my CucumberSpringContextConfiguration class
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#CucumberContextConfiguration
public class CucumberSpringContextConfiguration {
}
This is my CucumberIntegrationTest class
#RunWith(Cucumber.class)
#CucumberOptions(
features = "src\test\resources\feature",
plugin = {"pretty", "html:target\\cucumber"},
glue = "com.#####.########.cucumberspringboot.cucumberglue"
)
public class CucumberIntegrationTest {}
I tried running this code with a main class (src/main/java),the code compiled successfully. But since that is not my requirement I removed it and now I am getting below error -
SEVERE: Exception while executing pickle
java.util.concurrent.ExecutionException:
io.cucumber.core.backend.CucumberBackendException: Please annotate a
glue class with some context configuration.
For example:
#CucumberContextConfiguration
#SpringBootTest(classes = TestConfig.class)
public class CucumberSpringConfiguration { }
Or:
#CucumberContextConfiguration
#ContextConfiguration( ... )
public class CucumberSpringConfiguration { }
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at io.cucumber.core.runtime.Runtime.run(Runtime.java:93)
at io.cucumber.core.cli.Main.run(Main.java:92)
at io.cucumber.core.cli.Main.main(Main.java:34)
Caused by: io.cucumber.core.backend.CucumberBackendException:
Please annotate a glue class with some context configuration.
Please suggest how to achieve this without using main class.
io.cucumber.core.cli.Main.run(Main.java:92) at io.cucumber.core.cli.Main.main(Main.java:34)
This part of the stacktrace shows you are using the Cucumber CLI rather than CucumberIntegrationTest. So you are probably running your feature file through IDEA. Presumably you clicked the green play icon in the gutter of a feature file.
Unfortunately Intelij IDEA makes some assumptions and bad guesses. If you look at the run configuration that was created you'll see that there is a command line with --glue argument that probably points at features. You'll have to change this manually.
It is currently unnecessary to guess the --glue argument and I have already asked the team behind Intelij IDEA to fix this at IDEA-243074 but the issue has gotten zero attention so far. You can upvote it, maybe something happens.
You can also avoid this problem by putting your feature files in a different location e.g. src/test/resources/com/#####/########/cucumberspringboot. Becaus this is the parent package of cucumberglue Ingelij IDEA is less likely to mess things up.
Also note: you are currently using JUnit 4 to run Cucumber. Spring Boot prefers using JUnit 5. You should use the Cucumber JUnit Platform Engine. You can find a minimal example here.

spring-integration: SplitterFactoryBean may only be referenced once

I have a Spring project (not using Spring Boot, if that's relevant) that I'm trying to connect to a local database using the Postgres JDBC driver. (The local database is actually Yugabyte, but that should be fully compatible with the Postgres driver.)
When starting the application, I get this error message:
java.lang.IllegalArgumentException: An AbstractMessageProducingMessageHandler may only be referenced once (org.springframework.integration.config.SplitterFactoryBean#0) - use scope="prototype"
at org.springframework.util.Assert.isTrue(Assert.java:118)
at org.springframework.integration.config.AbstractStandardMessageHandlerFactoryBean.checkReuse(AbstractStandardMessageHandlerFactoryBean.java:168)
at org.springframework.integration.config.AbstractStandardMessageHandlerFactoryBean.createHandler(AbstractStandardMessageHandlerFactoryBean.java:137)
at org.springframework.integration.config.AbstractSimpleMessageHandlerFactoryBean.createHandlerInternal(AbstractSimpleMessageHandlerFactoryBean.java:186)
at org.springframework.integration.config.AbstractSimpleMessageHandlerFactoryBean.getObject(AbstractSimpleMessageHandlerFactoryBean.java:174)
at org.springframework.integration.config.AbstractSimpleMessageHandlerFactoryBean.getObject(AbstractSimpleMessageHandlerFactoryBean.java:59)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:171)
... 52 more
I can't place this error at all. There is one similar question on Stack Overflow, but there the asker seems to actually know what they're doing and how this is related to spring integration. I, however, am not aware at all that I'm trying to 'reuse' anything. The referenced question also doesn't seem to be related to database configuration.
My setup/configuration is a bit involved, so I'll try to quote the parts that seem relevant.
I have a dao layer project that has the following gradle dependencies (among others):
implementation("org.springframework:spring-context:5.2.2.RELEASE")
implementation("org.springframework:spring-jdbc:5.2.2.RELEASE")
implementation("org.jooq:jooq-kotlin:3.14.11")
runtimeOnly("org.postgresql:postgresql:42.2.19.jre7")
In the same project, I have some configuration (in Kotlin):
#Configuration
open class Config {
#Bean
open fun jdbcTemplate(dataSource: DataSource): JdbcTemplate = JdbcTemplate(dataSource)
#Bean
open fun dslContext(): DSLContext = DefaultDSLContext(SQLDialect.POSTGRES)
#Configuration
#Profile("!unittest")
open inner class NonTestConfig {
#Bean
open fun dataSource(): DataSource {
return DriverManagerDataSource().apply {
// Hardcoded properties to be replaced by values from property file
setDriverClassName("org.postgresql.Driver")
url = "jdbc:postgresql://localhost:5433/demo"
username = "yugabyte"
password = "yugabyte"
}
}
}
}
(Notes: the DSLContext bean is used for JOOQL, included for completeness' sake. The inner class config is there because there is also a separate unit testing config for an embedded database - that one works fine!)
Now, the above project is used in my top-level project that contains the actual application. It's a maven runtime dependency there. I import the config class in this project's XML configuration, using this method:
<context:annotation-config />
<bean class="my.package.Config" />
Then trying to start the application produces the error message.
I figured out what the problem was, but I still don't know how it relates to a <splitter>.
The problem was that the Config class, apart from the database stuff, also included a bean to encrypt data. It turned out that this bean was also defined in another library used by the top-level project. Fixing this duplicate bean problem made the error go away.
I discovered this in a roundabout way: I included the dao project and its configuration in a different top-level project that uses Spring Boot. This led to a clear error message about the encryptor bean having two definitions.
If anyone can explain why the error message is so cryptic in the non-Boot case, that would be a nice complementary answer.

how to maintain spring-boot external configuration for all environment

I have external property files in spring boot based project, I am configuring like below.
#Configuration
#PropertySource("file:${application_home}config.properties")
#ComponentScan({"com.myorg.project","com.myorg.project.module"})
#EnableAutoConfiguration
#EnableSpringDataWebSupport
public class Application extends SpringBootServletInitializer{
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Now I have many environments like dev,prod,staging. Like this I am having almost 7 environment.
I am maintaining like below
dev-config.properties
prod-config.properties
stg-config.properties
So adding any new config parameters becoming tedious task, as developers interested only in adding to dev-config.properties so extending the same to other is becoming problematic also developer may miss adding the same config variable name to other config files.
Is there any way where adding any config variable should be reflected other with default(if any change required will be handled by devops)
Is there anyway maintain what is changing from current release to old release ?
Because of this it is affecting CI-CD also.

Where does spring boot configure default application.properties

By default Spring Boot will automatically load properties from classpath:/application.properties
I want to know where is this auto configuration source code.
I want to exclude from my app.
IE: #EnableAutoConfiguration(exclude=XXXXAutoconfiguration.class)
The reason is:
Because I cannot override the default application.properties by an external property using #PropertySource
#SpringBootApplication
#ComponentScan(basePackages = {"com.test.green.ws"})
#PropertySource(value = {"classpath:/application.properties", "file:/opt/green-ws/application.properties"})
public class GreenWSApplication {
public static void main(String[] args) {
SpringApplication.run(GreenWSApplication.class, args);
}
}
There are many ways to override property keys without disabling the whole externalized configuration feature; and that's actually the goal.
You can see here the order the properties are considered in. For example, you can add that external properties file in a config folder right next to the packaged JAR, or even configure the file location yourself.
Now if you really want to disable all of that (and the Boot team strongly suggests not to do that), you can register your own EnvironmentPostProcessor (see here) and remove PropertySources from MutablePropertySources, which you can fetch with configurableEnvironment. getPropertySources().
There's no easier way to do that because:
this comes really early in the application init phase, before auto-configurations
this is not something you should do, as it will have many side effects

Mapping conflict when deploying with spring boot Jersey starter deployed as WAR

We're using Spring Boot with its Jersey Starter and deploy it as a WAR, programmatically deployed into another application's embedded Tomcat.
After our application startup, in some environments, a mapping conflict occurs and is logged as follows:
o.g.j.s.i.JerseyServletContainerInitializer : Mapping conflict. A Servlet registration exists with same mapping as the Jersey servlet application, named com.vidal.pmsi.config.PmsiResourceConfiguration, at the servlet mapping, /*.
The resource configuration is as follows:
#ApplicationPath("/")
#ExposedApplication
#Component
public class PmsiResourceConfiguration extends ResourceConfig {
public PmsiResourceConfiguration() {
packages("com.vidal.pmsi.api");
packages("com.vidal.pmsi.config");
property(ServerProperties.BV_DISABLE_VALIDATE_ON_EXECUTABLE_OVERRIDE_CHECK, true);
property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
}
}
As far as I understand, Spring Boot Jersey Starter will register a 'jerseyServlet'-named servlet mapping to '/*'.
In some environments, Jersey's own JerseyServletContainerInitializer will trigger after SpringApplication startup, failing to register PmsiResourceConfiguration because of the existing jerseyServlet mapping.
This is a problem because of our own open-source library that tries (and crashes at) getting the context path at startup:
// compile-time generated Linkers.java
#WebListener
#Generated("fr.vidal.oss.jax_rs_linker.LinkerAnnotationProcessor")
public final class Linkers implements ServletContextListener {
private static String contextPath = "";
private static String applicationName = ApplicationName.get();
#Override
public void contextInitialized(ServletContextEvent sce) {
//applicationName = FQCN of PmsiResourceConfiguration
contextPath = ContextPaths.contextPath(sce.getServletContext(), applicationName);
}
// [...]
}
// ContextPaths.java
public static String contextPath(ServletContext servletContext, String registeredKey) {
// registeredKey is therefore the FQCN of PmsiResourceConfiguration
String mappedPath = stripWildcard(servletContext.getServletRegistration(registeredKey).getMappings().iterator().next());
return servletContext.getContextPath() + mappedPath;
}
The last snippet of code will fail as there is no mapping for the registered resource configuration class (there is only one for 'jerseyServlet' key).
This does not fail when there isn't any mapping conflict reported.
Why?
I was running my head against a similar problem where I have a Spring Boot Application with a Jersey JAX-RS Webservice. Everything worked fine when using the embeddedTomcat but it went to hell when I tried deploying the war on a regular Tomcat of the same version (Tomcat8).
The problem is that by default the embeddedTomcat does not scan for ServletContainerInitializer in jar files, yet the regular one does and that conflicted with the ServletContainer/Config that Spring sets up.
Apart from excluding the jar containing the JerseyServletContainerInitializer I found an option to tell tomcat to filter out this particular ServletContainerInitializer (SCI). Setting the containerSciFilter attribute on the context helped:
<Context containerSciFilter="JerseyServletContainerInitializer">
...
</Context>
I did not define any SCI in my META-INF/services but the jar that contains the JerseySCI has defined it and it was on the right path to be found by Tomcat.
Considering that this was the closest matchinb question and without an answer I don't repost my question and try to answer this one as I believe that the cause is the same.

Resources