I have a Spring Boot application. I have several customers who each have a version of this application, with a few custom #Component, #Service, etc, classes. I am trying to abstract those custom Spring beans out into library jars which I can place on the classpath and load dynamically.
For example, suppose I have an interface in my core library:
public interface MyInterface {
String doSomething();
}
I have a different implementations of MyInterface in my customer-specific libraries, all annotated with #Component.
I want to be able to put
#Autowired
List<MyInterface> components;
in my main application.
Then, I want to just place my main application in a folder with the appropriate customer's library (or multiple) and run it with
java -cp . -jar my-application.jar
and have that #Autowired pick up the customer's specific components.
Is this possible? I don't want to use #Import in the main application, because that requires knowing which customer's library is being loaded.
I did figure this out. Here is a demo project for loading #Component objects from plugins at runtime.
https://github.com/imnotpete/plugin-demo
It uses the loader.properties file to specify a folder to add to the classpath, and then #ComponentScan can include any jars from that folder when autowiring.
loader.properties:
loader.path=plugins
Related
Let say I have an external jar named data-access-0.0.1.jar that contains Spring annotation like #Component, #Bean. But this jar does NOT contain the main method to run as a Spring application (means no #SpringBootApplication, no #ComponentScan, ...).
Now I have another jar named employee.0.0.1.jar (does have the main method to run as Spring boot application - #SpringBootApplication), that use data-access-0.0.1.jar as a dependency. But somehow it does not scan #Bean, #Component in an external jar (error when starting the app, no bean with type "myComponent" found).
I think #ComponentScan in employee-0.0.1.jar should configure base packages include a package from the external jar and it should work, but I do not want to apply this mechanism. I want to somehow configure in the external jar so that any another jar that depend on it should scan the whole jar for autowiring
I am learning Spring Boot. I made a simple Spring Boot project that can output a hello world string at http://localhost:8080/welcome
I use Maven to build my project that would output a jar file.
To start up my spring boot app, I use the command as below
java -jar my-springboot-app.jar
My question is:
How is java smart enough to locate my main class and its main method (e.g. the application launcher)?
I checked the jar file and browsed those BOOT-INF & META-INF and could not find any clues.
Does the spring boot framework (#SpringBootApplication) or maven automatically do the magic for me?
In case of spring boot jar the things are little bit more complicated than regular jar. Mainly because spring boot applicaton jar is not really a JAR (by jar I mean something that has manifest and compiled classes). Regular JARs can be "recognized" and processed by jvm, however in Spring Boot there are also packed dependencies (take a look at BOOT-INF/lib) so its jars inside JARs. How to read this?
It turns out that spring boot always has its own main class that is indeed referred to in MANIFEST.MF and this a real entry point of the packaged application.
The manifest file contains the following lines:
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.example.demo.DemoApplication
Main-Class is a JVM entry point. This class, written by spring developers, basically does two things:
- Establishes a special class loader to deal with a "non-regular-jar" nature of spring boot application. Due to this special class loaders spring boot application that contains "jars" in BOOT-INF/lib can be processed, for example, regular java class loaders apparently cannot do this.
- Calls the main method of Start-Class value. The Start-Class is a something unique to spring boot applications and it denotes the class that contains a "main" method - the class you write and the class you think is an entry point :) But from the point of view of the spring boot infrastructure its just a class that has an "ordinary" main method - a method that can be called by reflection.
Now regarding the question "who builds the manifest":
This MANIFEST.MF is usually created automatically by plugins offered by Spring Developers for build systems like Maven or Gradle.
For example, the plugin looks like this:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
During its work, this plugin identifies your main class (com.example.demo.DemoApplication in my example). This class is marked with #SpringBootApplication annotation and has a public static void main method.
However, if you put many classes like this the plugin probably won't recognize the correct class so you'll need to configure the plugin properties in POM.xml to specify the right class.
Java classes are executed within a larger context,
you run java -jar somejar.jar the class in question will be selected in the .jar file's manifest.
#SpringBootApplication will have componentscan, enabling auto configuration(autowired)
componentscan - to identify all the controller, service and configuration classes within the package.
I have a source project, here are some code
#Configuration
public class AnalyzeSyntaxServiceConfig {
#Bean
public AnalyzeSyntaxService analyzeSyntaxService() {
return new AnalyzeSyntaxService();
}
}
and use it in the source project like this
#Autowired
private AnalyzeSyntaxService analyzeSyntaxService;
it works well
then I package it as a jar file, and add it to the target project as a dependency in pom.xml, and I try to use this above service in the same way
#Autowired
private AnalyzeSyntaxService analyzeSyntaxService;
but it's null, why?
Are the package names different between the source and consuming/dependent code base?
A Spring Boot application will scan from the package the SpringBootApplication is placed in and any child packages.
If you have it within the same project, and your configuration class is structured that it is within the same package or a child package e.g.
com.myapp or com.myapp.configs it will be scanned and picked up.
When importing it to a different project you will need to manually component scan for the configuration via the #ComponentScan annotation and provide it with a package to scan for your configuration.
https://github.com/Flaw101/springbootmixin/tree/example/componentscanning
Because the JacksonConfig.class is in a parent (different) package the ComponentScan does not work. The Application class scans everything in com.darrenforsythe.mixinabstractclass and it's child packages. To make Spring Boot scan the JacksonConfig we have to be explicit and add the #ComponentScan("com.darrenforsythe") to the application.
If you uncomment the #ComponentScan within the MixinasbtractclassApplication the tests then pass again as it will load the JacksonConfig again.
Additionally, I would recommend using constructor. injection this would avoid the Autowired dependency being null and inform you on init of the ApplicationContext rather than at runtime.
Make sure that package in which the class available was scanned. Here is the annotation for that, put this annotation in SpringBoot application class.
#ComponentScan("com.abc.xyz")
Also, see if by any chance the class which has this autowired one is getting instantiated. If that class is instantied then ofcource the autowired ones will be null.
I'm running a Spring Boot Application within a Tomcat instance packaged as a war file. I would like to be able to add "packages" to my instance in the form of rest services. To that end I need to be able to configure scanBasePackages in the #SpringBootApplication annotation at runtime. i.e. when tomcat starts up. For now I have ...
#SpringBootApplication(scanBasePackages="path1, path2")
public class RestApplication extends SpringBootServletInitializer {
//code
}
...but I would like to have path2 be configurable and alternately be able to add path3, etc. if desired. How can I achieve this? I understand that I can't configure the String in the annotation so my question is about what other alternatives I have to this annotation for setting this.
Cheers.
you can try to use something like this in your project
#Configuration
#Profile("custom-profile")
#ImportResource( {"file:path/additional-context.xml" } )
public class ConfigClass { }
and configure additional packages scanning in this file then.
<context:component-scan base-package="x.y.z.service, x.y.z.controller, x.y.z.dao" />
Note, your resource additional-context.xml declared as external and you have ability to change it without rebuilding war at all.
#ImportResource will be handled after declaring profile "custom-profile" as active. It's a safe way for starting application with "default configuration" without any "additional-context.xml" file of disk.
Have you tried this:
#SpringBootApplication(scanBasePackages={"path1", "path2"})
Hope this helps :)
I'm working on a Spring Boot application. I want to provide a (pretty rudimentary) plugin system. Initially I was hoping it'd be enough to just add the JAR to the classpath like so:
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class sysclass = URLClassLoader.class;
Method method = sysclass.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(sysloader, new File("./plugin/plugin.jar").toURI().toURL());
SpringApplication.run(Application.class, args);
In the plugin.jar is a class annotated with #Controller and a RequestMapping. The context loads fine and the constructor of the controller is getting called as well. However, looking at the logs, I can see that the RequestMapping did not get picked up.
Additionally, if I try to #Autowire a JpaRepository in the plugin controller it fails complaining that it can't find the repository interface class (which I'm guessing is some problem that arose from me messing around with the ClassLoader).
Just autowiring the repository in my main application works fine though, so it shouldn't be an issue with its configuration.
Is there something I'm doing wrong? Can I maybe configure Springs ApplicationContext or its ClassLoader to make this work correctly?
To summarise, I want to load some Controllers (and maybe other Spring components) at runtime from an external JAR in another folder.
For now I just ended up declaring profiles in my application pom for my various plugins and I just compile it using the profiles of the plugins that I want. It's not very dynamic but at least I can separate the plugin development from the application development completely and have Spring pick up on all of the plugin's components.
This is not exactly what I wanted, but I figured I'd describe how I "solved" it anyway. If anyone knows a way to make this work with external JARs I'd be happy to accept that answer instead!
I solved this by importing the dependency in which my controller resides, and then calling the packages of the controller and the main run method the same name (but in different projects). It's a hack, but it works.