How to make non executable library (jar) using spring boot? - java

I am working on an application and deploying in cloud foundry. Internally it is having 3 custom dependencies developed by our team.
All 3 dependencies are boot project and have their #Configuration.
Dependency 1 is to interact with Couchbase. Source of this dependency is boot project.
Dependency 2 is to interact with FluentD for logging. Source of this dependency is boot project.
Dependency 3 is to interact with external rest service. Source of this dependency is boot project.
Dependency 4 is having all these above 3 dependencies and also having few utils classes and constants.
I am using this dependency 4 in multiple web applications which are having WebMVC implementation.
Everything is working fine in my local machine. But when I am pushing this web application on cloud, sometimes libraries getting executed before the web application which is crashing my app intermittently. Good thing app is getting recover in few seconds.
I did below changes in my libraries (jars/dependencies) and tried on cloud. After doing these changes ratio of app crash reduces, but unfortunately it is still crashing sometimes and I am able to see dependencies gets executed before application.
Added bootRepackage.enabled = false bootRepackage.withJarTask = jar in library's build.gradle
Took off from library and added in my web application
springBoot {
mainClass = "com.java.Application"
executable = true
}
Took off #SpringBootApplication from libraries(dependencies/jars). It's just in my web application now.
I do not know these are the only steps to make a boot dependency non-executable or I would have to do something else. Please let me know if I am missing something.
Here is the sample off application class of one of my dependency.
import org.springframework.context.annotation.ComponentScan;
#ComponentScan
public class LoggingApplication {
}
Sample of Web application main class.
#SpringBootApplication
#EnableWebMvc
#Import(LoggingApplication.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

You didn't expose stacktrace nor provided any specifics of "crash". So looking into my crystal ball, this sounds like you are doing some work during wiring phase of Spring IoC container. This "work" should be moved into #PostConstruct handlers, so that you are sure it will be executed after Spring Context is fully created and initialized.

Related

Spring-Boot Components from other jar are not in context

I have following situation. JDK 17, Spring-Boot: 2.6.2. A gradle multi-project. One project is a library (java-library, no spring boot plugin). Another project is a spring boot application with spring boot plugin. Generally, the spring dependency management plugin is not used, gradle platform concept is used instead. Application project includes dependency to library project per "implementation(project(':.."
Library:
Library project and spring boot application project have different packages.
like:
library root package is a.b.c and application root package is a.b.d
Library project has in root package of the package hierarchy a configuration class (for example a.b.c.LibraryConfig) which is annotated with #Configuration annotation and with #ComponentScan annotation, which has attribute "basePackageClass" pointing to this configuration class:
#Configuration
#ComponentScan(basePackageClass = LibraryConfig.class)
public class LibraryConfig {
Inside of library hierarchy (so in packages a.b.c.*) are services and rest controllers.
Application:
Application has in root package of the package hierarchy (for example in a.b.d) application class and in sub-package "config" it has configuration class (would be for example a.b.d.config.AppConfig), which imports the configuration class from library:
#Configuration
#Import(a.b.c.LibraryConfig.class)
public class AppConfig {
Problem:
Classes from the library are there in runtime - we are able to load them.
No any component of the library is in context (not registered), neither services nor rest controllers.
What are we missing?
We have tried different constellations of imports and scans. Also added component scan to application class and set there particular packages. Nothing helped.
The problem was, that the main application was using spring boot indexing annotation processor and library - not. So after enabling the annotation processor for library problem was solved.

Spring context indexer not working for dependency jar

I have some library jar lib.jar (made using spring boot but packaged as normal jar without spring boot plugin) which is made of spring boot and contains spring.components file generated by spring-context-indexer.
Now, I'm using this jar in my application which also has spring-context-indexer and its own spring.components file and uses some of the bean defined in lib.jar.
When I start my application, spring should register all beans defined in spring.components of lib.jar and spring.components of application. But spring isn't registering any of bean of lib.jar.
I tried using basePackages property of #SpringBootApplication but no results.
I even copied all entries of spring.components of lib.jar into spring.components of my application but no result.
Can anyone please help me?
TL;DR
If you're using Spring Data, #SpringBootApplication.scanBasePackages is not enough, you also need #EnableJdbcRepositories (or *Jpa* or whatsoever).
package application;
// without this annotation all Repository classes
// from library will be missing
#EnableJdbcRepositories({
"application",
"library"
})
#SpringBootApplication(
scanBasePackages = {
"application",
"library"
}
)
public class Application {
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
}
}
Some more info
Okay, maybe I'm a bit late, but I decided to investigate this case a bit.
That's what I've found as of 2 Feb 2022:
All META-INF/spring.components files are loaded in CandidateComponentsIndexLoader.doLoadIndex. You can use debug to check whether it sees file from lib or not
CandidateComponentsIndexLoader then creates CandidateComponentsIndex, which is then stored in the component scanner, for me it is AnnotationConfigServletWebServerApplicationContext.scanner.componentsIndex
Then in ClassPathScanningCandidateComponentProvider findCandidateComponents is called, which, if componentsIndex is not null, just gets components from that index by provided basePackage.
That's why missing basePackage is crucial.
I haven't dug into the Spring Data algorithms, but in my case Spring hadn't been generating library Repositories until I added the #EnableJdbcRepositories with packages.
P.S. All links represent files at the 5.3.15 tag, latest atm.

Excluding PropertySource from scanning in SpringBoot WebMvcTest or other application context for WebMvcTest

