Using SystemLoader / SpringFactoryLoader to load external Jar in Spring-Project - java

first: I'm really new to spring-boot and maven. So I still don't get how everything plugs together.
What I'm trying to achieve is some kind of plugin-feature for my application. From my research it seems the best way to do this is using ServiceLoader or the spring-boot implmentation of the SpringFactoriesLoader.
According to several instructions from the web I put two projects together
James (the main application) GitHub
TemperatureSensor (the plugin) GitHub
The JamesApplication provides an interfaces which is supposed to be implemented (de.maxrakete.james.device.domain.DeviceInterface).
The TemperatureSensor implements said class and exposes this in several ways.
For the ServiceLoader in in the file META-INF\services\de.maxrakete.james.device.domain.DeviceInterface with this content
de.maxrakete.james.plugin.TemperatureSensor.TemperatureSensor
For the SpringFactoriesLoader in the file META-INF\spring.factories with this content
de.maxrakete.james.device.domain.DeviceInterface=de.maxrakete.james.plugin.TemperatureSensor.TemperatureSensor
According to this page I tried two different implementations (see in the onApplicationEvent-function) in the MainApplication:
#SpringBootApplication
public class JamesApplication implements ApplicationListener<ApplicationReadyEvent> {
public static void main(String[] args) {
SpringApplication.run(JamesApplication.class, args);
ClassLoader cl = ClassLoader.getSystemClassLoader();
URL[] urls = ((URLClassLoader)cl).getURLs();
for(URL url: urls){
System.out.println("Classpath file: " + url.getFile());
}
}
#Override
public void onApplicationEvent(ApplicationReadyEvent event) {
ServiceLoader<DeviceInterface> loader = ServiceLoader.load(DeviceInterface.class);
loader.iterator();
List<DeviceInterface> foos = SpringFactoriesLoader.loadFactories(DeviceInterface.class, null);
}
}
I'm trying both ways to load the jar, but nothing is happening (I'm supposed to get some log-messages from the plugin) but this is not happening.
The way I'm running the application is like this:
java -cp "./plugins/TemperatureSensor-0.0.1-SNAPSHOT.jar" -jar james.war
As you see I'm trying to add the jar in the subfolder to the classpath, but in the ouput of the main-function (where I try to print all the files in the classpath) I only get Classpath file: /home/max/folder/james.war
Conclusion
So, there are three possible error-sources
Wrong cli command to add classpath files
Wrong declaration of interfaces in the META-INF folder
Wrong implementation of the Loader
Maybe I'm compiling the sources the wrong way?
Wrong configuration of the pom.xml
I really have no idea what the problem might be. I tried to provide you with as much information as possible and all the steps of my research. I hope someone finds some helpful clues, which I might have overlooked.
Thanks veryone!

Related

Trying to add a path to classpath through gradle or some other way

I have a gradle/SpringBoot project. I'm trying to run some junit tests that use 3rd party annotations to inject classes. The injected classes are creating a text file (call it 'foo.txt') under $(PROJECT_DIR)/app/build/resources/test. However the same injected class then uses a classLoader.getReource("foo.txt") to try to find the file to try to process it. The classLoader looks like it is searching for foo.text under $(PROJECT_DIR)/build/resources/test and since it can't find it there the 3rd party class throws an exception.
So I somehow either need to make classLoader.getResource() called from the 3rd party class search in $(PROJECT_DIR)/app/build/resources/test for foo.txt or make the 3rd party class create foo.txt in $(PROJECT_DIR)/build/resources/test.
I was thinking the way to resolve this might be to add $(PROJECT_DIR)/app/build/resources/test to the classpath in gradle for the test task. But I don't know how to add a new path to the classpath through gradle. I searched all over google but don't see any examples.
Also, If there is a better solution I'm open to that too, but I have limitations in how I can try to resolve this i.e. assume I can't change the 3rd party app for now, and can't refactor project structure of the project I am creating tests in.
Update:
In case it is helpful here is what the test/s looks like:
#ExtendWith({3rdPartyClassResolver.class})
public class 3rdPartyTest {
//the actual failing test
#Test
#3rdPartyClass(
storageLocation = "https://storage-location.us/",
application = "app1",
version = "latest",
requestor = "app2")
public void testing3rdPartyClass(Map<3rdPartyClass, List<Event>> map) {
System.out.println(); //I don't make it to this point
}
//this is what the 3rd party class is trying to do.
//I'm able to reproduce it using this test. it fails the same way.
#Test
public void writeToFile() throws Exception {
Path pathnio = Paths.get("build/resources/test/foo.txt");
Files.write(pathnio, Collections.singleton("some text"));
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL resource = classLoader.getResource("foo.txt");
assertNotNull(resource); //this assert fails
}
}

