9 Nisan 2018 Pazartesi

URLClassLoader Sınıfı

Giriş
Şu satırı dahil ederiz.
import java.net.URLClassLoader;
Bu sınıf ClassLoader arayüzünü gerçekleştirir.

Kullanım
URLClassLoader sınıfını kapatmazsak yüklenen jar dosyasını Windows'ta bir daha silemeyiz.

Örnek
Kapatmak için şöyle yaparız.
String jarPath = ...;
URL jarUrl = new File(jarPath).toURI().toURL();
try (URLClassLoader classLoader = AccessController.doPrivileged(
  (PrivilegedAction<URLClassLoader>) () ->
    new URLClassLoader(new URL[]{jarUrl}, MyMain.class.getClassLoader())
)) {

 Class<?> clazz = loadMainClass(classLoader, mainClass);

 Method main = getMainMethod(clazz, calledByMember);
 String[] args = new String[] {""}
 main.invoke(null, args);
}
constructor - URL []
Burada Class Loader Delegation kullanılıyor. 
Örnek
Şöyle yaparız. Kullanılan URL file://... ile başlar.
URLClassLoader cl = new URLClassLoader(new URL[]{new URL("file:///C:/foo.jar")});
Örnek
Elimizde C:/Download/Foo.class dosyası olsun. Şöyle yaparız.
URLClassLoader loader = new URLClassLoader(new URL[] {
            new URL("file://C:/Downloads")
});
Class c = loader.loadClass("Foo");
Örnek

URL'ye elle file:// + ... şeklinde string birleştirme işlemi yapmamak için File sınıfını kullanarak şöyle yaparız.
File path = new File("foo.jar");
URL[] urls = new URL[] { path.toURI().toURL() };
URLClassLoader cl = new URLClassLoader(urls);
constructor - URL [] + ClassLoader
Eğer Class Loader Delegation kullanmak istemezsek şöyle yaparız
// Bu ikisi farklı
URLClassLoader cl = new URLClassLoader(classpath, ClassLoader.getSystemClassLoader());

URLClassLoader cl = new URLClassLoader(classpath, null);
Örnek
Şöyle yaparız.
ClassLoader cl = new URLClassLoader(new URL[]{new File("Example.jar").toURL()},
  Main.class.getClassLoader());
findClass metodu 
Açıklaması şöyle
This method will actually find the specified class simply by scanning inside jar files. This method is the most expensive one because it involves I/O activities to find a class from files and as well as call to a method called defineClass which converts some byte-codes into a representation of actual java Class<?> instance. One needs to prevent calling this unnecessarily considering performance, as to why we must call findLoadedClass and check before invoking this method.
findLoadedClass metodu 
Açıklaması şöyle
This method actually check whether a class has already been loaded previously, if so, it returns the loaded class and, if not, it returns null. We need to check this to prevent loading the same class again and again, very expensively, every time it requires in a program sequence. Just think that this method acts like a cache.
getURLS metodu
Şöyle yaparız.
URL[] ressources = classLoader).getURLs();
loadClass metodu - fullname

Örnek
Şöyle yaparız.
Class c = cl.loadClass("com.mycompany.Foo");
Örnek
Açıklaması şöyle
In child-first class loading strategy, it searches in its own classloader context before delegating it to the parent classloader.
Açıklaması şöyle
This method is the actual orchestrator of how should the class load into the program. It defines the order of strategy. That’s why we need to override this to achieve what we want
Şöyle yaparız
public class ChildFirstClassLoader extends URLClassLoader {

  private final ClassLoader sysClzLoader;

  public ChildFirstClassLoader(URL[] urls, ClassLoader parent) {
    super(urls, parent);
    sysClzLoader = getSystemClassLoader();
  }

  @Override
  protected Class<?> loadClass(String name, boolean resolve)
  throws ClassNotFoundException {
    // has the class loaded already?
    Class<?> loadedClass = findLoadedClass(name);
    if (loadedClass == null) {
      try {
        if (sysClzLoader != null) {
          loadedClass = sysClzLoader.loadClass(name);
        }
      } catch (ClassNotFoundException ex) {
        // class not found in system class loader... silently skipping
      }

      try {
        // find the class from given jar urls as in first constructor parameter.
        if (loadedClass == null) {
          loadedClass = findClass(name);
        }
      } catch (ClassNotFoundException e) {
        // class is not found in the given urls.
        // Let's try it in parent classloader.
        // If class is still not found, then this method will throw class not found ex.
        loadedClass = super.loadClass(name, resolve);
      }
    }
    if (resolve) {      // marked to resolve
      resolveClass(loadedClass);
    }
    return loadedClass;
  }
}
Sonra resource yükleme içini child first hale getirmek lazım. Açıklaması şöyle
Resources are important as classes as well. You can’t have an implementation where a class loading strategy is different from its resource loading strategy. If those two strategies are different, you gonna end up in a debugging hell, finding what went wrong! (I am talking about config files, metafiles, and other classpath embedded resources). Believe me, I was there once.