I develop web app with Spring Boot. I have problem with unit test for web layer.
For these tests I'm using annotation #WebMvcTest. Problem is that my main configuration class contains #PropertySource with java arg, which contains path to external properties file, and when I start my unit web test, error is occured that this java arg can't be parsed(of course I can add this java arg to run configuration of my test, but my web unit tests don't need this file).
My main config class:
#SpringBootApplication
#PropertySource(value = {"${pathto.configfile}"})
#EnableAspectJAutoProxy
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
My first solution was to create separate configuration class with "!test" profile, and relocate #PropertySource("..") to it. And to my unit test add #ActiveProfiles("test")
My second Configuration class:
#Configuration
#Profile({"!test"})
#PropertySource(value = {"${pathto.configfile}"})
public class PropertyConfiguration {
}
For unit web test this solution works very good. But new problem appears in starting of my web app. In my external properties file I have
property spring.profiles.active. To this property I assign or db or file value. Depending on its value, apropriate implementation of Repository is created and injected to my service layer. When value is db, app starts good,
but when value is file error is being thrown: NoSuchBeanDefinitionException.
When I come back to my previous version(without second configuration file), app starts good in both cases(but not web unit tests)
So, explain please, why when value of spring.profiles.active=db, app starts good, but when spring.profiles.active=file- failed.And how I can solve my task?
I attempted to find how I can add other application context to my web unit tests, but I didn't find.
Any idea?:)
For my database repositories I'm using Spring Data JPA, so I don't create implementation of these repositories, but I create implementations of my file
repositories, and my implementations had #Profile("file"). After deleting this annotation from implementations, it leaved only on interfaces. I don't know why one config class worked, but two classes didn't. But problem is solved)

Using Spring boot/cloud with Amazon AWS lambda does not inject values

I have an AWS lambda RequestHandler class which is invoked directly by AWS. Eventually I need to get it working with Spring Boot because I need it to be able to retrieve data from Spring Cloud configuration server.
The problem is that the code works if I run it locally from my own dev environment but fails to inject config values when deployed on AWS.
#Configuration
#EnableAutoConfiguration
#ComponentScan("my.package")
public class MyClass implements com.amazonaws.services.lambda.runtime.RequestHandler<I, O> {
public O handleRequest(I input, Context context) {
ApplicationContext applicationContext = new SpringApplicationBuilder()
.main(getClass())
.showBanner(false)
.web(false)
.sources(getClass())
.addCommandLineProperties(false)
.build()
.run();
log.info(applicationContext.getBean(SomeConfigClass.class).foo);
// prints cloud-injected value when running from local dev env
//
// prints "${path.to.value}" literal when running from AWS
// even though Spring Boot starts successfully without errors
}
}
#Configuration
public class SomeConfigClass {
#Value("${path.to.value}")
public String foo;
}
src/main/resources/bootstrap.yml:
spring:
application:
name: my_service
cloud:
config:
uri: http://my.server
failFast: true
profile: localdev
What have I tried:
using regular Spring MVC, but this doesn't have integration with #Value injection/Spring cloud.
using #PropertySource - but found out it doesn't support .yml files
verified to ensure the config server is serving requests to any IP address (there's no IP address filtering)
running curl to ensure the value is brought back
verified to ensure that .jar actually contains bootstrap.yml at jar root
verified to ensure that .jar actually contains Spring Boot classes. FWIW I'm using Maven shade plugin which packages the project into a fat .jar with all dependencies.
Note: AWS Lambda does not support environment variables and therefore I can not set anything like spring.application.name (neither as environment variable nor as -D parameter). Nor I can control the underlying classes which actually launch MyClass - this is completely transparent to the end user. I just package the jar and provide the entry point (class name), rest is taken care of.
Is there anything I could have missed? Any way I could debug this better?
After a bit of debugging I have determined that the issue is with using the Maven Shade plugin. Spring Boot looks in its autoconfigure jar for a META-INF/spring.factories jar see here for some information on this. In order to package a Spring Boot jar correctly you need to use the Spring Boot Maven Plugin and set it up to run during the maven repackage phase. The reason it works in your local IDE is because you are not running the Shade packaged jar. They do some special magic in their plugin to get things in the right spot that the Shade plugin is unaware of.
I was able to create some sample code that initially was not injecting values but works now that I used the correct plugin. See this GitHub repo to check out what I have done.
I did not connect it with Spring Cloud but now that the rest of the Spring Boot injection is working I think it should be straightforward.
As I mentioned in the comments you may want to consider just a simple REST call to get the cloud configuration and inject it yourself to save on the overhead of loading a Spring application with every request.
UPDATE: For Spring Boot 1.4.x you must provide this configuration in the Spring Boot plugin:
<configuration>
<layout>MODULE</layout>
</configuration>
If you do not then by default the new behavior of the plugin is to put all of your jars under BOOT-INF as the intent is for the jar to be executable and have the bootstrap process load it. I found this out while addressing adding a warning for the situation that was encountered here. See https://github.com/spring-projects/spring-boot/issues/5465 for reference.

Converting a Spring Boot JAR Application to a WAR

I have tried to run "Converting a Spring Boot JAR Application to a WAR" application by converting into a war package as instructed...and since it was mentioned that void main() is no longer needed,I removed it and tried to build it using gradle but it throws error unable to find main class.
The content of class after my modification is as below
Application.java
#Configuration
#EnableAutoConfiguration
#ComponentScan
public class Application {
}
what is the mistake am I making?
If you don't want an executable war then remove the Spring Boot plugin. If you do, leave it in, and keep the main().

Categories