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().
Related
I have a Spring Boot application which copies external JAR files to a folder, depending on certain conditions. These JARs can contain many Spring components (i.e. classes annotated or meta-annotated with #Component) and the Spring application should be able scan and instantiate for these beans. Is it possible, based on certain conditions, to dynamically load the contents of the JAR files and make them available to the Spring application context? I am fully aware of the security implications this has.
I have read about the different types of Launchers which Spring provides for its executable JAR format, such as JarLauncher and PropertiesLauncher, but it looks like that these launchers do not detect changes to the classpath, but instead only scan the directories once for JAR files.
The following simple application demonstrates the problem:
// .../Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args) {
System.out.println("Please copy JAR files and press Enter ...");
System.in.read();
SpringApplication.run(Application.class, args);
}
}
Replace the default JarLauncher with PropertiesLauncher:
// build.gradle
tasks.named('bootJar') {
manifest {
attributes 'Main-Class': 'org.springframework.boot.loader.PropertiesLauncher',
'Start-Class': 'com.example.customlauncher.Application'
}
}
Specify the location to the external JARs in the properties file of the PropertiesLauncher:
# .../resources/loader.properties
loader.path=file:/path/to/dir
The application is a Spring Initializer Gradle application and packaged by running the bootJar task: ./gradlew bootJar.
It is then started with the following command:
java -jar build/libs/customlauncher-0.0.1-SNAPSHOT.jar
This works if the JAR file is already present at the specified location (/path/to/dir), but it does not work if the java command is executed while the directory is empty and the JAR file is then copied while the app waits for the user to copy the files and press Enter ↲.
There are a couple of related questions, but it looks like they all assume that the JAR files already exist at the time of starting the JVM:
How to put a directory first on the classpath with Spring Boot?
Spring Boot Executable Jar with Classpath
SpringBoot external jar not load
Is there a way to achieve this without too many awkard hacks? Or is recommended to utilize something like OSGi? Am I looking at this completely wrong and there is a better way to have JARs on the classpath that do not need always need loading (if the JAR is "disabled", it should not be loaded/compiled by the JVM, should not be picked up by Spring, etc.)?
It looks like this is possible if the JAR files are copied before starting the Spring application. It feels hackish, but it works. Use at your own risk!
You need two classes, one for bootstrapping the external JARs, which will then start the second via a manually created PropertiesLauncher. The bootstrapping class can be a plain old regular Java class (but it can be a Spring Boot Application too) and only the second class needs to be a SpringBootApplication.
// BootstrapApplication.java
public class BootstrapApplication {
public static void main(String[] args) {
System.out.println("Please copy JAR files and press Enter ...");
System.in.read();
PropertiesLauncher.main(args);
}
}
// Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
In the gradle file, we can switch back to the default JarLauncher, by removing the bootJar task manifest configuration and applying settings via the springBoot configuration block. mainClass will end up as Start-Class in the MANIFEST.MF file.
// build.gradle
springBoot {
mainClass = 'com.example.customlauncher.BootstrapApplication'
}
In the properties file for the loader, a new property needs to be set, which points to the real application class. The settings in this file are only picked up by PropertiesLauncher and ignored by JarLauncher. In other words: JarLauncher delegates to Start-Class from the manifest file and PropertiesLauncher delegates to loader.main from its properties file.
# .../resources/loader.properties
loader.path=file:/path/to/dir
loader.main=com.example.customlauncher.Application
Spring (Boot) will first call the main method of BootstrapApplication, as specified in the MANIFEST.MF file (controlled via springBoot configuration block in the build.gradle file). In the implementation of this main method, a new PropertiesLauncher is created with the main class set to the "real" application (i.e. Application).
Executing the application is still done via the same invocation:
java -jar build/libs/customlauncher-0.0.1-SNAPSHOT.jar
Any JAR files added to /path/to/dir after the JVM has started, but before calling PropertiesLauncher#main in BootstrapApplication are then available in the classpath and application context as seen from Application.
I have 2 Projects.
All the API contract (interfaces) defines in the demo-parent project (spring boot application)
The implementation for those defined in the demo-child project (spring boot application)
demo-parent is a dependency for demo-child, defined in the pom.xml of demo-child
In demo-parent :
AccessAPI.java
public interface AccessAPI {
void call();
}
SpringDemoParentApplication.java
#SpringBootApplication
public class SpringDemoParentApplication {
#Autowired
private AccessAPI accessAPI;
public static void main(String[] args) {
SpringApplication.run(SpringDemoParentApplication.class, args);
}
#PostConstruct
void exec() {
accessAPI.call();
}
}
In demo-child I have the implementation for the service :
AccessAPIImpl.java
#Service
public class AccessAPIImpl implements AccessAPI {
#Override
public void call() {
System.out.println("Executing from AccessAPIImpl");
}
}
Goal trying to achieve :
I must be able to build these projects independently, and pass the demo-child project jar via classpath to pick the implementation and inject all the implementation when running of demo-parent .
Such as :
>java -jar demo-parent.jar -cp demo-child.jar
I expected that the implementation would be picked up from the demo-child and autoinjected but It is not working as expected.
Note : I dont want to add demo-child dependency on demo-parent, the implementations/dependency must be picked up at runtime.
Please check the Git repository :
https://github.com/anthonyvk/spring-demo-child
https://github.com/anthonyvk/spring-demo-parent
TL; TR:
java -cp demo-parent.jar -Dloader.path=demo-child.jar org.springframework.boot.loader.PropertiesLauncher
if the demo-child.jar is a Spring Boot application, i.e. if the jar is repacked by the Spring Boot plugin, you need to change the loader.path:
java -cp demo-parent.jar -Dloader.path='demo-child.jar!/BOOT-INF/classes' org.springframework.boot.loader.PropertiesLauncher
Explanation:
By default, Spring Boot uses JarLauncher, which looks only in BOOT-INF/lib/ inside the jar archive, -cp argument has no effect. The commands above switch to PropertiesLauncher, which honors loader.path parameter (-cp equivalent). For details see The Executable Jar Format
By the way, the project structure seems to be a bit problematic:
adding spring boot application (demo-parent.jar) as a dependency to demo-child.jar is tricky, as demo-parent.jar is repacked by the Spring Boot plugin and maven cannot find AccessAPI class
there are runtime circular dependencies, even if not explicitly specified in the POM
I don't know detailed requirements, but if possible, I would suggest:
extracting interfaces to a separate project, let's say demo-api
making demo-parent and demo-child depended on demo-api, not on each other
making demo-api and demo-child regular jar's, not Spring Boot applications (removing Spring Boot plugin)
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.
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.
I am developing a Spring Boot application using STS with the Gradle plugin.
I have a different configuration for tests, to prevent our Selenium tests from having to login.
So in src/test/java/etc I have something like this:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public static class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests().anyRequest().permitAll();
}
}
Whereas in src/main/java I have an equivalent class that configures login etc, requiring login for all pages.
If I run the application through the Gradle plugin (bootRun), everything works fine.
However, if I run or debug it through Eclipse directly (e.g. right clicking on the project, Run As->Spring Boot App or by clicking
the run/debug buttons in the Spring or Java view) then the test config is applied, so access is granted to all pages without login.
I'm guessing that the test classes are being included in the classpath when I start the application this way.
Is there an easy way to prevent this from happening?
When you run the test from eclipse, the classpath is prepared by eclipse (and not by maven or gradle).
Eclipse only uses one classpath per project and does not know anything about dependency scopes (like 'compile' or 'test').
So the classpath always contains any resources of a referenced project.
You cannot change this behavior of eclipse.
You need to use naming conventions, profile etc. to avoid accidental use of test resources.
You can append #TestComponent to you test configuration class. These bean configurations will be skipped during component scan of your application. Depending on the component scan configuration, you need to define an #ComponentScan exclude filter:
excludeFilters = #ComponentScan.Filter(value = TestComponent.class, type = FilterType.ANNOTATION))