Some classes are associated with resources. Considering the above classloader, the resources are loaded as parent-first strategy, then what you going to end up is classes with mismatched resources, thus failing the whole program. So, we need to override resource loading methods too as shown below.
Resource yükleme işini şöyle yaparız
@Override
public Enumeration<URL> getResources(String name) throws IOException {
  List<URL> allRes = new LinkedList<>();

  // load resources from sys class loader
  Enumeration<URL> sysResources = sysClzLoader.getResources(name);
  if (sysResources != null) {
    while (sysResources.hasMoreElements()) {
      allRes.add(sysResources.nextElement());
    }
  }
  // load resource from this classloader
  Enumeration<URL> thisRes = findResources(name);
  if (thisRes != null) {
    while (thisRes.hasMoreElements()) {
      allRes.add(thisRes.nextElement());
    }
  }
  // then try finding resources from parent classloaders
  Enumeration<URL> parentRes = super.findResources(name);
  if (parentRes != null) {
    while (parentRes.hasMoreElements()) {
      allRes.add(parentRes.nextElement());
    }
  }
  return new Enumeration<URL>() {
    Iterator<URL> it = allRes.iterator();

    @Override
    public boolean hasMoreElements() {
      return it.hasNext();
    }

    @Override
    public URL nextElement() {
      return it.next();
    }
  };
}

@Override
public URL getResource(String name) {
  URL res = null;
  if (sysClzLoader != null) {
    res = sysClzLoader.getResource(name);
  }
  if (res == null) {
    res = findResource(name);
  }
  if (res == null) {
    res = super.getResource(name);
  }
  return res;
}
Neden getSystemClassLoader kullanılıyor. Açıklaması şöyle. Eğer SystemClassLoader kullanılmazsa o zaman herkes istediği gibi JDK sınıflarını ezebilir. Güvenlik açığı oluşur.
Just imagine this. In a child-first class loading environment, child gets the priority. Sometimes, even a user could impersonate some internal classes in extension or system classloader and therefore it can have very undesirable effects in terms of security of your application. Simply, by loading your own set of classes, you may be able to simulate a non-secure environment while your program is executing and completely crash your application’s JVM. Although, normal users won’t deliberately try those things, you know, some accidents could happen. We needed to prevent this.

So, considering security, this is not a perfect solution for our problem.

Thread ile kullanmak için şöyle yaparız
public void beginExecution(Runnable runnable) {
  Thread t = new Thread(runnable);
  URLClassLoader runtimeCl = new URLClassLoader(..., null);
  t.setContextClassLoader(new ChildFirstClassLoader(..., runtimeCl));
  t.start();
}
ExecutorService ile kullanmak için şöyle yaparız. Runnable çalışınca ilk ClassLoader'ı Thread'e geri veriyor. Böylece ExecutorService bir sürü şey için kullanılabiliyor.
public class Worker implements Runnable {

  private ClassLoader runtimeClassloader;

  @Override
  public void run() {
    ClassLoader ctxCL = Thread.currentThread().getContextClassLoader();
    Thread.currentThread().setContextClassLoader(
      new ChildFirstClassLoader(..., runtimeClassloader));
        try {
            // execute script
        } finally {
            Thread.currentThread().setContextClassLoader(ctxCL);
        }
    }
}
...
POOL.submit(new Worker(runtimeClassLoader));
newInstance metodu - URL[]
Örnek
Şöyle yaparız
URLClassLoader cl = URLClassLoader.newInstance(new URL[] {new URL("file:///C:/foo.jar")});
resolveClass metodu
Açıklaması şöyle
If the boolean parameter, resolve, to loadClass has been set to true then, it will try to load all the classes referenced by this class. After that, it verifies the correctness and compatibility of the loaded class bytes. If any of the above steps fail, it will throw any subclass of LinkageError. Usually the resolve parameter will always be true when called by the user, but within recursive calls, it will be passed as false, because the class is being specified has already been resolved.
Javassist Kütüphanesi
URLClassLoader'ı kullanabilmek için sınfın "fully qualified name" özelliğini bilmek gerekir. Javassit kütüphanesi ile bu özelliği bilmeye gerek kalmadan şöyle yaparız.
ClassPool cp = ClassPool.getDefault();
InputStream ins = an input stream for reading a class file;
CtClass cc = cp.makeClass(ins);
Daha sonra şöyle yaparız.
Class clazz = cc.toClass();




Hiç yorum yok:

Yorum Gönder