Following best practices, I'm using Thread.currentThread().getContextClassLoader().getResourceAsStream to load resources in a web application (like text files or xml files), instead of going through the file API.
However, this has the disadvantage that if the resource changes on disk, a following call to getResourceAsStream keeps returning the old version indefinitely.
I would like it to pick up the new version though. In my debugger I see there's a simple HashMap called resourceEntries in the classLoader. Using reflection I've been able to remove a specific entry and this seems to work.
This method is however fragile.
Is there a more standard way to do this?
Try this:
ClassLoader ctxLoader = Thread.currentThread().getContextClassLoader();
URL resURL = ctxLoader.getResource(resName);
URLConnection resConn = resURL.openConnection();
resConn.setUseCaches(false);
InputStream resIn = resConn.getInputStream();
...
In addition to kschneid's answer which might work for Tomcat indeed, I wanted to add that for JBoss AS 5+ it already seems to work without needing any special tricks.
Caching of resources is probably class loader specific. The JBoss AS one either doesn't cache or is smart enough to see that the resource on disk has changed.
i finally solved this problem by change the jar file name, every time i change the resource content, i change a new name with current timestamp
Related
I have a plugin system realized in Tomcat 7 which loads dynamically classes of these plugins (reloadable via a custom classloader). Unfortunately, if a plugin has a JSP which uses a class which belongs to this plugin, it cannot find this class. This seems to be the correct behavior as we have the Tomcat classloader hierarchy and my custom classloader is at the bottom.
Nevertheless, with this hacky thing I can make the classes available to the JSPs:
URLClassLoader webappClassloader = (URLClassLoader)Thread.currentThread().getContextClassLoader();
Method addURLMethod = webappClassloader.getClass().getDeclaredMethod("addURL", URL.class);
addURLMethod.setAccessible(true);
for(String url : pluginFolders)
addURLMethod.invoke(webappClassloader, new URL("file://" + url));
Please note, that the plugins are not in the WEB-INF/classes directory.
This is all great but now a class can only be loaded once (see also this). In order to make the plugins updatable (without restarting the whole webapp) I am using a custom classloader. But I wasn't able to set this classloader to be used by Jasper (I tried Thread.currentThread().setContextClassLoader() which had no effect). I digged into the source of Jasper and it seems like it should be possible to set a custom classloader. But I have no idea how to achieve this from a servlet.
Maybe you can give me an answer to one of the following questions?
Is it possible to get e.g. JspCompilationContext within a servlet?
I've read something about a custom JSPServlet. How would this be done?
Do you have a better idea of how to set the classloader?
Thanks in advance!
You need to set your own ClassLoader using the for the <Context> in META-INF/context.xml. This will allow you to specify the ClassLoader that will be used for your web application.
Earlier I put my properties file within my classpath src/. Now I would like to put it within a folder called config/. This way the end users can actually modify the file by themselves.
However now my code below does not work anymore
ResourceBundle.getBundle("Messages", Locale.getDefault());
What I meant by the code doesn't work anymore is, after I deploy the application and I modify the Messages_en_US.properties the changes do not take place.
How can I achieve what I want ? Should I use getBundle at all ?
EDIT
I have added config folder into the classpath as well, but I am not sure if this is relevant. This is a plain Java application where I am not using Maven or any building tools.
By default, a ResourceBundle is only loaded when it is first requested, and reused for subsequent requests. You can throw away the cached ResourceBundles with ResourceBundle.clearCache();
Additionally, by default, ResourceBundles are loaded from the classpath. You must therefore ensure that the classloader in question does not cache the resource either. Or you can provide your own ResourceBundle.Control to load the properties file by whatever means you prefer.
Suppose that I want to prevent trivial disassembly of jar/class files.
A JVM is started from a C++ application that can descramble the jar/class files that are stored within its own executable. Is there a way of somehow streaming the contents of such files to a JVM without saving them on disk?
I'm looking for a solution on both windows and unix platforms.
You can create a ClassLoader which gets its class data from anywhere. You could even have it call native methods to obtain byte code for a class. Have a look at URLClassLoader which is widely used, it can obtain it's classes from files on disk or the network or any supported URL.
Think part what you're after is supplied by the JarInputStream class, Docs
You'd need some custom class-loading behavior as well. May need to create a Classloader implementation that loads your classes as well if you go that route. It might be simpler to use the URLClassloader as well depending on your circumstances.
I have one jar file in my application's class path. At run time, I add new classes to the jar file and sometimes also modify the fields/methods of the existing classes. Currently I am using URLClassLoader to load the classes dynamically. The new classes added dynamically are loaded correctly and I am able to use them at runtime. But it fails to reload the existing classes that are modified at runtime. I read many articles which states we need to explicitly handle reloading because class once loaded will not be reloaded until all the references to the class are destroyed. Also I tried out sample code that I found but none of them worked.
Can anyone suggest me a proper approach for reloading ? Any sample code for the same will be highly appreciated.
Normally to reload a class you need to unload the entire class loader. i.e. remove all references to all classes loaded for that class loader.
Another option is to use instrumentation to change the byte code of an existing class. This usually comes with limitations and changing fields is something you cannot do. i.e. the objects of that type would have to be translated somehow.
What I normally do is have services which are very quick to start/restart. This way to you easily restart a process which needs updated code ideally by pressing the Run in my IDE. This minimises deployment time as well.
In principle, a class that has already been loaded cannot be reloaded with the same classloader.
For a new load, it is necessary to create a new classloader and thus load the class.
Using URLClassLoader has one problem and that is that the jar file remains open.
If you have multiple classes loaded from one jar file by different instances of URLClassLoader and you change the jar file at runtime, you will usually get this error: java.util.zip.ZipException: ZipFile invalid LOC header (bad signature). The error may be different.
In order for the above errors not to occur, it is necessary to use the close method on all URLClassLoaders using the given jar file. But this is a solution that actually leads to a restart of the entire application.
A better solution is to modify the URLClassLoader so that the contents of the jar file are loaded into the RAM cache. This no longer affects other URLClassloaders that read data from the same jar file. The jar file can then be freely changed while the application is running. For example, you can use this modification of URLClassLoader for this purpose: in-memory URLClassLoader
The intended machine doesn't have a connection to the internet and I do not want to load it using a fixed location.
Aim: To load DTDs from a jar, the jar will be a dependency.
To load any file from the classpath (it is, the space where youre classes reside, usually a bunch of jars) you can do:
InputStream is = this.getClass().getResourceAsStream("my/package/ResourceFile.dtd");
And then you can use the input stream where you want.
Note: getResourceAsStream() loads the resource using the class loader that loaded the class. If you are making an application any class from your application (and hence loaded by the same classloader with your jars) will be fine.
There is no standard way to provide a local cache (CATALOG if I recall correctly) of DTD's.
Hence, you will need to investigate the parser that will use the local copies, and use its non-standard configuration API to let it know about these local copies so that the trip to the net is avoided.
Use the getResourceAsStream() method to pick out entries from the classpath.