Loading Spring components from external JAR file - java

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.

Related

Using SpringExtension with Tomcat and Hibernate web app

We are trying to use spring-test's SpringExtension to write integration tests for our Spring and Hibernate-based Tomcat web application. Our sessionFactory bean configuration has the property configured mappingJarLocations with a sample value as /WEB-INF/lib/company-common*.jar which contains hibernate mapping files. In both actual deployment and Eclipse dev deployment, this works fine as the docBasePath (in Servlet environment) is appended to this pattern and the files are getting resolved. But this is not the case while running JUnit test cases either in a local or a CI environment.
We tried our best to use the provided support by having few overridden implementations of WebTestContextBootstraper, GenricXmlWebContextLoader, XmlWebApplicationContext, and WebDelegatingSmartContextLoader but had to finally give up as we cannot override the final method org.springframework.test.context.web.AbstractGenericWebContextLoader.loadContext(MergedContextConfiguration) to provide the custom implementation of XmlWebApplicationContext. Our current approach is to manually create the application context and use it in the tests.
Here is the project structure:
Project_WebApp
|--src/**
|--WebContent/**
|--pom.xml
When the app is packaged as Project_WebApp.war, the dependencies are inside WEB-INF/lib from the root of extracted war. When deployed as a webapp in Tomcat using Eclipse, the dependencies are copied to <Eclipse_Workspace_Dir>/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/Project_WebApp/WEB-INF/lib. In both cases, the dependencies are available at <Resource_Base_Path>/WEB-INF/lib and Resource_Base_Path has no relation to Project_WebApp base directory.
Questions:
Did any one use SpringExtension in a scenario similar to above? If so can you suggest any alternative approaches?
Instead of /WEB-INF/lib/company-common*.jar, we tried a classpath-based pattern but didn't work as the obtained class path resources don't match the pattern. Is there anything else to try here?

How does Java runtime find my main class?

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.

Loading Spring beans from library without importing configuration file?

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

Configuring base package scan for Spring Boot Application at Runtime

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 :)

Spring cannot resolve type in WEB-INF/libs

I recently start a spring web project with HBase. The problem is,spring seems unable to resolve types under WEB-INF/libs. it complains about "unresolved org.apache.hadoop.conf.Configuration" which is indirectly referenced from required .class file, which is actually in a .jar under the WEB-INF/lib library. Is there anything to be set for spring container to find it?
You need to make sure that Spring itself was loaded by the same class loader: the WAR class loader. This class loader should include all JARs in WEB-INF/lib and all .class files in WEB-INF/classes.
There's a way to pass a class loader into Spring to use a different class loader than the one that loaded it (or the Thread's context class loader), but that gets more complicated.
OK I got the problem. It seems that my project depended on hadoop-core-1.0.2 whilst hbase depend on hadoop-core-1.0.0 and that confused Spring. I fixed the dependency and problem solved.

Categories

Resources