日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

1、為什么要自定義類加載器呢?有什么好處

①、隔離加載類

在某些框架內進行中間件與應用的模塊隔離,把類加載到不同的環境。比如:阿里內某容器框架通過自定義類加載器確保應用中依賴的jar包不會影響到中間件運行時使用的jar包。再比如:Tomcat這類Web應用服務器,內部自定義了好幾種類加載器,用于隔離同一個Web應用服務器上的不同應用程序。

兩個jar包內都存在相同類名且包名相同,如果沒有隔離加載類,則會報錯,如:兩個版本的jar

②、修改類加載方式

類的加載模型并非強制,除Bootstrap外,其他的加載并非一定要引入,或者根據實際情況在某個時間點進行按需進行動態加載

③、擴展加載源

比如從數據庫、網絡、甚至是電視機機頂盒進行加載

④、防止源碼泄露

JAVA代碼容易被編譯和篡改,可以進行編譯加密。那么類加載也需要自定義,還原加密的字節碼。

通常Java系統想增加License(授權),就可以通過自定義類加載器實現。

具體實現我們在最后面測試。

2、自定義類加載器的使用場景

①、實現類似進程內隔離,類加載器實際上用作不同的命名空間,以提供類似容器、模塊化的效果。例如,兩個模塊依賴于某個類庫的不同版本,如果分別被不同的容器加載,就可以互不干擾。這個方面的集大成者是JavaEE和OSGI、JPMS等框架。

②、應用需要從不同的數據源獲取類定義信息,例如網絡數據源,而不是本地文件系統。或者是需要自己操縱字節碼,動態修改或者生成類型。

3、類加載器注意點

在一般情況下,使用不同的類加載器去加載不同的功能模塊,會提高應用程序的安全性。但是,如果涉及Java類型轉換,則加載器反而容易產生不美好的事情。在做Java類型轉換時,只有兩個類型都是由同一個加載器所加載,才能進行類型轉換,否則轉換時會發生異常。

4、自定義類加載器實現方式

Java提供了抽象類java.lang.ClassLoader,所有用戶自定義的類加載器都應該繼承ClassLoader類。

在自定義ClassLoader的子類時候,我們常見的會有兩種做法:

  • 方式一:重寫loadClass()方法
  • 方式二:重寫findClass()方法

對比

這兩種方法本質上差不多,畢竟loadClass()也會調用findClass(),但是從邏輯上講我們最好不要直接修改loadClass()的內部邏輯。建議的做法是只在findClass()里重寫自定義類的加載方法,根據參數指定類的名字,返回對應的Class對象的引用。

loadClass()這個方法是實現雙親委派模型邏輯的地方,擅自修改這個方法會導致模型被破壞,容易造成問題。同時,也避免了自己重寫loadClass()方法的過程中必須寫雙親委托的重復代碼,從代碼的復用性來看,不直接修改這個方法始終是比較好的選擇。

當編寫好自定義類加載器后,便可以在程序中調用loadClass()方法來實現類加載操作。

ClassLoader類中loadClass()方法源碼,可以看出內部調用了findClass()方法

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    // 通過類的全限定名稱(加包名)調用findClass方法查找類
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

說明

  • 自定義加載器的父類加載器是系統類加載器
  • JVM中的所有類加載都會使用java.lang.ClassLoader.loadClass(String)接口(自定義類加載器并重寫java.lang.ClassLoader.loadClass(String)接口的除外),連JDK的核心類庫也不能例外。

5、通過重寫findClass()方法實現自定義類加載器

①、自定義類com.lc.Demo

package com.lc;

/**
 * @author liuchao
 * @date 2023/3/25
 */
public class Demo {

    public void hello() {
        System.out.println("myClassLoader hello");
    }
}

通過javac Demo 將 類編譯為Demo.class 文件放入/Users/liuchao/Desktop/文件夾下

②、自定義類加載器MyClassLoader

package com.lc;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * 自定義ClassLoader
 *
 * @author liuchao
 * @date 2023/3/25
 */
public class MyClassLoader extends ClassLoader {
    /**
     * 負責加載的類所屬目錄
     */
    public String classPath;

    /**
     * 包名
     */
    public String packageName;

    public MyClassLoader(String packageName, String classPath) {
        this.packageName = packageName;
        this.classPath = classPath;
    }

