Java类加载器--使用自定义类加载器实现模块隔离

在看下面的实现之前,可以先了解下面的几篇文章内容:

1 https://blog.csdn.net/u013412772/article/details/80837735
2 https://blog.csdn.net/u013412772/article/details/80842636
3 https://blog.csdn.net/u013412772/article/details/80851700
4 https://blog.csdn.net/u013412772/article/details/80865875
5 https://blog.csdn.net/u013412772/article/details/80906997

模块隔离功能:每个模块使用一个类加载器进行加载,每个模块独立作为一个Jar包对外提供功能.我们把每个模块的Jar里面的类加载到一个内存map,然后再创建一个自定义类加载器,重写loadClass方法,当加载类时候优先在内存map里面查找,如果存在则直接返回,否则调用默认加载方法.工程结构图如下:

这里写图片描述

将demo_classloader工程使用maven的命令clean package命令打包成jar包(demo_classloader的pom.xml设置打包形式也是jar),如下所示:

这里写图片描述

在target目录下找到打成jar的文件:

这里写图片描述

其中datamanager工程里面的classloader包里面的ModuleManager是核心类,实现了从不同模块加载jar里面的类到内存map的功能,核心代码如下:

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import com.alibaba.druid.util.StringUtils;

public class ModuleManager {

    /**
     * 用来并发加载多个模块jar
     */
    private ExecutorService executorService = Executors.newFixedThreadPool(8);

    /**
     * cache用来存放所有Jar里面的Class对象
     */
    @SuppressWarnings("rawtypes")
    private Map<String, Class> cache = new ConcurrentHashMap<String, Class>();

    /**
     * moduleList是需要加载的模块的Jar的路径
     */
    private List<String> moduleList = new ArrayList<String>();

    /**
     * 核心方法:其中future的call方法针对每个模块新建了一个URLClassLoader加载器用来加载该模块的类,并把加载的类放入内存map.
     */
    public void init() {
        System.out.println("----begin load all module----");
        List<Future<String>> futureList = new ArrayList<Future<String>>();
        for (String module : moduleList) {
            Future<String> future = executorService.submit(new Callable<String>() {

                @Override
                public String call() throws Exception {
                    try {
                        // URL[] moduleURL = new URL[] { new URL("file://"
                        // +module) };
                        File moduleFile = new File(module);
                        URL moduleURL = moduleFile.toURI().toURL();
                        URL[] moduleUrl = new URL[] { moduleURL };

                        @SuppressWarnings("resource")
                        URLClassLoader classLoader = new URLClassLoader(moduleUrl);
                        @SuppressWarnings("resource")
                        JarFile jar = new JarFile(new File(module));
                        Enumeration<JarEntry> entries = jar.entries();
                        while (entries.hasMoreElements()) {
                            JarEntry entry = entries.nextElement();
                            String className = getClassName(entry);
                            if (StringUtils.isEmpty(className)) {
                                continue;
                            }

                            try {
                                Class<?> clazz = classLoader.loadClass(className);
                                cache.put(className, clazz);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return module;
                }
            });
            futureList.add(future);
        }

        for (Future<String> future : futureList) {
            try {
                String moduleName = future.get();
                System.out.println("----load module " + moduleName + "ok");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        executorService.shutdown();
        System.out.println("----end load all module----");
    }

    private String getClassName(JarEntry jarEntry) {
        String jarName = jarEntry.getName();

        if (!jarName.endsWith(".class")) {
            return null;
        }
        if (jarName.charAt(0) == '/') {
            jarName = jarName.substring(1);
        }
        jarName = jarName.replace("/", ".");
        return jarName.substring(0, jarName.length() - 6);
    }

    public ExecutorService getExecutorService() {
        return executorService;
    }

    public void setExecutorService(ExecutorService executorService) {
        this.executorService = executorService;
    }

    public Map<String, Class> getCache() {
        return cache;
    }

    public void setCache(Map<String, Class> cache) {
        this.cache = cache;
    }

    public List<String> getModuleList() {
        return moduleList;
    }

    public void setModuleList(List<String> moduleList) {
        this.moduleList = moduleList;
    }

}

ModuleClassLoader是自定义的一个类加载器,代码如下:

import java.net.URL;
import java.net.URLClassLoader;

public class ModuleClassLoader extends URLClassLoader {

    private ModuleManager manager = new ModuleManager();

    public ModuleClassLoader(URL[] urls, ModuleManager manager) {
        super(urls);
        this.manager = manager;
    }

    /**
     * 重写了loadClass,优先从内存map查找要加载的类
     */
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        if (manager.getCache().containsKey(name)) {
            return manager.getCache().get(name);
        } else {
            return super.loadClass(name, resolve);
        }
    }

}

ModuleClassLoaderTest是个测试类,如下所示:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;

import classloader.ModuleClassLoader;
import classloader.ModuleManager;

public class ModuleClassLoaderTest {

    @SuppressWarnings("resource")
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
            InvocationTargetException, InstantiationException {
        List<String> jarList = new ArrayList<String>();
        jarList.add("D:\\workspace\\demo_classloader\\target\\demo_classloader-0.0.1-SNAPSHOT.jar");

        ModuleManager manager = new ModuleManager();
        manager.setModuleList(jarList);
        manager.init();

        ModuleClassLoader classLoader = new ModuleClassLoader(((URLClassLoader) ModuleClassLoaderTest.class.getClassLoader()).getURLs(), manager);
        Class<?> moduleA = classLoader.loadClass("module.ModuleService");
        Method sayHelloModuleA = moduleA.getMethod("sayHelloModule");
        String result = (String) sayHelloModuleA.invoke(moduleA.newInstance(), null);
        System.out.println(result);
    }

}

首先创建了一个ModuleManager对象,设置需要加载模块的jar列表,然后调用init方法使用不同的类加载器加载不同jar里面的class到内存map.然后创建了一个ModuleClassLoader的实例,其内部维护这内存map,当使用classLoader.loadClass("module.ModuleService");加载ModuleService时,回去内存的map查看是否存在该类,如果有,则返回ModuleService的class对象,然后创建一个ModuleService的实例,最后通过反射调用了sayHelloModule方法.

猜你喜欢

转载自blog.csdn.net/u013412772/article/details/80927208