【正文】Java类加载器(  CLassLoader )死磕 6: 

自定义网络类加载器

本小节目录

6.1. 自定义网络类加载器的类设计

6.2. 文件传输Server端的源码

6.3. 文件传输Client端的源码

6. 4 自定义加载器SocketClassLoader的源码

6.5. SocketClassLoader的使用

前面提到,除了通过Java内置的三大加载器,从JVM中系统属性中设置的三大地盘加载Java类,还存多种的获取Class文件途径。其中非常重要的一种途径,就是网络。

通过网络的加载类,就得依赖网络的传输协议。

网络的传输协议有很多种,比方说TCP、HTTP、SMB等等,都可以用来实现网络的类字节码的传输。

TCP是最为基础的,也是一种传输可靠的传输协议。本小节通过TCP协议,实现自定义的网络类加载器。

1.1.1. 自定义网络类加载器的类设计

服务端的类图如下:

客户端的类图:


1.1.2. 文件传输Server端的源码

文件传输Server端的工作:

开启一个ServerSocket服务,等待Client客户端的TCP连接。

对于每一个客户端TCP连接,开启一个单独的线程,处理文件的请求和发送文件数据。

独立线程首先会接受客户端传输过来的文件名称,根据文件名称,在自定义的类路径下查找文件。这里的类路径是这个第三方类库的路径,为了可以灵活多变,并且与源代码工程的输出路径相不能相同,也在System.properties 配置文件增加配置项,具体为:

class.server.path=D:/疯狂创客圈 死磕java/code/out2/

此配置项在前面的案例中,已经用到了,后面也会多次用到。

服务端找到文件后,开始向客户端传输数据。首先传输文件的大小,然后在传输文件的内容。

简单粗暴,直接上源码。

public class SocketClassServer

{

    ServerSocket serverSocket = null;

    static String filePath = null;

    public SocketClassServer() throws Exception

    {

        serverSocket = new ServerSocket(SystemConfig.SOCKET_SERVER_PORT);

        this.filePath = SystemConfig.CLASS_SERVER_PATH;

        startServer();

    }

    /**

     * 启动服务端

     * 使用线程处理每个客户端传输的文件

     *

     * @throws Exception

     */

    public void startServer()

    {

        while (true)

        {

            // server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的

            Logger.info("server listen at:" + SystemConfig.SOCKET_SERVER_PORT);

            Socket socket = null;

            try

            {

                socket = serverSocket.accept();

                // 每接收到一个Socket就建立一个新的线程来处理它

                new Thread(new SendTask(socket)).start();

            } catch (Exception e)

            {

                e.printStackTrace();

            }

        }

    }

    /**

     * 处理客户端传输过来的文件线程类

     */

    class SendTask implements Runnable

    {

        private Socket socket;

        private DataInputStream dis;

        private FileOutputStream fos;

        public SendTask(Socket socket)

        {

            this.socket = socket;

        }

        @Override

        public void run()

        {

            try

            {

                dis = new DataInputStream(socket.getInputStream());

                // 文件名

                String fileName = dis.readUTF();

                DataOutputStream dos = new DataOutputStream(socket.getOutputStream());

                sendFile(fileName, dos);

            } catch (Exception e)

            {

                e.printStackTrace();

            } finally

            {

                IOUtil.closeQuietly(fos);

                IOUtil.closeQuietly(dis);

                IOUtil.closeQuietly(socket);

            }

        }

        private void sendFile(String fileName, DataOutputStream dos) throws Exception

        {

            fileName = classNameToPath(fileName);

            fileName = SocketClassServer.filePath + File.separator + fileName;

            File file = new File(fileName);

            if (!file.exists())

            {

                throw new Exception("file not found! :" + fileName);

            }

            long fileLen = file.length();

//先传输文件长度

            dos.writeLong(fileLen);

            dos.flush();

            FileInputStream fis = new FileInputStream(file);

            // 开始传输文件

            Logger.info("======== 开始传输文件 ========");

            byte[] bytes = new byte[1024];

            int length = 0;

            long progress = 0;

            while ((length = fis.read(bytes, 0, bytes.length)) != -1)

            {

                dos.write(bytes, 0, length);

                dos.flush();

                progress += length;

                Logger.info("| " + (100 * progress / fileLen) + "% |");

            }

            Logger.info("======== 文件传输成功 ========");

        }

    }

    private String classNameToPath(String className)