How to keep a jar file external but still use its classes in my Android project?

I need to have a jar file located in a main/assets directory within an Android project. It is important the jar file is located there.
With my main Android project is there a way to reference this jar file in my code and to use its classes?
To be clear I don't want to add the jar to the main project once compiled.
EDIT: I have tried the link below and it seems to load the Class file I've stated. But I'm strugging how to define constructor arguments for the dynamically loaded Class.
android-custom-class-loading-sample
EDIT2
Nearly there. I've confirmed the class is loaded from my classes.jar. I'm stuck instantiating it though.
On the licenseValidatorClazz.getConstructor line I get the error below. I'm guessing I'm missing something from my Interface file?
java.lang.NoSuchMethodException: [interface com.google.android.vending.licensing.Policy, interface com.google.android.vending.licensing.DeviceLimiter, interface com.google.android.vending.licensing.LicenseCheckerCallback, int, class java.lang.String, class java.lang.String]
public Class licenseValidatorClazz = null;
public LicenseValidator validator;
...
// Initialize the class loader with the secondary dex file.
DexClassLoader cl = new DexClassLoader(dexInternalStoragePath.getAbsolutePath(),
optimizedDexOutputPath.getAbsolutePath(),
null,
mContext.getClassLoader());
try {
// Load the library class from the class loader.
licenseValidatorClazz = cl.loadClass("com.google.android.vending.licensing.LicenseValidator");
validator = (LicenseValidator) licenseValidatorClazz.getConstructor(Policy.class,DeviceLimiter.class,LicenseCheckerCallback.class,int.class,String.class,String.class).newInstance(ddd, new NullDeviceLimiter(),
callback, generateNonce(), mPackageName, mVersionCode);
} catch (Exception exception) {
// Handle exception gracefully here.
exception.printStackTrace();
}
I have an Interface which contains the functions to pass to the loaded class.
public interface LicenseValidator
{
public LicenseCheckerCallback getCallback();
public int getNonce();
public String getPackageName();
public void verify(PublicKey publicKey, int responseCode, String signedData, String signature);
public void handleResponse(int response, ResponseData rawData);
public void handleApplicationError(int code);
public void handleInvalidResponse();
}
TO use an external jar to be associated with your application and use it during runtime, it needs to be in dalvik format since normal jars cannot work under dalvikVM.
Convert your files using the dx tool
using aapt cmd , add those classes.dex to your jar file.
Now this jar which contains files in dalvik format can be loaded into our project.
Here is a post which explains the procedure to accomplish it.
There are steps to accomplish this.
You have to make a copy of your JAR file into the private internal storage of your aplication.
Using the dx tool inside the android folder, you have to generate a classes.dex file associated with the JAR file. The dx tool will be at the location /android-sdks/build-tools/19.0.1 (this file is needed by the Dalvik VM, simply jar can not be read by the dalvik VM))
Using the aapt tool command which is also inside the same location, you have to add the classes.dex to the JAR file.
This JAR file could be loaded dynamically using DexClassLoader.
If you are making a JAR from any one your own library, you have to do this steps (1-4) every time when there is a change in your library source code. So you can automate this steps by creating a shell script(in Mac/Linux/Ubuntu) or batch scripts(in Windows). You can refere this link to understand how to write shell scripts.
Note : One situation for implementing this method is, when it is impossible to add the JAR files directly to the build path of core project and need to be loaded dynamically at run time. In normal cases the JAR files could be added to the build path.
please check this link for the detailed code and implementation.
How to load a jar file at runtime
Android: How to dynamically load classes from a JAR file?
Hope this helps!!
You should try out the Services API - java.util.ServiceLoader
You define a service interface and its implementations in your jar.
package com.my.project;
public interface MyService { ... }
public class MyServiceBarImpl implements MyService { ... }
public class MyServiceFooImpl implements MyService { ... }
Then you define the services contained within the jar file in the META-INF/services/ directory. For instance, in the file 'META-INF/services/com.my.project.MyService', you list the provider classes.
# Known MyService providers.
com.my.project.MyServiceBarImpl # The original implementation for handling "bar"s.
com.my.project.MyServiceFooImpl # A later implementation for "foo"s.
Then, in your main codebase, you can instantiate a MyService instance with the ServiceLoader:
for (MyService service : ServiceLoader.load(MyService.class)) {
//Perform some test to determine which is the right MyServiceImpl
//and then do something with the MyService instance
}
These examples are taken more-or-less straight from the API, although I've changed the package names to make them slightly less annoying to read.

gwt-test-utils does not find my entry point class

