美文网首页
2021-06-14_Java自定义类加载器2

2021-06-14_Java自定义类加载器2

作者: kikop | 来源:发表于2021-06-14 00:29 被阅读0次

20210614_Java自定义类加载器2

1概述

我们知道自定义类加载器就是用于对象隔离,如何实现呢?

本章主要学习下Java类加载器,完成完成了两个同名的class com.kikop.Address共存。

主要内容如下:

  1. 模拟两个Jar包MyModuleA-1.0-SNAPSHOT.jar、MyModuleB-1.0-SNAPSHOT.jar,分别定义同名包空间com.kikop下的Address类。
  2. 借助自定义类加载器,实现两个同名类的加载。
  3. 总结分析。

[图片上传失败...(image-903e64-1623601711557)]

2代码实战

2.1ClassLoaderUtil

package com.kikop.utils;

import java.io.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author kikop
 * @version 1.0
 * @project Name: myjvmdemo
 * @file Name: ClassLoaderUtil
 * @desc 自定义加载器工具类
 * @date 2021/6/13
 * @time 18:00
 * @by IDE: IntelliJ IDEA
 */
public class ClassLoaderUtil {


    /**
     * 返回类的字节码
     *
     * @param filePath
     * @return
     */
    public static byte[] getBytesByFilePath(String filePath) {

        byte[] resultBytes = null;
        InputStream inputStream = null;

        // 借助 byteArrayOutputStream暂存字节流
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try {
            File file = new File(filePath);
            if (!file.exists()) {
                return resultBytes;
            }
            inputStream = new FileInputStream(file);
            int c = 0;
            while ((c = inputStream.read()) != -1) {
                byteArrayOutputStream.write(c);
            }
            resultBytes = byteArrayOutputStream.toByteArray();
        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                byteArrayOutputStream.close();
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return resultBytes;
    }


    /**
     * 返回类的字节码
     *
     * @param classpath
     * @param jarName
     * @param className
     * @return
     * @throws IOException
     */
    public static byte[] getBytesByJarFile(String classpath, String jarName, String className) throws IOException {

        byte[] resultBytes = null;

        InputStream inputStream = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        try {
            String tmp = className.replaceAll("\\.", "/");
            JarFile jar = new JarFile(classpath + "/" + jarName);
            JarEntry entry = jar.getJarEntry(tmp + ".class");

            inputStream = jar.getInputStream(entry);
            byteArrayOutputStream = new ByteArrayOutputStream();
            int nextValue = inputStream.read();
            while (-1 != nextValue) {
                byteArrayOutputStream.write(nextValue);
                nextValue = inputStream.read();
            }


//            byte[] buffer=new byte[2048];
//            int len=0;
//            while((len=in.read(buffer))!=-1){
//                out.write(buffer,0,len);
//            }

            resultBytes = byteArrayOutputStream.toByteArray();
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            try {
                byteArrayOutputStream.close();
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return resultBytes;
    }
}

2.2自定义MyClassLoader

package com.kikop.myclazzloader;

import com.kikop.utils.ClassLoaderUtil;
import org.omg.CORBA.PRIVATE_MEMBER;

import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

/**
 * @author kikop
 * @version 1.0
 * @project Name: myjvmdemo
 * @file Name: MyClassLoader
 * @desc 通过自定义加载器加载指定类
 * @date 2021/6/13
 * @time 18:00
 * @by IDE: IntelliJ IDEA
 */
public class MyJarClassLoader extends ClassLoader {

    // 自定义加载器的加载目录
    private String classPath;

    // 自定义加载器的jar名称
    private String JarName;

    // 自定义加载类前缀
    private String customClazzPrefix;

    private HashMap<String, Class<?>> loadedClasses = new HashMap<>();

    public MyJarClassLoader(ClassLoader parent) {
        super(parent);
    }

    public MyJarClassLoader(ClassLoader parent, String classPath, String JarName, String customClazzPrefix) {
        super(parent);
        this.classPath = classPath;
        this.JarName = JarName;
        this.customClazzPrefix = customClazzPrefix;
    }

    /**
     * @param name 指定的类
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {

        Class<?> aClass = null;

        if (!name.startsWith(this.customClazzPrefix)) { // do native
            return super.loadClass(name);
        }
        if (loadedClasses.containsKey(name)) {
            return loadedClasses.get(name);
        }
        try {
            byte[] bytesByJarFile = ClassLoaderUtil.getBytesByJarFile(this.classPath, this.JarName, name);

            if (null != bytesByJarFile) {
                // defineClass方法将字节码转化为类
                // defineClass(name,classDate,0,classDate.length);
                aClass = defineClass(name, bytesByJarFile, 0, bytesByJarFile.length, null);
                loadedClasses.put(name, aClass);
                return aClass;
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        return super.loadClass(name);
    }
}

2.3MyModuleA::Address

package com.kikop;


/**
 * @author kikop
 * @version 1.0
 * @project Name: MyModuleA
 * @file Name: Address
 * @desc
 * @date 2021/6/13
 * @time 18:00
 * @by IDE: IntelliJ IDEA
 */
public class Address {

    public String showVersion() {
        return "V1.0";
    }
}

2.5MyModuleB::Address

package com.kikop;


/**
 * @author kikop
 * @version 1.0
 * @project Name: MyModuleB
 * @file Name: Address
 * @desc
 * @date 2021/6/13
 * @time 18:00
 * @by IDE: IntelliJ IDEA
 */
public class Address {

    public String showVersion() {
        return "V2.0";
    }
}

2.4测试

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {

        // E:\workdirectory\Dev\study\technicaltools
//        System.out.println(System.getProperty("user.dir"));

        String classPath = System.getProperty("user.dir") + File.separator + "myjvmdemo" + File.separator + "lib" + File.separator;
        String customClazzPrefix = "com.kikop";

        // 1.Jar包A测试
        String jarNameA = "MyModuleA-1.0-SNAPSHOT.jar";
        MyJarClassLoader myJarClassLoaderA = new MyJarClassLoader(
                MyJarClassLoader.class.getClassLoader(),
                classPath, jarNameA, customClazzPrefix);

        Class<?> aClass = myJarClassLoaderA.loadClass("com.kikop.Address");

        Class<?> aClass_copy = myJarClassLoaderA.loadClass("com.kikop.Address");


        // 直接newInstance的话必须保证默认的构造方法正常存在,也就是没有被私有化!这是前提条件
        Object addressInstanceA = aClass.newInstance();
        Method showVersionMethodA = aClass.getDeclaredMethod("showVersion");
        Object invokeResultA = showVersionMethodA.invoke(addressInstanceA);
        System.out.println("Jar包A:" + aClass.getClassLoader() + ",showVersion:" + invokeResultA);

        // 2.Jar包B测试
        String jarNameB = "MyModuleB-1.0-SNAPSHOT.jar";
        MyJarClassLoader myJarClassLoaderB = new MyJarClassLoader(MyJarClassLoader.class.getClassLoader(),
                classPath, jarNameB, customClazzPrefix);
        Class<?> bClass = myJarClassLoaderB.loadClass("com.kikop.Address");

        // 使用灵活度强的 getConstructor获取指定的构造方法。
        // 先获得需要被调用的构造器(private 修饰的构造方法)
        Constructor<?> constructor = bClass.getConstructor();
        // 若要访问私有的成员,得先申请一下,缺陷:对于此时的话,单例模式就不再安全了!反射可破之
        constructor.setAccessible(true);
        // 成功,通过私有的受保护的构造方法创建了对象
        Object addressInstanceB = constructor.newInstance();
        Method showVersionMethodB = bClass.getDeclaredMethod("showVersion");
        Object invokeResultB = showVersionMethodB.invoke(addressInstanceB);
        System.out.println("Jar包B:" + bClass.getClassLoader() + ",showVersion:" + invokeResultB);

        boolean isSame = aClass.equals(bClass);
        if (isSame) {
            System.out.println("claxx一样,天理难容!");
        } else {
            System.out.println("不同的类加载器对象实例,所以方法区中的clazz也不一样!");
        }
    }
Jar包A:com.kikop.myclazzloader.MyJarClassLoader@7382f612,showVersion:V1.0
Jar包B:com.kikop.myclazzloader.MyJarClassLoader@77afea7d,showVersion:V2.0
不同的类加载器对象实例,所以方法区中的clazz也不一样!

3总结

通过上面的代码实战, 我们基本完成了自定义加载器的类加载。

完成了两个同名的class com.kikop.Address共存。

参考

1深入理解Java类加载器(一):Java类加载原理解析

https://blog.csdn.net/justloveyou_/article/details/72217806

相关文章

网友评论

      本文标题:2021-06-14_Java自定义类加载器2

      本文链接:https://www.haomeiwen.com/subject/wcjneltx.html