    {

        return className.replace('.', '/') + ".class";

    }

    public static void main(String[] args)

    {

        try

        {

            SocketClassServer socketServer = new SocketClassServer();

            socketServer.startServer();

        } catch (Exception e)

        {

            e.printStackTrace();

        }

    }

}

源码比较长,建议运行main函数,先将服务端的源码跑起来,然后再阅读代码,这样阅读起来更加容易懂。

另外,在使用基于网络的类加载器之前,一定要确保服务端的代码先执行。否则客户端会报错。

案例路径:com.crazymakercircle.classLoader.SocketClassServer

案例提示:无编程不创客、无案例不学习。一定要跑案例哦

运行的结果是:

         <clinit> |>  开始加载配置文件到SystemConfig

        loadFromFile |>  load properties: /system.properties

         startServer |>  server listen at:18899

看到以上结果,表示服务端开始启动。监听了18899端口,等待客户端的连接。

1.1.3. 文件传输Client端的源码

客户端的工作:

建立和服务器的TCP连接后,首先做的第一步工作,是发送文件名称给服务器端。

然后阻塞,直到服务器的数据过来。客户端开始接受服务器传输过来的数据。接受数据的工作由函数receivefile()完成。

整个的数据的读取工作分为两步,先读取文件的大小,然后读取传输过来的文件内容。

简单粗暴,直接上源码。

public class SafeSocketClient {

    private Socket client;

    private FileInputStream fis;

    private DataOutputStream dos;

    /**

     * 构造函数<br/>

     * 与服务器建立连接

     *

     * @throws Exception

     */

    public SafeSocketClient() throws IOException {

            this.client = new Socket(

                    SystemConfig.SOCKET_SERVER_IP,

                    SystemConfig.SOCKET_SERVER_PORT

            );

            Logger.info("Cliect[port:" + client.getLocalPort() + "] 成功连接服务端");

    }

    /**

     * 向服务端去取得文件

     *

     * @throws Exception

     */

    public byte[] getFile(String fileName) throws Exception {

        byte[] result = null;

        try {

            dos = new DataOutputStream(client.getOutputStream());

            // 文件名和长度

            dos.writeUTF(fileName);

            dos.flush();

            DataInputStream dis = new DataInputStream(client.getInputStream());

            result = receivefile(dis);

            Logger.info("文件接收成功,File Name:" + fileName);

        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            IOUtil.closeQuietly(fis);

            IOUtil.closeQuietly(dos);

            IOUtil.closeQuietly(client);

        }

        return result;

    }

    public byte[] receivefile(DataInputStream dis) throws Exception {

        int fileLength = (int) dis.readLong();

        ByteArrayOutputStream bos = new ByteArrayOutputStream(fileLength);

        long startTime = System.currentTimeMillis();

        Logger.info("block IO 传输开始:");

        // 开始接收文件

        byte[] bytes = new byte[1024];

        int length = 0;

        while ((length = dis.read(bytes, 0, bytes.length)) != -1) {

            DeEnCode.decode(bytes,length);

            bos.write(bytes, 0, length);

            bos.flush();

        }

        Logger.info(" Size:" + IOUtil.getFormatFileSize(fileLength));

        long endTime = System.currentTimeMillis();

        Logger.info("block IO 传输毫秒数:" + (endTime - startTime));

        bos.flush();

        byte[] result = bos.toByteArray();

        IOUtil.closeQuietly(bos);

        return result;

    }

}

案例路径:com.crazymakercircle.classLoader.SocketClassClient

案例提示:无编程不创客、无案例不学习。

此案例类没法独立运行,因为没有运行的入口。只能作为基础类,供其他类调用。

1.1.4. 自定义加载器SocketClassLoader的源码

前面讲到,自定义类加载器,首先要继承ClassLoader抽象类,并且重写其findClass()方法。

在重写的findClass()方法中,完成以下三步:

(1)在自己的地盘(查找路径),获取对应的字节码;

(2)并完成字节码到Class类对象的转变;

(3)返回Class类对象。

简单粗暴,直接上源码。

public class SocketClassLoader extends ClassLoader {

    public SocketClassLoader() {

//        super(null);

    }

    protected Class<?> findClass(String name)

throws ClassNotFoundException {

       Logger.info("findClass name = " + name);

        byte[] classData = null;

        try {

            SocketClassClient client = new SocketClassClient();

            classData = client.getFile(name);

        } catch (Exception e) {

            e.printStackTrace();

            throw new ClassNotFoundException();

        }

        if (classData == null) {

            throw new ClassNotFoundException();

        } else {

            return defineClass(name, classData, 0, classData.length);

        }

    }

}