I am trying to get gwt-test-utils to work. I set up the project in the following way:
src/main/java : all the java source code
src/test/java : the test source code
src/test/resources : resource files for the tests
I am building my project with gradle and eclipse. Gradle uses these directories correctly by default and I added all three of them as source directories to Eclipse.
I have successfully built and run the project and was able to execute some plain old JUnit tests as well as a GWTTestCase, so I think I set up the project and its dependencies correctly.
Now I wanted to use gwt-test-utils for some more advanced integration tests. To do so I did the following:
Add the gwt-test-utils and gwt-test-utils-csv to my dependencies
gwtTestUtilsVersion = '0.45'
testCompile group:'com.googlecode.gwt-test-utils', name:'gwt-test-utils', version:gwtTestUtilsVersion
testCompile group:'com.googlecode.gwt-test-utils', name:'gwt-test-utils-csv', version:gwtTestUtilsVersion
Add a gwt-test-utils.properties file to the directory src/test/resources/META-INF with the following content:
path/to/my/module = gwt-module
Added a class that extends GwtCsvTest to a package in the src/test/java directory. It is modeled after the second example in HowToWriteCsvScenario from the gwt-test-utils project wiki, replacing occurrence of their example classes with mine. It looks like this
#CsvDirectory(value = "gwtTests")
public class LoginLogoutTest extends GwtCsvTest
{
#Mock
private MainServiceAsync mainService;
private AppController appController = new AppController();
#CsvMethod
public void initApp()
{
appController.onModuleLoad();
}
#Before
public void setup()
{
GwtFinder.registerNodeFinder("myApp", new NodeObjectFinder()
{
#Override
public Object find(Node node)
{
return csvRunner.getNodeValue(appController, node);
}
});
GwtFinder.registerNodeFinder("loginView", new NodeObjectFinder()
{
#Override
public Object find(Node node)
{
return csvRunner.getNodeValue(appController.getRootPresenter().getCurrentlyActiveSubPresenters().iterator().next().getView(), node);
}
});
addGwtCreateHandler(createRemoteServiceCreateHandler());
}
}
added a csv-file for configuring the test to src/test/resources/gwtTests with the following content
start
initApp
assertExist;/loginView/emailTextBox
I tried executing it via the Eclipse's Run As > JUnit Test and indirectly via gradle build (which executes all the test cases, not just this one). Both lead to the same error:
ERROR GwtTreeLogger Unable to find type 'myPackage.client.AppController'
ERROR GwtTreeLogger Hint: Check that the type name 'myPackage.client.AppController' is really what you meant
ERROR GwtTreeLogger Hint: Check that your classpath includes all required source roots
The AppController class is the entry-point configured in the module I configured in gwt-test-utils.properties, which makes me think that configuration works correctly and the rest of the setup (dependencies and all) work as well.
In an earlier version I used the same file as a subclass of GWTTestCase and created an AppController instance in the same way. That worked, so I'm pretty sure the class path is setup correctly to include it as well. I also tried changing it back to the previous version just now and it still works.
I have no clue why the class is not found. Is there anything gwt-test-utils does differently which means I need to specifically set the class path for it? Otherwise it should just work, since both gradle and eclipse know about all the relevant source folders and dependencies.

Dynamic ClassLoader

