classloader - Can a custom class loader be created for Java to conditionally load a class from JAR or from CLASS_PATH? -


we had discussion @ work pros , cons of static (from built jar) vs dynamic (from separate location in class_path) loading of java libraries.

in middle of discussion, occurred me: regardless of side right, maybe there's way have cake , eat too:

  • have default custom class loader organization
  • class loader - when loading specific library - checks configuration that, every library (or app+library combo more granularity), contains flag determines if library loaded statically jar or dynamically class_path
  • the apps built library classes in jars (default "backup" version of library use if don't want dynamically load new library version)
  • if wish library loaded statically (e.g. because new version incompatible older one, or eliminate change risk if warranted), set configuration flag library true.
  • if instead change risk zero/low, set flag false; , allow library loaded dynamically, allowing release new versions of library picked apps without re-compilation , re-release (obviously, apps need tested new library minefield of bugs approach).

whether idea or bad, what know is, whether second bullet point technically feasible java (say 1.8+), , if so, involved in implementing it?

yes, absolutely. assume java 8 answer , have yet familiarize myself capabilities of java 9's module system.

the main idea straightforward--a classloader loadclass(string, boolean) implementation adheres following "protocol":

  1. if class name argument refers system class, delegate loading parent loader , return. otherwise proceed.
  2. does name, according configuration, refer application class? if proceed step 2.1, otherwise 3.
    1. get application "search path" (the filesystem path of jar file according requirements in question).
    2. attempt find, in implementation-specific manner (by means of e.g. path resolution against search path's entries), class resource below search path matches name argument. if such resource exists, proceed step 5, otherwise 3.
  3. for each known library not yet examined, sorted descending priority:
    1. does name refer class of library? if continue; otherwise return 3.
    2. get search path particular library.
    3. attempt find class resource under search path matches name argument. if successful, proceed step 5, otherwise return step 3.
  4. if no matching class resource established in step 2 or 3, raise exception. otherwise continue.
  5. retrieve content of resource, delegate defineclass , resolveclass appropriate, , return new class.


below (pretty ugly) sample implementation of such classloader.

assumptions:

  • a module application or library.
  • a module comprises known set of classes; in other words, module "is aware of contents".
  • an application uses 0 or more libraries.
  • multiple logical applications may running on same jvm.
  • a configuration communicates classloader:
    • the "current" application requesting class loading.
    • mappings between applications , search paths.
    • mappings between library-application pairs , search paths.
  • multiple loaders may use (the persisted representation of) same configuration.

package com.example.q45313762;  import java.io.bufferedinputstream; import java.io.bytearrayoutputstream; import java.io.ioexception; import java.io.inputstream; import java.net.urisyntaxexception; import java.net.url; import java.net.urlclassloader; import java.nio.file.files; import java.nio.file.paths; import java.security.codesource; import java.security.protectiondomain; import java.security.cert.certificate; import java.util.arrays; import java.util.collections; import java.util.enumeration; import java.util.iterator; import java.util.linkedhashmap; import java.util.linkedhashset; import java.util.map; import java.util.objects; import java.util.set; import java.util.concurrent.locks.lock; import java.util.concurrent.locks.readwritelock; import java.util.concurrent.locks.reentrantreadwritelock; import java.util.function.bifunction; import java.util.function.predicate; import java.util.function.supplier;  public final class configurableclasspathclassloader extends urlclassloader {      public interface configuration {          interface module {              string getname();              string getversion();              boolean includes(string resourcename);          }          interface library extends module {}          interface application extends module {}          enum loadingmode {             static, dynamic;         }          application getcurrentapplication();          iterable<url> getlibrarysearchpath(library lib, loadingmode mode, application app);          iterable<url> getapplicationsearchpath(application app);          iterable<library> getapplicationlibraries(application app);      }      public static final class simplestaticconfiguration implements configuration {          private static abstract class simplemodule implements module {              private final string name, version;             private final predicate<string> resourcenamematcher;              private simplemodule(string name, string version, predicate<string> resourcenamematcher) {                 requirenonenull(name, version, resourcenamematcher);                 name = name.trim();                 version = version.trim();                 if (name.isempty() || version.isempty()) {                     throw new illegalargumentexception("arguments must not empty.");                 }                 this.name = name;                 this.version = version;                 this.resourcenamematcher = resourcenamematcher;             }              @override             public string getname() {                 return name;             }              @override             public string getversion() {                 return version;             }              @override             public boolean includes(string resourcename) {                 if (resourcename == null) {                     return false;                 }                 return resourcenamematcher.test(resourcename);             }              @override             public final int hashcode() {                 final int prime = 31;                 int result = 1;                 result = prime * result + ((name == null) ? 0 : name.hashcode());                 result = prime * result + ((resourcenamematcher == null) ? 0 : resourcenamematcher.hashcode());                 result = prime * result + ((version == null) ? 0 : version.hashcode());                 return result;             }              @override             public final boolean equals(object obj) {                 if (this == obj) {                     return true;                 }                 if (obj == null) {                     return false;                 }                 if (!(obj instanceof simplemodule)) {                     return false;                 }                 simplemodule other = (simplemodule) obj;                 if (name == null) {                     if (other.name != null) {                         return false;                     }                 }                 else if (!name.equals(other.name)) {                     return false;                 }                 if (resourcenamematcher == null) {                     if (other.resourcenamematcher != null) {                         return false;                     }                 }                 else if (!resourcenamematcher.equals(other.resourcenamematcher)) {                     return false;                 }                 if (version == null) {                     if (other.version != null) {                         return false;                     }                 }                 else if (!version.equals(other.version)) {                     return false;                 }                 return true;             }          }          public static final class simplelibrary extends simplemodule implements library {              public simplelibrary(string name, string version, predicate<string> resourcenamematcher) {                 super(name, version, resourcenamematcher);             }          }          public static final class simpleapplication extends simplemodule implements application {              public simpleapplication(string name, string version, predicate<string> resourcenamematcher) {                 super(name, version, resourcenamematcher);             }          }          private static final class moduleregistry {              private static abstract class key {                  private final module module;                  private key(module module) {                     requirenonenull(module);                     requirenonenull(module.getname(), module.getversion());                     this.module = module;                 }                  private module getmodule() {                     return module;                 }              }              private static final class librarykey extends key {                  private final loadingmode mode;                 private final application app;                  private librarykey(library lib, loadingmode mode, application app) {                     super(lib);                     requirenonenull(mode);                     requirenonenull(app);                     this.mode = mode;                     this.app = app;                 }                  private library getlibrary() {                     return (library) super.getmodule();                 }                  private loadingmode getloadingmode() {                     return mode;                 }                  private application getapplication() {                     return app;                 }                  @override                 public int hashcode() {                     final int prime = 31;                     int result = 1;                     library lib = getlibrary();                     result = prime * result + ((lib == null) ? 0 : lib.hashcode());                     result = prime * result + ((mode == null) ? 0 : mode.hashcode());                     result = prime * result + ((app == null) ? 0 : app.hashcode());                     return result;                 }                  @override                 public boolean equals(object obj) {                     if (this == obj) {                         return true;                     }                     if (obj == null) {                         return false;                     }                     if (!(obj instanceof librarykey)) {                         return false;                     }                     librarykey other = (librarykey) obj;                     library thislib = getlibrary(), otherslib = other.getlibrary();                     if (thislib == null) {                         if (otherslib != null) {                             return false;                         }                     }                     else if (!thislib.equals(otherslib)) {                         return false;                     }                     if (mode != other.mode) {                         return false;                     }                     if (app == null) {                         if (other.app != null) {                             return false;                         }                     }                     else if (!app.equals(other.app)) {                         return false;                     }                     return true;                 }              }              private static final class applicationkey extends key {                  private applicationkey(application app) {                     super(app);                 }                  private application getapplication() {                     return (application) super.getmodule();                 }                  @override                 public int hashcode() {                     final int prime = 31;                     int result = 1;                     application app = getapplication();                     result = prime * result + ((app == null) ? 0 : app.hashcode());                     return result;                 }                  @override                 public boolean equals(object obj) {                     if (this == obj) {                         return true;                     }                     if (obj == null) {                         return false;                     }                     if (!(obj instanceof applicationkey)) {                         return false;                     }                     applicationkey other = (applicationkey) obj;                     application thisapp = getapplication(), othersapp = other.getapplication();                     if (thisapp == null) {                         if (othersapp != null) {                             return false;                         }                     }                     else if (!thisapp.equals(othersapp)) {                         return false;                     }                     return true;                 }              }              private static final class value {                  private final set<url> searchpath;                  private value(url... searchpath) {                     requirenonenull((object) searchpath);                     if (searchpath == null || searchpath.length == 0) {                         this.searchpath = empty_search_path;                     }                     else {                         this.searchpath = new linkedhashset<>(arrays.aslist(searchpath));                         iterator<url> itr = this.searchpath.iterator();                         while (itr.hasnext()) {                             url searchpathentry = itr.next();                             string proto = searchpathentry.getprotocol();                             if ("file".equals(proto) || "jar".equals(proto)) {                                 continue;                             }                             itr.remove();                         }                         verify();                     }                 }                  private set<url> getsearchpath() {                     verify();                     return (searchpath == empty_search_path) ? searchpath : collections.unmodifiableset(searchpath);                 }                  private void verify() {                     iterator<url> itr = searchpath.iterator();                     while (itr.hasnext()) {                         try {                             if (!files.exists(paths.get(itr.next().touri()))) {                                 itr.remove();                             }                         }                         catch (illegalargumentexception | urisyntaxexception | securityexception e) {                             itr.remove();                         }                     }                 }                  @override                 public int hashcode() {                     final int prime = 31;                     int result = 1;                     result = prime * result + ((searchpath == null) ? 0 : searchpath.hashcode());                     return result;                 }                  @override                 public boolean equals(object obj) {                     if (this == obj) {                         return true;                     }                     if (obj == null) {                         return false;                     }                     if (!(obj instanceof value)) {                         return false;                     }                     value other = (value) obj;                     if (searchpath == null) {                         if (other.searchpath != null) {                             return false;                         }                     }                     else if (!searchpath.equals(other.searchpath)) {                         return false;                     }                     return true;                 }              }              private final map<key, value> m = new linkedhashmap<>();             private supplier<application> appprovider;              private moduleregistry() {             }              private moduleregistry(moduleregistry mr) {                 m.putall(mr.m);                 appprovider = mr.appprovider;             }              private void putlibraryentry(library lib, loadingmode mode, application app, url... searchpath) {                 m.put(new librarykey(lib, mode, app), new value(searchpath));             }              private void putapplicationentry(application app, url... searchpath) {                 m.put(new applicationkey(app), new value(searchpath));             }              private set<library> getlibraries(application app) {                 set<library> ret = null;                 (key k : m.keyset()) {                     if (!(k instanceof librarykey)) {                         continue;                     }                     librarykey lk = (librarykey) k;                     if (lk.getapplication().equals(app)) {                         if (ret == null) {                             ret = new linkedhashset<>();                         }                         ret.add(lk.getlibrary());                     }                 }                 if (ret == null) {                     ret = no_libs;                 }                 return ret;             }              private set<url> getlibrarysearchpath(library lib, loadingmode mode, application app) {                 set<url> ret = empty_search_path;                 value v = m.get(new librarykey(lib, mode, app));                 if (mode == loadingmode.dynamic && (v == null || v.getsearchpath().isempty())) {                     v = m.get(new librarykey(lib, loadingmode.static, app));                 }                 if (v != null) {                     ret = v.getsearchpath();                 }                 return ret;             }              private set<url> getapplicationsearchpath(application app) {                 set<url> ret = empty_search_path;                 value v = m.get(new applicationkey(app));                 if (v != null) {                     ret = v.getsearchpath();                 }                 return ret;             }              private supplier<application> getapplicationprovider() {                 return appprovider;             }              private void setapplicationprovider(supplier<application> appprovider) {                 requirenonenull(appprovider);                 requirenonenull(appprovider.get());                 this.appprovider = appprovider;             }              private void clear() {                 m.clear();             }          }          public static final class builder {              private final moduleregistry registry = new moduleregistry();              private builder() {             }              public synchronized builder withlibrary(library lib, loadingmode mode, application app, url... searchpath) {                 registry.putlibraryentry(lib, mode, app, searchpath);                 return this;             }              public synchronized builder withapplication(application app, url... searchpath) {                 registry.putapplicationentry(app, searchpath);                 return this;             }              public synchronized builder withapplicationprovider(supplier<application> appprovider) {                 registry.setapplicationprovider(appprovider);                 return this;             }              public synchronized simplestaticconfiguration build() {                 simplestaticconfiguration ret = new simplestaticconfiguration(this);                 registry.clear();                 return ret;             }              public synchronized builder reset() {                 registry.clear();                 return this;             }          }          public static final set<url> empty_search_path = collections.emptyset();         private static final set<library> no_libs = collections.emptyset();          public static builder newbuilder() {             return new builder();         }          private final moduleregistry registry;          private simplestaticconfiguration(builder b) {             registry = new moduleregistry(b.registry);         }          @override         public application getcurrentapplication() {             return registry.getapplicationprovider().get();         }          @override         public iterable<url> getlibrarysearchpath(library lib, loadingmode mode, application app) {             return registry.getlibrarysearchpath(lib, mode, app);         }          @override         public iterable<url> getapplicationsearchpath(application app) {             return registry.getapplicationsearchpath(app);         }          @override         public iterable<library> getapplicationlibraries(application app) {             return registry.getlibraries(app);         }      }      private static final string java_home_prop = system.getproperty("java.home");      private static void requirenonenull(object... args) {         if (args != null) {             (object o : args) {                 objects.requirenonnull(o);             }         }     }      private final lock readlock, writelock;     private configuration cfg;      {         readwritelock rwl = new reentrantreadwritelock(false);         readlock = rwl.readlock();         writelock = rwl.writelock();     }      public configurableclasspathclassloader(configuration cfg, classloader parent) {         super(new url[0], parent);         setconfiguration(cfg);     }      public void setconfiguration(configuration cfg) {         requirenonenull(cfg);         try {             writelock.lock();             this.cfg = cfg;         }         {             writelock.unlock();         }     }      @override     protected class<?> loadclass(string name, boolean resolve) throws classnotfoundexception {         if (name == null) {             throw new classnotfoundexception(name);         }         synchronized (getclassloadinglock(name)) {             class<?> ret;             class<?> self = getclass();             if (self.getname().equals(name)) {                 // no need "reload" our own class                 return self;             }             ret = findloadedclass(name);             if (ret != null) {                 // loaded                 return ret;             }             // unknown             ret = findclass(name);             if (resolve) {                 resolveclass(ret);             }             return ret;         }     }      @override     protected class<?> findclass(string name) throws classnotfoundexception {         // perform search on global classpath (obviously far ideal)         enumeration<url> allmatches;         string modifiedname = name.replace(".", "/").concat(".class");         try {             allmatches = getresources(modifiedname);         }         catch (ioexception ioe) {             throw new classnotfoundexception(name);         }         set<url> filteredmatches = new linkedhashset<>();         while (allmatches.hasmoreelements()) {             url match = allmatches.nextelement();             if (match.getpath().replacefirst("file:", "").startswith(java_home_prop)) {                 // bootstrap classpath class - these off limits                 return getparent().loadclass(name);             }             // candidate match             filteredmatches.add(match);         }         if (!filteredmatches.isempty()) {             try {                 readlock.lock();                 bifunction<configuration.module, iterable<url>, url[]> matcher = (module, searchpath) -> {                     url[] ret = null;                     if (module.includes(name)) {                         outer: (url searchpathentry : searchpath) {                             (url filteredmatch : filteredmatches) {                                 if (filteredmatch != null && filteredmatch.getpath().replacefirst("file:", "")                                         .startswith(searchpathentry.getpath())) {                                     ret = new url[] { filteredmatch, searchpathentry };                                     break outer;                                 }                             }                         }                     }                     return ret;                 };                 configuration.application app = cfg.getcurrentapplication();                 url matchedclassresource = null, matchingsearchpath = null;                 if (app != null) {                     // try application search path match                     url[] tmp = matcher.apply(app, cfg.getapplicationsearchpath(app));                     if (tmp != null) {                         matchedclassresource = tmp[0];                         matchingsearchpath = tmp[1];                     }                     else {                         // try matching against search path of library "known to" app                         (configuration.library lib : cfg.getapplicationlibraries(app)) {                             tmp = matcher.apply(lib,                                     cfg.getlibrarysearchpath(lib, configuration.loadingmode.dynamic, app));                             if (tmp != null) {                                 matchedclassresource = tmp[0];                                 matchingsearchpath = tmp[1];                                 break;                             }                         }                     }                     if (matchedclassresource != null) {                         // matched - load                         byte[] classdata = readclassdata(matchedclassresource);                         return defineclass(name, classdata, 0, classdata.length,                                 constructclassdomain(matchingsearchpath));                     }                 }             }             {                 readlock.unlock();             }         }         throw new classnotfoundexception(name);     }      private byte[] readclassdata(url classresource) {         try (inputstream in = new bufferedinputstream(classresource.openstream());                 bytearrayoutputstream out = new bytearrayoutputstream()) {             while (in.available() > 0) {                 out.write(in.read());             }             return out.tobytearray();          }         catch (ioexception ioe) {             throw new runtimeexception(ioe);         }     }      private protectiondomain constructclassdomain(url codesourcelocation) {         codesource cs = new codesource(codesourcelocation, (certificate[]) null);         return new protectiondomain(cs, getpermissions(cs), this, null);     }  } 

notes:

  • search paths registered loader must subsets (subtrees) of effective classpath ("java.class.path" property). furthermore, "fat jars" not supported.
  • i not include usage example due post length constraints. provide 1 via gist if requested.

Comments

Popular posts from this blog

node.js - Node js - Trying to send POST request, but it is not loading javascript content -

javascript - Replicate keyboard event with html button -

javascript - Web audio api 5.1 surround example not working in firefox -