案例路径:com.crazymakercircle.classLoader.SocketClassClient

有了前面的基础,此源码超级简单,关键的两行,就是下面这个两行:

SafeSocketClient client = new SafeSocketClient();

classData = client.getFile(name);

上面一行,实例化一个SafeSocketClient 客户端client对象。下面一行,通过取得 client对象的client.getFile(name) 方法,通过网络TCP协议,获得类的字节码。

至于findClass()方法中的其他的处理工作,和前面的文件系统内加载器FileClassLoader 中的findClass(),是一样的。

这里不做赘述。

1.1.5. SocketClassLoader的使用

简单粗暴,先上代码:

public class SocketLoaderDemo

{

    public static void testLoader()

    {

        try

        {

            SocketClassLoader classLoader = new SocketClassLoader();

            String className = SystemConfig.PET_DOG_CLASS;

            Class dogClass = classLoader.loadClass(className);

            Logger.info("显示dogClass的ClassLoader =>");

            ClassLoaderUtil.showLoader4Class(dogClass);

            IPet pet = (IPet) dogClass.newInstance();

            pet.sayHello();

        } catch (ClassNotFoundException e)

        {

            e.printStackTrace();

        } catch (IllegalAccessException e)

        {

            e.printStackTrace();

        } catch (InstantiationException e)

        {

            e.printStackTrace();

        }

    }

    public static void main(String[] args)

    {

        testLoader();

    }

}

案例路径:com.crazymakercircle.classLoaderDemo.base.SocketLoaderDemo

案例提示:无编程不创客、无案例不学习。一定要跑案例哦

运行的结果是:

  <clinit> |>  开始加载配置文件到SystemConfig

        loadFromFile |>  load properties: /system.properties

           findClass |>  findClass name = com.crazymakercircle.otherPet.pet.LittleDog

              <init> |>  Cliect[port:51525] 成功连接服务端

         receivefile |>  block IO 传输开始:

         receivefile |>  Size:2.0KB

         receivefile |>  block IO 传输毫秒数:8

             getFile |>  文件接收成功,File Name:com.crazymakercircle.otherPet.pet.LittleDog

          testLoader |>  显示dogClass的ClassLoader =>

      showLoaderTree |>  com.crazymakercircle.classLoader.SocketClassLoader@34ce8af7

      showLoaderTree |>  sun.misc.Launcher$AppClassLoader@18b4aac2

      showLoaderTree |>  sun.misc.Launcher$ExtClassLoader@51016012

Disconnected from the target VM, address: '127.0.0.1:51523', transport: 'socket'

            sayHello |>  嗨,大家好!我是LittleDog-1

看到以上结果,表示客户端成功接收了字节码文件,并且成功加载了类。

扩展一下,例如你的第三方的字节码是放在数据库中,也可类似的自己写个类加载器,从指定的数据库加载二进制类。

源码:

代码工程:  classLoaderDemo.zip

下载地址:在疯狂创客圈QQ群文件共享。

疯狂创客圈:如果说Java是一个武林,这里的聚集一群武痴, 交流编程体验心得
QQ群链接:疯狂创客圈QQ群

无编程不创客,无案例不学习。 一定记得去跑一跑案例哦

类加载器系列全目录

1.导入

2. JAVA类加载器分类

3. 揭秘ClassLoader抽象基类

4. 神秘的双亲委托机制

5. 入门案例:自定义一个文件系统的classLoader

6. 基础案例:自定义一个网络类加载器

7. 中级案例:设计一个加密的自定义网络加载器

8. 高级案例1:使用ASM技术,结合类加载器,解密AOP原理

9. 高级案例2:上下文加载器原理和案例