I have a large desktop Java application and I want to allow other developers to develop plugins for. Plugins will be jars placed in a specified dir. They will not be on the classpath at startup. I will load and deploy them at runtime.
The complication is that some plugins will have dependencies on each other, as well as the core application. So I cannot load each plugin/jar in its own URLClassLoader. Therefore I want to load all plugins into 1 URLClassLoader. Furthermore, some plugins may fail to initialise for various reasons. And I only want a ClassLoader at the end of day that knows about the successfully loaded plugins. The reasons are quite bizarre and relate to some legacy stuff that is using reflection to instantiate classes. This needs to fail if the plugin doesn't initialise for classes defined inside the plugin jar that failed.
Without this requirement, the solution would be:
Collect the jar URLs and build a ClassLoader based on them
Try to initialise a plugin class from each jar (defined in config in the manifest)
Now the ClassLoader here would be passed to the legacy system for it to use for its reflection stuff. However, it's my understanding that it will still be able to instantiate classes from plugin jars whose plugin failed to initialise (since the jar will still in the URL[] of the ClassLoader). Hence this breaks my requirement above.
The only solution I have come up with so far is to create a custom URLClassLoader as follows (simply to allow access to findClass()):
public class CustomURLClassLoader extends URLClassLoader {
public CustomURLClassLoader(final URL[] urls, final ClassLoader parent) {
super(urls, parent);
}
#Override
protected Class<?> findClass(final String name) throws ClassNotFoundException {
return super.findClass(name);
}
}
And then I made another custom ClassLoader that essentially knows about multiple child ClassLoaders:
public class MultiURLClassLoader extends ClassLoader {
private Set<CustomURLClassLoader> loaders = new HashSet<CustomURLClassLoader>();
public MultiURLClassLoader(final ClassLoader parent) {
super(parent);
}
#Override
protected Class<?> findClass(final String name) throws ClassNotFoundException {
Iterator<CustomURLClassLoader> loadersIter = loaders.iterator();
boolean first = true;
while (first || loadersIter.hasNext()) {
try {
if (first) {
return super.findClass(name);
} else {
return loadersIter.next().findClass(name);
}
} catch (ClassNotFoundException e) {
first = false;
}
}
throw new ClassNotFoundException(name);
}
public void addClassLoader(final CustomURLClassLoader classLoader) {
loaders.add(classLoader);
}
public void removeClassLoader(final CustomURLClassLoader classLoader) {
loaders.remove(classLoader);
}
}
Then my loading plugin alogorithm will be something like
MultiURLClassLoader multiURLClassLoader = new MultiURLClassLoader(ClassLoader.getSystemClassLoader());
for (File pluginJar : new File("plugindir").listFiles()) {
CustomURLClassLoader classLoader = null;
try {
URL pluginURL = pluginJar.toURI().toURL();
final URL[] pluginJarUrl = new URL[] { pluginURL };
classLoader = new CustomURLClassLoader(pluginJarUrl, multiURLClassLoader);
multiURLClassLoader.addClassLoader(classLoader);
Class<?> clazz = Class.forName("some.PluginClass", false, multiURLClassLoader);
Constructor<?> ctor = clazz.getConstructor();
SomePluginInterface plugin = (SomePluginInterface)ctor1.newInstance();
plugin.initialise();
} catch (SomePluginInitialiseException e) {
multiURLClassLoader.removeClassLoader(classLoader);
}
}
Then I can pass the multiURLClassLoader instance onto the legacy system and it will only be able to find classes (via reflection) whose plugin successfully loaded.
I've done some basic testing and it seems to work as I'd like so far. But I would very much like someones opinion on whether this seems like a good idea or not? I have never played this much with ClassLoaders before and I am wanting to avoid getting myself in too deep before its too late.
Thanks!
The problem I see is that if you don't know in advance which plugin depends on which, it's very hard to do anything reasonable, to debug problems, to isolate non-functional or bad-behaving plugins, etc.
Therefore I'd suggest another option: Add another field into each plugin's manifest, which will say on what other plugins it depends. Perhaps just a list of other plugin JARs it needs to function. (The core application classes would be always available.) I believe this would make the design much more robust and simplify many things.
Then, you could choose from different designs, for example:
For each plugin you could create a separate ClassLoader that would load just the JARs it needs. Probably the most robust solution. But I see a drawback: plugins that act as dependencies for many other ones will be loaded repeatedly in different class-loaders. It depends on circumstances (plugin count, JARs size, ...) if this could be a problem or not, it could even be an advantage.
You could have one big ClassLoader for all plugins, as you suggest, but you could ask it for plugin classes in the order of their dependencies. The ones that don't depend on anything first, then the ones that depend on those first ones etc. If some plugin class fails to load/initialize, you could immediately discard all plugins that depend on it.
Are you looking for something like the OSGi approach?
You could do something like Petr Pudlák has said, however you should take in account the fact that one of the solutions you have can create cyclic dependencies...

Load classes from folder without specifying the package

I have an application that allows, using an abstract class, people to write their own implementations. I load these implementations as .class-files from a directory. Currently, I have this solution:
File classDir = new File("/users/myproject/classes/");
URL[] url = { classDir.toURI().toURL() };
URLClassLoader urlLoader = new URLClassLoader(url);
String filename;
for (File file : classDir.listFiles()) {
filename = string.getFilenameWithoutExtension(file);
if (filename.equals(".") || filename.equals("..") || filename.startsWith("."))
continue;
AbstractClass instance = (AbstractClass)urlLoader
.loadClass("org.mypackage." + filename)
.getConstructor(ConfigUtil.class, DatabaseUtil.class, StringUtil.class)
.newInstance(config, database, string));
instance.doSomething();
}
As you see - I need to specify the package the classes are located in in order to correctly load them. Omitting the package, I get an
java.lang.NoClassDefFoundError:
MyClass (wrong name: org/mypackage/MyClass)
error.
Now, from a architectural POV, I think it is very ill-designed that classes other people designed have to be compiled to MY package when loading them.
So I ask you: Is there a way I can load classes form the file system without having to specify the package they reside in?
Yes; implement an interface (or use an annotation).
Then use any class-scanning library (there are lots of SO questions about this, like this one) to load the particular class in question. Searching for "Java class scanning" or "Java plugin mechanism" will help.
You might also just want to use the Java Plugin Framework and avoid some effort. Although it's not clear to me that it's maintained any more, I know people are still using it.
You can use the ServiceProvider to load implementations which you don't know.

Categories

Resources