Java Custom ClassLoader implements isolation to run different versions of JAR packs __classloader

Source: Internet
Author: User
Tags pack reflection
1. Application Scenarios

Sometimes we need to run several different versions of Jar packages in a Project to deal with different clusters of versions or other problems. If you choose to implement such a feature in the same project at this time, it is usually possible to select only a lower version of the jar package because they are usually backward-compatible, but this also tends to lose some of the features or functionality of the new version, so we need to introduce these jar packages in an extended manner and execute them in isolation. To implement the mandatory counterpart of the version. 2. To achieve

In Java, all classes are loaded by default by ClassLoader, and Java defaults to provide three-layer ClassLoader, and through the principle of the parental delegation model, the basic model and loading location are as follows (more ClassLoader related principles please search for yourself):

The default ClassLoader in Java specify the load directory that it specifies, and typically do not use JVM parameters to load a custom directory, so we need to customize a ClassLoader to load an extended directory with different versions of JAR packages . At the same time, in order for the extended jar packages to be run in absolute isolation from the startup project, we need to ensure that the classes they load do not have the same ClassLoader, and according to the principles of the parental delegation model, we must make the custom ClassLoader parent Null so that either the JRE's own jar package or some base Class will not be delegated to the APP ClassLoader (of course simply setting Parent to null is not enough, as will be explained later). At the same time, these implementations of different versions of the jar package, after two development can run independently of the project. 2.1 Examples

Assuming that there is a requirement to implement a corresponding execution program for a cluster (such as a Hadoop cluster) version of V1 and V2, assume the following items:

Executor-parent: Provides a basic MAVEN reference that allows you to package all of the Sub modules/project Executor-common with maven one-click
: Provides the basic interface, already has the public realization and so on
Executor-proxy: Agents executing different versions of the program
EXECUTOR-V1: Version V1 executable
executor-v2: version of the executable program V2

Here in order to highlight the implementation of ClassLoader, do not do executor-parent implementation, at the same time for simplicity, also did not set the package name. 1) Executor-common

Provides an interface in Executor-common that declares the specific method of execution:

Public interface Executor {
    void execute (Final String name);
}

The method here uses the underlying type String, which might actually use a custom type, and in PORXY implementations you need to use a custom ClassLoader to load the parameters and use reflection to get the method (a simple example follows). Back to the previous example, this provides an abstract implementation class at the same time:

public class Abstractexecutor implements Executor {