    @Override
    protected Class<?> findClass(String name) {
        //獲取字節碼完整路徑
        String fileName = classPath + name + ".class";
        ByteArrayOutputStream baos = null;
        BufferedInputStream bis = null;
        try {
            //獲取輸入流
            bis = new BufferedInputStream(new FileInputStream(fileName));
            //獲取輸出流
            baos = new ByteArrayOutputStream();

            //讀取數據寫入輸出流
            int len;
            byte[] data = new byte[1024];
            while ((len = bis.read(data)) != -1) {
                baos.write(data, 0, len);
            }
            //獲取內存中完整的字節素組數據
            byte[] byteCodes = baos.toByteArray();
            //通過調用defineClass 方法將字節數組轉換為class的實例
            return defineClass(packageName + "." + name, byteCodes, 0, byteCodes.length);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (null != baos) {
                    baos.close();
                }
                if (null != bis) {
                    bis.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

③、寫測試代碼MyClassLoaderTest

package com.lc;

import java.lang.reflect.Method;

/**
 * 測試
 *
 * @author liuchao
 * @date 2023/3/25
 */
public class MyClassLoaderTest {
    public static void mAIn(String[] args) throws Exception {
        MyClassLoader classLoader = new MyClassLoader("com.lc", "/Users/liuchao/Desktop/");

        Class clazz = classLoader.loadClass("Demo");

        System.out.println("當前Demo類的加載器為:" + clazz.getClassLoader().getClass().getName());
        System.out.println("當前Demo類的加載器的父類加載器為:" + clazz.getClassLoader().getClass().getClassLoader().getClass().getName());

        Method method = clazz.getMethod("hello");

        Object obj = clazz.newInstance();

        method.setAccessible(Boolean.TRUE);
        method.invoke(obj);
    }
}

執行效果:

 

至此我們的自定義類加載器就算完成了

6、自定義類加載器實現Java license

我們改造第5節的代碼

①、增加字節碼加密方法


    @Test
    public void test01() {
        //秘鑰
        String secretKey = "12asdfwe23123212";
        String className = "Demo";
        //原字節碼
        byte[] byteCodes = FileUtil.readBytes("/Users/liuchao/Desktop/" + className + ".class");
        //加密后的字節碼
        byte[] encryptByteCodes = SecureUtil.aes(secretKey.getBytes()).encrypt(byteCodes);
        //加密后的字節碼重新生成新的class文件放入 temp目錄下
        FileUtil.writeBytes(encryptByteCodes, "/Users/liuchao/Desktop/temp/" + className + ".class");


    }

②、改造MyClassLoader類

package com.lc;

import cn.hutool.crypto.SecureUtil;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * 自定義ClassLoader
 *
 * @author liuchao
 * @date 2023/3/25
 */
public class MyClassLoader extends ClassLoader {
    /**
     * 負責加載的類所屬目錄
     */
    public String classPath;

    /**
     * 包名
     */
    public String packageName;

    /**
     * 秘鑰
     */
    public String secretKey;

    public MyClassLoader(String secretKey, String packageName, String classPath) {
        this.secretKey = secretKey;
        this.packageName = packageName;
        this.classPath = classPath;
    }

    @Override
    protected Class<?> findClass(String name) {
        //獲取字節碼完整路徑
        String fileName = classPath + name + ".class";
        ByteArrayOutputStream baos = null;
        BufferedInputStream bis = null;
        try {
            //獲取輸入流
            bis = new BufferedInputStream(new FileInputStream(fileName));
            //獲取輸出流
            baos = new ByteArrayOutputStream();

            //讀取數據寫入輸出流
            int len;
            byte[] data = new byte[1024];
            while ((len = bis.read(data)) != -1) {
                baos.write(data, 0, len);
            }
            //獲取內存中完整的字節素組數據 (加密的)
            byte[] encryptByteCodes = baos.toByteArray();

            //解密返回
            byte[] byteCodes = SecureUtil.aes(secretKey.getBytes()).decrypt(encryptByteCodes);
            //通過調用defineClass 方法將字節數組轉換為class的實例
            return defineClass(packageName + "." + name, byteCodes, 0, byteCodes.length);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (null != baos) {
                    baos.close();
                }
                if (null != bis) {
                    bis.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

③、調用測試

package com.lc;

import java.lang.reflect.Method;

/**
 * 測試
 *
 * @author liuchao
 * @date 2023/3/25
 */
public class MyClassLoaderTest {
    public static void main(String[] args) throws Exception {
        String secretKey = "12asdfwe23123212";
        MyClassLoader classLoader = new MyClassLoader(secretKey, "com.lc", "/Users/liuchao/Desktop/temp/");

        Class clazz = classLoader.loadClass("Demo");

        System.out.println("當前Demo類的加載器為:" + clazz.getClassLoader().getClass().getName());
        System.out.println("當前Demo類的加載器的父類加載器為:" + clazz.getClassLoader().getClass().getClassLoader().getClass().getName());

        Method method = clazz.getMethod("hello");

        Object obj = clazz.newInstance();

        method.setAccessible(Boolean.TRUE);
        method.invoke(obj);
    }
}

效果:

 

在未解密之前,想打開加密后的類,效果:

 

當然現實中,需要有秘鑰過期時間的限制,大家可以在此基礎上繼續擴展。

分享到:
標簽:Java
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定