Java类加载器( 死磕 6)的更多相关文章

  1. Java类加载器(死磕 1-2)

      Java类加载器(  CLassLoader ) 死磕 1.2:  导入 & 类加载器分类 本小节目录 1.导入 1.1. 从class文件的载入开始 1.2. 什么是类加载器 2. JA ...

  2. Java类加载器(死磕5)

    Java类加载器(  CLassLoader )  死磕5:  自定义一个文件系统classLoader 本小节目录 5.1. 自定义类加载器的基本流程 5.2. 入门案例:自定义文件系统类加载器 5 ...

  3. Java类加载器( 死磕9)

    [正文]Java类加载器(  CLassLoader ) 死磕9:  上下文加载器原理和案例 本小节目录 9.1. 父加载器不能访问子加载器的类 9.2. 一个宠物工厂接口 9.3. 一个宠物工厂管理 ...

  4. Java类加载器( 死磕7)

    [正文]Java类加载器(  CLassLoader )死磕7:  基于加密的自定义网络加载器 本小节目录 7.1. 加密传输Server端的源码 7.2. 加密传输Client端的源码 7.3. 使 ...

  5. Java类加载器( 死磕8)

    [正文]Java类加载器(  CLassLoader ) 死磕 8:  使用ASM,和类加载器实现AOP 本小节目录 8.1. ASM字节码操作框架简介 8.2. ASM和访问者模式 8.3. 用于增 ...

  6. Java类加载器( 死磕 4)

    [正文]Java类加载器(  CLassLoader ) 死磕 之4:  神秘的双亲委托机制 本小节目录 4.1. 每个类加载器都有一个parent父加载器 4.2. 类加载器之间的层次关系 4.3. ...

  7. Java类加载器(死磕3)

    [正文]Java类加载器(  CLassLoader ) 死磕3:  揭秘 ClassLoader抽象基类 本小节目录 3.1. 类的加载分类:隐式加载和显示加载 3.2. 加载一个类的五步工作 3. ...

  8. java笔记--理解java类加载器以及ClassLoader类

    类加载器概述: java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制 ...

  9. java类加载器深入研究

    看了下面几篇关于类的加载器的文章,豁然开朗.猛击下面的地址开始看吧. Java类加载原理解析      深入探讨 Java 类加载器 分析BootstrapClassLoader/ExtClassLo ...

随机推荐

  1. AC日记——网络最大流 洛谷 P3376

    题目描述 如题,给出一个网络图,以及其源点和汇点,求出其网络最大流. 输入输出格式 输入格式: 第一行包含四个正整数N.M.S.T,分别表示点的个数.有向边的个数.源点序号.汇点序号. 接下来M行每行 ...

  2. git上传(本地和远程有冲突时)

    一. 冲突的产生:在上次git同步(上传)之后,本地和远程均有更改 二. 处理 1. 丢弃本地,采用远程: git checkout 冲突文件及其路径 如: git checkout bzrobot_ ...

  3. Eclipse安装Spring工具套件

    前言: 安装spring工具套件是为了更快捷的使用spring,但是我觉得既然已经有了maven,工具套件其实不那么重要. 而且装好后我发觉没什么两样,只是新建bean文件时比较爽一点. 安装步骤: ...

  4. cookie读取设置name

    cookie就是k-v形式,可以理解为一个hashmap cookie就是k-v形式,可以理解为一个hashmap cookie就是k-v形式,可以理解为一个hashmap 建立一个无生命周期的coo ...

  5. 【bootstrap】modal模态框的几种打开方法+问题集锦

    第一部分: 关于bootstrap中modal的使用,下面把几种自己用的打开方法展示出来 首先呢,得有个Bootstrap的页面,这里就不说了. 其次呢,得有个modal放在页面中,不管你这段代码加在 ...

  6. linux安装开源邮件服务器iredmail的方法:docker

    直接安装的方法,参考网文,我不介绍.本文介绍的是快速的方法:docker 使用镜像源:https://hub.docker.com/r/lejmr/iredmail/,因为pull的数量最多 直接 d ...

  7. 第四讲_图像识别之图像分类Image Classification

    第四讲_图像识别之图像分类Image Classification 目录 图片分类 性能指标:top1,top5 ILSVRC:每种任务数据集不一样 imageNet:根据WorldNet组织的图片集 ...

  8. xgboost的SparkWithDataFrame版本实现

    再xgboost的源码中有xgboost的SparkWithDataFrame的实现,如下:https://github.com/dmlc/xgboost/tree/master/jvm-packag ...

  9. soapUI学习笔记--用例字段参数化

    字段参数化的简单操作 1.把Request新增一个TestCase 增加TestCase,下方会出现: 2.案例中,请求参数只有一个.先运行下请求,可以运行成功(保证接口是通的) 3.添加参数.见图中 ...

  10. Controller//控制器

    #include<opencv2\core\core.hpp> #include<opencv2\imgproc\imgproc.hpp> #include<opencv ...