Ao estudar a possibilidade de utilização do WebLogic Server como um Grails EmbeddableServer, substituindo Tomcat ou Jetty na execução de ‘grails run-app’, deparei-me com algumas limitações. Certas características do ambiente de execução Java não estão preparadas para serem alteradas após o início da JVM (ou estão?). Apesar de todo alarde, é apenas software e usá-lo de forma pouco convencional não desencadeará o fim do mundo.
As soluções (truques ou workarounds, se preferirem) a seguir necessitam de um SecurityManager ou de políticas de segurança permissivas o suficiente. Podem ser implementadas em Groovy ou Java “puro”. Basicamente faz-se uso de AcessibleObject.setAcessible e de como algumas classes das bibliotecas base de Java funcionam internamente, em particular as implementações disponíveis com o JDK ou JRE da Oracle/Sun e com o OpenJDK.
1. Adicionar, dinamicamente, paths ao CLASSPATH (java.class.path)
change_classpath.groovy
// groovy change_classpath.groovy def changeClasspath = {paths -> def systemClassLoader = ClassLoader.systemClassLoader assert systemClassLoader.metaClass.respondsTo(systemClassLoader, 'addURL', java.net.URL) def pathSeparator = File.pathSeparator def classpathExtension = new StringBuilder() for (path in paths) { def lib = new File(path); assert lib.exists() systemClassLoader.addURL(lib.toURI().toURL()) classpathExtension.append(pathSeparator); classpathExtension.append(lib.canonicalPath) } // Required under certain conditions System.setProperty('java.class.path', System.getProperty('java.class.path', '') + classpathExtension.toString()) } def classes = ['Lib1', 'Lib2', 'Lib3', 'Lib4'] for (className in classes) { try {Class.forName(className); assert false} catch(ClassNotFoundException e) {assert true} } def extraPaths = ['lib1.jar', 'lib2classes'] extraPaths.addAll((new File('extraLibs').list() as List).collect {'extraLibs/' + it}) changeClasspath(extraPaths) for (className in classes) { def lib = Class.forName(className) lib.doSomething() println "${className}.classLoader ${lib.classLoader}" }
ChangeClasspath.java
// java -ea ChangeClasspath import java.lang.reflect.Method; import java.io.File; import java.util.ArrayList; import java.util.List; public class ChangeClasspath { public static void main(String [] args) throws java.net.MalformedURLException, NoSuchMethodException, IllegalAccessException, java.lang.reflect.InvocationTargetException, ClassNotFoundException { String [] classes = {"Lib1", "Lib2", "Lib3", "Lib4"}; for (String className : classes) { try {Class.forName(className); assert false;} catch(ClassNotFoundException e) {assert true;} } List <String> extraPaths = new ArrayList(); extraPaths.add("lib1.jar"); extraPaths.add("lib2classes"); for (String path : new File("extraLibs").list()) { extraPaths.add("extraLibs/" + path); } changeClasspath(extraPaths); for (String className : classes) { Class lib = Class.forName(className); Method doSomethingMethod = lib.getDeclaredMethod("doSomething"); doSomethingMethod.invoke(null); System.out.println(className + ".classLoader " + lib.getClassLoader()); } } private static void changeClasspath(List <String> paths) throws NoSuchMethodException, IllegalAccessException, java.net.MalformedURLException, java.lang.reflect.InvocationTargetException{ ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); assert systemClassLoader instanceof java.net.URLClassLoader; String pathSeparator = File.pathSeparator; StringBuilder classpathExtension = new StringBuilder(); Method addURLMethod = java.net.URLClassLoader.class.getDeclaredMethod("addURL", java.net.URL.class); addURLMethod.setAccessible(true); for (String path : paths) { File lib = new File(path); assert lib.exists(); addURLMethod.invoke(systemClassLoader, lib.toURI().toURL()); } // Required under certain conditions System.setProperty("java.class.path", System.getProperty("java.class.path", "") + classpathExtension.toString()); } }