    @Override public
    void execute (Final String name) {
        This.handle (New Handler () {
            @Override public
            void handle () {
                System.out.println ("V:" + name);
            }
        );
    }

    protected void handle (Handler Handler) {
        handler.call ();
    }

    Protected abstract class Handler {public
        void call () {
            ClassLoader Oldclassloader = Thread.CurrentThread (). Getcontextclassloader ();
            Temporarily change ClassLoader
            thread.currentthread (). Setcontextclassloader (AbstractExecutor.class.getClassLoader ());

            Handle ();

            Revert to the previous ClassLoader
            Thread.CurrentThread (). Setcontextclassloader (Oldclassloader);

        public abstract void handle ();
    }

There is a need to temporarily change the current thread's contextclassloader to respond to the following code that might appear in the Extender:

ClassLoader ClassLoader = Thread.CurrentThread (). Getcontextclassloader ();

Classloader.loadclass (...);

Because they get the ClassLoader of the current thread to load class, the ClassLoader of the current thread is most likely the app ClassLoader rather than the custom ClassLoader, perhaps for security reasons, But this can cause it to load into class (if any) in the startup project, or other exceptions, so we need to temporarily set the current thread's ClassLoader to the custom ClassLoader for absolute quarantine execution. 2) Executor-v1 & Executor-v2

Executor-v1 and Executor-v2 rely on the Executor-common.jar and implement the method of Executor interface:

public class ExecutorV1 extends Abstractexecutor {

    @Override public
    void execute (Final String name) {
        This.handle (New Handler () {
            @Override public
            void handle () {
                System.out.println ("V1:" + name);
            }
        );
    }

}
public class ExecutorV2 extends Abstractexecutor {

    @Override public
    void execute (Final String name) {
        This.handle (New Handler () {
            @Override public
            void handle () {
                System.out.println ("V2:" + name);
            }
        );
    }

}

Here only print their version information, in practice, they may need to introduce a different version of the jar package, and then according to these jar packages to complete the appropriate action. 3) Executor-proxy

Executor-proxy uses custom ClassLoader and reflection to implement the Executor interface in the load and run ExecutorV1 and ExecutorV2, while ExecutorV1 and ExecutorV2 will be implemented with a JAR package The forms are placed separately in the ${EXECUTOR-PROXY_HOME}\EXT\V1 and ${executor-proxy_home}\ext\v2 directories, where the custom ClassLoader implementations are as follows:

public class Standardexecutorclassloader extends URLClassLoader {private final static String BaseDir = System.getprop

    Erty ("User.dir") + File.separator + "ext" + file.separator; Public Standardexecutorclassloader (String version) {super (new url[] {}, NULL);//set Parent to null load
    Resource (version);
        @Override public class<?> loadclass (String name) throws ClassNotFoundException {//The test can print a look at

        SYSTEM.OUT.PRINTLN ("Class loader:" + name);
    return Super.loadclass (name);
            @Override protected class<?> Findclass (String name) throws ClassNotFoundException {try {
        return Super.findclass (name); 
        catch (ClassNotFoundException e) {return StandardExecutorClassLoader.class.getClassLoader (). LoadClass (name);

        } private void LoadResource (string version) {string jarpath = BaseDir + version; Load the jar package Tryloadjarindir in the corresponding version directory (jarPath);
    Loads the Jar package Tryloadjarindir (Jarpath + file.separator + "Lib") under the Lib directory under the corresponding version directory;
        private void Tryloadjarindir (String dirpath) {file Dir = new File (Dirpath);
                The jar Pack if (dir.exists () && dir.isdirectory ()) {for File file:dir.listFiles ()) in the auto load directory {
                    if (File.isfile () && file.getname (). EndsWith (". Jar")) {This.addurl (file);
                Continue '}} ' private void Addurl (file file) {try {super.addurl (new URL ("File
        ", NULL, File.getcanonicalpath ()));
        catch (Malformedurlexception e) {e.printstacktrace ();
        catch (IOException e) {e.printstacktrace (); }
    }

}

Standardexecutorclassloader when instantiated, the jar packages under the extended directory and its Lib directory are loaded automatically, and the jar under the Lib directory is loaded to load the extended dependency pack.

With Standardexecutorclassloader, we also need a proxy class executorporxy that invokes each version of the program, which is implemented as follows:

Import Java.lang.reflect.Method;

public class Executorproxy implements Executor {
    private String version;
    Private Standardexecutorclassloader ClassLoader;

    Public Executorproxy (String version) {
        this.version = version;
        ClassLoader = new Standardexecutorclassloader (version);
    }

    @Override public
    void execute (String name) {
        try {
            //Load Executorproxy class
            class<?> Executorclazz = Classloader.loadclass ("Executor" + version.touppercase ());

            Object executorinstance = Executorclazz.newinstance ();
            Method method = Executorclazz.getmethod ("Execute", string.class);

            Method.invoke (executorinstance, name);
        catch (Exception e) {
            e.printstacktrace ();}}}

This is a relatively simple implementation, because the parameter of the method invoked through reflection is the basic type, and in practice, it is more likely to be a custom parameter, then you need to load its Class with a custom ClassLoader before you can get the corresponding method. The following is an example of an omitted context (which cannot be run directly):

public void Call () throws IOException {try {//Load Hbaseapi class class<?> Hbaseapiclazz = Lo
        Adhbaseapiclass ();

        Object hbaseapiinstance = Hbaseapiclazz.newinstance (); Load parameter class class<?> Paramclazz = Classloader.loadclass (Vo_package_path + "." + Sourceparame.get

        Class (). Getsimplename ()); Transition parameter to Targeparameter from sourceparameter Object targetparam = Beanutils.transfrom (Paramclaz

        Z, Sourceparame);
        Get Function Method method = Hbaseapiclazz.getmethod (methodname, Paramclazz);

    Invoke function by Targetparam Method.invoke (Hbaseapiinstance, Targetparam); catch (ClassNotFoundException | nosuchmethodexception | SecurityException | instantiationexception |
    Illegalaccessexception e) {e.printstacktrace ();
    catch (IllegalArgumentException e) {//TODO auto-generated catch block E.printstacktrace (); catch (InvoCationtargetexception e) {//TODO auto-generated catch block E.printstacktrace (); }
}
3. Run

The ExecutorV1 and ExecutorV2 are packaged separately, and their packaged jar bundles and their dependencies (under the Lib directory) are placed in the EXT\V1 and ext\v2 directories of the Executor-proxy project, and in the Executor-proxy project you can use the J Unit for testing:

public class Executortest {

    @Test public
    void TestExecuteV1 () {

        Executor Executor = new Executorproxy ("V1");

        Executor.execute ("TOM");

    @Test public
    void TestExecuteV2 () {

        Executor Executor = new Executorproxy ("V2");

        Executor.execute ("TOM");
    }


The results of the printing were as follows:

Execute testExecuteV1 ():

v1:tom
Execute testExecuteV2 ():

v2:tom
4. Summary

In general, implementing quarantine allows you to specify jar packages, primarily by customizing the ClassLoader to Parent = NULL to avoid using the system's own ClassLoader load Class. Before invoking the appropriate version of the method, change the contextclassloader of the current thread to avoid the expansion pack's dependent packets being fetched through Thread.CurrentThread (). Getcontextclassloader () to a non-custom ClassLoader for class loading when you get method by reflection, if the parameter is a custom type, be sure to get class with the custom ClassLoader load parameter, and then get method, and the parameter must also be converted to use a custom Classloade loaded type (different ClassLoader loaded same class is not equal)

In practice, it is often easy to do 1th or 3rd, while ignoring the 2nd, such as using HBase related packages.

Of course, this is just a solution, we can still use micro-services to achieve the same or even better results,

Above.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.