Using the code:
ModuleFinder.of(Paths.get(path)).findAll()
I am able to retrieve a Set<ModuleReference> of all the .jars in the path folder.
My next step would be getting a Module from a ModuleReference but there's no method that returns that, I can get a ModuleDescriptor but even that one doesn't help. Is there a way to do this?
If you desire to access the module content you should open the ModuleReference that you've attained.
This would provide you access to the ModuleReader which
is intended for cases where access to the resources in a module is
required
A resource in a module is identified by an abstract name that is a
'/'-separated path string. For example, module java.base
may have a resource "java/lang/Object.class" that, by convention,
is the class file for java.lang.Object. A module reader may treat
directories in the module content as resources (whether it does or not is
module reader specific). Where the module content contains a directory
that can be located as a resource then its name ends with a slash ('/'). The directory can also be located with a name that drops the trailing slash.
Do keep in mind though, that the docs also specify :
A ModuleReader is open upon creation and is closed by invoking the
close
method. Failure to close a module reader may result in a resource
leak. The try-with-resources statement provides a useful construct to
ensure that module readers are closed.
One way to get the Module from the resources would be to access it using the Class#getModule as:
Module module = com.foo.bar.YourClass.class.getModule();
Edit: I've learned with time a better way to use the ModuleFinder to access a Module as suggested by #Alan as well could possibly be :
ModuleFinder finder = ModuleFinder.of(path);
ModuleLayer parent = ModuleLayer.boot();
Configuration configuration = parent.configuration().resolve(finder, ModuleFinder.of(), Set.of("curious")); // 'curious' being the name of the module
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
ModuleLayer layer = parent.defineModulesWithOneLoader(configuration, systemClassLoader);
Module m = layer.findModule("curious").orElse(null);
The classes in java.lang.module are more model world. Look at j.l.module.Configuration and also j.l.ModuleLayer to see how to create a configuration and instantiate it in the Java virtual machine as a layer of modules. There is a code fragment in the ModuleLayer javadoc that might get you going, just bewarned that this is an advanced topic and best to master the basics first.
Related
I have a very simple POC setup where I deploy a JEE7 webapp on a wildfly 9.
Via a jaxRs Resource endpoint I can trigger a "plugin loader".
The PluginLoader does use a directory and scans for jar files in the directory, which URLs then will be fed into a URLClassLoader.
Afterwards I use the ServiceLoader to load implmementations of a simple interface from those URLs.
When the ServiceLoader starts iterating over the found implementations, I get this error:
Caused by: java.util.ServiceConfigurationError: com.test.MyIface: Provider com.test.MyImpl not a subtype
The structure is also very simple:
MyIface.jar is the interface.
MyImpl.jar is a implementation of MyIface, while it contains a META-INF/services file with the correct naming and content for MyIface..
The webapp itself only knows MyIFace of course.
In JavaSE using a simple main entry point and invoking the loader from there, everything works.
In JavaEE the services file seems to be ignored though..at least that is what I get from the exception.
I put it in src/main/resources/META-INF/services
and in src/main/resource/WEB-INF/classes/META-INF/services (as I read that already in context with SPI and webapps)
In order for this to work, the following 2 steps must be followed:
Instantiate a ClassLoader (the stock URLClassLoader will do) that knows both the targeted jar AND the classloader of the web application.
It needs to know the targeted jar obviously to load the service implementation
It needs to have the classloader of the web app as parent so that all the classloaders share the interface class; otherwise, even if the custom classloader loads the interface, you will run to ClassCastExceptions like "MyIface is not an instance of MyIface"
Specify the classloader you created using the ServiceLoader.load(Class, ClassLoader) method
We have a very large project under Java 8. I temporarily need to use a feature1 introduced in Java 9 in order to debug a problem, so my plan was to build and run with mostly-current tools (probably 11.0.1), add minimal code to call the feature, and then simply not merge any of the debugging changes to the trunk.
The challenge is that I don't want to have to slog through constructing a module-info.java merely for this. (The existing non-module code actually builds and runs with no problems under Java SE 11, it's just that the production user environment is constrained to Java 8. Thus, we've made almost no effort to try and modularize this project.)
Why modules might matter: the feature I need to use is System.LoggerFinder, which is found via ServiceLoader. The hip new way of using ServiceLoader is via module declarations, and that's what the current service loader documentation spends most time describing. The old way of putting text files under META-INF/services/ still works, and we're already successfully using such services and provider classes, but the current setup isn't working with LoggerFinder.
The temporary class we're trying to load
package com.example.for.stackoverflow;
public class LoggerFinder extends System.LoggerFinder
{
public LoggerFinder()
{
System.err.println("behold, a LoggerFinder");
}
#Override
public System.Logger getLogger (String name, Module module)
{
org.slf4j.Logger real = ...existing function to fetch logging facade...
return new SystemLoggerWrapper (name, real);
}
}
class SystemLoggerWrapper implements System.Logger { ... }
This kind of wrapper implementation is pretty straightforward, and has been published elsewhere.2 However, the published examples all use
module-info.java to get ServiceLoader to DTRT.
This project's build system is done with Ant, so we've added lines under the <jar> task:
<service type="java.lang.System.LoggerFinder">
<provider classname="com.example.for.stackoverflow.LoggerFinder"/>
</service>
This works, in that the final JAR contains a META-INF/services/java.lang.System.LoggerFinder text file containing the proper classname.
However, at runtime, the provided LoggerFinder isn't used. Looking through the output of -verbose:class it's clear that the defaults are all still in play. There are no exceptions thrown from ServiceLoader itself. The runtime classpath is already picking up the JAR where the LoggerFinder implementation lives (it's not the only service, and the other ServiceLoader-esque providers are being found).
A very few existing questions on SO touch on the topic,
here
and
here,
but they also take the modular route.
Is there some way of getting visibility into what ServiceLoader is doing?
Is this just an exercise in futility without a modules-info.java?
1 I'm trying to activate debug messages from the sun.util.logging.PlatformLogger system, which (at least as of Java 9) will work through System.Logger, and become activated if -- according to its own documentation -- a System.LoggerFinder can be found by ServiceLoader. All of this juggling is aimed towards that PlatformLogger goal.
2 for example, https://www.baeldung.com/java-9-logging-api under section three
The way ServiceLoader loads classes on the classpath hasn't changed1. It still locates providers by searching for appropriate provider-configuration files under META-INF/services.
The documentation of LoggerFinder says it searches for providers visible to the system class loader. As you mention in a comment the provider is included via -cp this should not be an issue.
The provider-configuration file's name must be the fully qualified binary name of the SPI. From the ServiceLoader documentation:
Deploying service providers on the class path
A service provider that is packaged as a JAR file for the class path is identified by placing a provider-configuration file in the resource directory META-INF/services. The name of the provider-configuration file is the fully qualified binary name of the service. The provider-configuration file contains a list of fully qualified binary names of service providers, one per line.
The binary name of LoggerFinder, as returned by Class.getName, is java.lang.System$LoggerFinder. Based on that, the provider-configuration file's name should be:
META-INF/services/java.lang.System$LoggerFinder
I'm not at all familiar with Ant, but I'd guess you should change the value of type to use the fully qualified binary name.
<service type="java.lang.System$LoggerFinder">
<provider classname="com.example.for.stackoverflow.LoggerFinder"/>
</service>
Note: When testing this on my own, I initially couldn't get the system to use my LoggerFinder implementation either. When I changed the provider-configuration file name to java.lang.System$LoggerFinder (once finally reading the docs) then it worked as expected. Unfortunately, I don't have Ant available to test a solution with the <jar> task.
1. Unless you use ServiceLoader.load(ModuleLayer,Class) as it will ignore the unnamed modules (i.e. classpath).
I am trying to create Java plugins for an existing application. The plugins would like to re-use a lot of the already existing code-base in the main application (e.g. logging, error handling, etc).
I am trying to load plugins as .jar files like this:
String localPath = "...";
String pluginName = "...";
File jarFile = new File(localPath);
ClassLoader pluginLoader = URLClassLoader.newInstance(new URL[]{jarFile.toURL()});
pluginLoader.loadClass(pluginName).newInstance();
The problem I am having is that the classes I would like to import inside the plugin can not be found, even though they exist in the main app, I am getting errors like this:
NoClassDefFoundError: com/foo/exception/FooException
at com.foo.plugins.PluginManager.loadPlugin(PluginManager.java:187)
at com.foo.plugins.PluginManager.loadPlugins(PluginManager.java:86)
...
com/foo/exception/FooException is used everywhere in the code, but I didn't want to have to include this class (and many many others) in the plugin jar file. Instead I would like the ClassLoader to somehow be aware of the locally existing classes. Is this possible? If so, how can I do it?
You need to use your main application's classloader as a parent:
ClassLoader mainLoader = ThisClass.class.getClassLoader(); // some class in the main application
ClassLoader pluginLoader = URLClassLoader.newInstance(new URL[]{jarFile.toURL()}, mainLoader);
Then classes loaded by the plugin classloader will have access to classes loaded by the main classloader (as well as that classloader's parent, if it has one, and so on).
I'm trying to find out if we can load a oracle commerce component from file system. Generally we assemble all the code into an ear file and deploy it, however, I got a requirement where in I have to store some components in file system rather than packaging them along with ear file.
I know that we can use URLClassloader to load a class as shown below,
File classDir = new File("A:\\LodeeModule\\classes");
URL[] url = { classDir.toURI().toURL() };
ClassLoader loader = new URLClassLoader(url);
for (File file : classDir.listFiles()) {
String filename = file.getName().replace(".class", "");
loader.loadClass("com.buddha.testers." + filename).getConstructor().newInstance();
}
but how can we use the same for an component which has to be resolved by Nucleus at later point of time? Is there any way to instruct Nucleus to resolve component from file system?
You should just be able to add the JAR that contains the components classes to the CLASSPATH system variable used by the application server instance.
Then in the component configuration just define the implementing class as you normally would
$class=some.class.path.class
If you are using Jboss EAP 6+ on a newer version of ATG (11.0+) you might have some more trouble, you have to jump through some more hoops due to its classloader
https://docs.jboss.org/author/display/AS7/Class+Loading+in+AS7
Essentially you would need to define a jboss module containing your jar files, and define a dependency between the ear's "module" and the module containing your classes.
Alternatively you can define a ClassLoaderService that will manage the classes for your JARs
To do this, you need to define a new ClassLoaderService, so create a new properties file as you would with any other component.
/my/custom/ClassLoaderService.properties
$class=atg.nucleus.ServicesManifestClassLoaderService
$description=Custom Class Loader Service.
# The files to go into the classpath of the classloader
classpathFiles=\
/path/to/my/jars/lib/someClasses.jar,\
/path/to/my/jars/lib/someOtherClasses.jar
loggingDebug=false
Then in the actual component that you need these classes for add this line;
$classloader=/my/custom/ClassLoaderService
I think you're looking for the atg.dynamo.data-dir property. If you specify that property dynamo will look at that location for the "server configs" or properties files. This allows you to separate the configs from the ear file.
Note: You can still include configs in the ear, I believe they will still have first precedence
It's usually specified when you start the server, something like:
run.sh -c <your server> -Datg.dynamo.data-dir=/data/something/serverconfigs
This feature is largely undocumented, but many people know about it.
See http://docs.oracle.com/cd/E24152_01/Platform.10-1/ATGPlatformProgGuide/html/s0302developmentmodeandstandalonemode01.html
EDIT:
I mistook what you were originally asking. You might want to take a look at the disposable class loader that ATG provides, but keep in mind this is only intended for development purposes.
Please forgive my pitiful knowledge of Java EJBs but, when an EJB is deployed to an application server as a .jar file, where do things like Hibernate and log4j first look for their configuration files (hibernate.cfg.xml and log4j.properties) in the .jar file?
(...) when an EJB is deployed to an application server as a .jar file, where do things like Hibernate and log4j first look for their configuration files (hibernate.cfg.xml and log4j.properties) in the .jar file?
This depends on the implementation of the tool and is unrelated to the fact that you are using EJBs. For Hibernate, the documentation writes:
3.7. XML configuration file
An alternative approach to
configuration is to specify a full
configuration in a file named
hibernate.cfg.xml. This file can be
used as a replacement for the
hibernate.properties file or, if both
are present, to override properties.
The XML configuration file is by
default expected to be in the root of
your CLASSPATH.
Regarding Log4J, the procedure is described below:
Default Initialization Procedure
The log4j library does not make any
assumptions about its environment. In
particular, there are no default log4j
appenders. Under certain well-defined
circumstances however, the static
inializer of the Logger class will
attempt to automatically configure
log4j. The Java language guarantees
that the static initializer of a class
is called once and only once during
the loading of a class into memory. It
is important to remember that
different classloaders may load
distinct copies of the same class.
These copies of the same class are
considered as totally unrelated by the
JVM.
The default initialization is very
useful in environments where the exact
entry point to the application depends
on the runtime environment. For
example, the same application can be
used as a stand-alone application, as
an applet, or as a servlet under the
control of a web-server.
The exact default initialization
algorithm is defined as follows:
Setting the log4j.defaultInitOverride system property to any other value then
"false" will cause log4j to skip the
default initialization procedure (this
procedure).
Set the resource string variable to the value of the
log4j.configuration system property. The preferred way to
specify the default initialization
file is through the
log4j.configuration system property. In case the system property
log4j.configuration is not defined, then set the string variable
resource to its default value
"log4j.properties".
Attempt to convert the resource variable to a URL.
If the resource variable cannot be converted to a URL, for example due to
a MalformedURLException, then search
for the resource from the classpath by
calling
org.apache.log4j.helpers.Loader.getResource(resource,
Logger.class) which returns a URL.
Note that the string
"log4j.properties" constitutes a
malformed URL. See
Loader.getResource(java.lang.String)
for the list of searched locations.
If no URL could not be found, abort default initialization. Otherwise,
configure log4j from the URL. The
PropertyConfigurator will be used to
parse the URL to configure log4j
unless the URL ends with the ".xml"
extension, in which case the
DOMConfigurator will be used. You
can optionaly specify a custom
configurator. The value of the
log4j.configuratorClass system property is taken as the fully
qualified class name of your custom
configurator. The custom configurator
you specify must implement the
Configurator interface.
To summarize, if you put both files at the root of your EJB-JAR, they should be found.
Regarding the title of your question, I suggest to read Packaging EJB 3 Applications that I'm quoting below:
Dependencies between Java EE modules
Unfortunately, no Java EE
specification provides a standard for
class loading, and each application
server implements class loaders in
whatever way seems best to the vendor.
However, Java EE defines the
visibility and sharing of classes
between different modules, and we can
depict the dependency between
different modules as shown in figure
4.
As illustrated in figure 4, the EAR
class loader loads all JARs in the lib
directory that is shared between
multiple modules. Typically a single
EJB class loader loads all EJB
packaged in all EJB-JAR modules. The
EJB class loader is often the child of
the application class loader, and
loads all EJB classes. Because the EJB
is a child to the EAR class loader,
all classes loaded at the> EAR level
will be visible to the EJBs.
(source: developer.com)
Figure 4: Illustration of class
visibility of an EAR file containing
multiple web modules, EJBs, and shared
library modules. The EAR class loader
loads the classes in the JARs packaged
as library modules, and all classes
loaded by the EAR class loader are
visible to the EJBs. The classes
loaded by EJB class loader are
typically visible to the web module in
most containers because the WAR class
loader is a child of the EJB class
loader.
I think Log4j would look in more than one place for log4j.properties file. Anyway, all configuration files in an ejb-jar go inside the META-INF directory.