自定义类加载器加载加密jar包,使用Reflections扫描自定义加载器加载的Class,RSA加密解密签名AES密文,AES加密解密文件

本文主要是介绍自定义类加载器加载加密jar包,使用Reflections扫描自定义加载器加载的Class,RSA加密解密签名AES密文,AES加密解密文件,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

为什么要做这个工作:

    游戏私服是游戏人最讨厌的一件事,而游戏私服基本上都是内部人员把内部的自启服务器泄露出去,我们现在做的就是,内部发行的服务器版本是加密后的二进制文件,必须用给定的RSA秘钥才能解密二进制文件,然后 再使用自定义类加载器进行加载,在整个过程中都是流操作,不会生成class文件,就能防止内部发行的服务器被拷贝。这样并不能完全防止服务器泄露,如果有心人拿到秘钥,拿到加密后的class,自己写代码解密,也不能完全禁止,但是使用秘钥能在服务器删除秘钥,设置有效期,能在一定程度上加大服务器泄露的成本,本身安全就不是绝对的,能做的只是加大泄露的难度。

   开始工作之前一定要对类加载器的工作机制有深入的理解,启动类加载器,扩展类加载器,系统类加载器,另外还有上下文类加载器

   关于类加载器参考:https://blog.csdn.net/justloveyou_/article/details/72217806

   关于上下文类加载器参考:https://blog.csdn.net/justloveyou_/article/details/72231425

   启动完成自定义加载器,有时候我们想通过Reflections去扫描class,如下所示:

Reflections reflections = new Reflections(packagePath);

  如果不做一些特殊处理,自定义类加载器的加载的class是不会被扫描到的。

  接下来做三个工作:1定义类加载器  2定义启动类  3定义自己的容器扫描路径。

1. 定义自己的类加载器

   自定义类加载器中缓存的是使用AES加密后文件的二进制字节数据,在解密,加载类过程中不会生成class文件,防止jar包外泄。

package earth.pack.container;import earth.support.RSAUtils;
import java.security.Key;
import java.util.HashMap;
import java.util.Set;/*** @author zhangjiaqi* @desc: 容器的类加载器* @time 2021/4/9 11:18*/
public class ContainerLoader extends ClassLoader {/*** 所有加密后的文件的二进制流*/private HashMap<String, byte[]> allFiles = new HashMap<String, byte[]>();/* 密文key */private Key key = null;public ContainerLoader(ClassLoader parent, Key key, HashMap<String, byte[]> allFiles) {// 这里就算是不设置 也会把自动设置父类加载器为系统加载器super(parent);this.key = key;this.allFiles = allFiles;}/*** 重写findClass方法** @param name 是我们这个类的全路径* @return* @throws ClassNotFoundException*/@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {Class log = null;// 获取该class文件字节码数组byte[] classData = getData(name);if (classData != null) {// 将class的字节码数组转换成Class类的实例log = defineClass(name, classData, 0, classData.length);}return log;}/*** 获取解密后的二进制流** @param name* @return*/public byte[] getData(String name) {byte[] bytes = null;if (allFiles.containsKey(name)) {byte[] encryptBytes = allFiles.get(name);try {bytes = RSAUtils.decryptByAES(encryptBytes, key);} catch (Exception e) {e.printStackTrace();}} else {System.out.println("error: " + name);}return bytes;}
}

2 启动类加载器

  我们这里是通过http从服务器取AES密文,当然整个过程都是通过RSA加密传输的。

package earth.pack.container;import com.alibaba.fastjson.JSONObject;
import earth.config.ContainerConfig;
import earth.enums.EnvParamName;
import earth.pack.container.reflection.ContainerDir;
import earth.pack.container.reflection.ContainerFile;
import earth.pack.container.reflection.ContainerHandler;
import earth.pack.container.reflection.ContainerType;
import earth.processor.IBizHandler;
import earth.support.HttpClient;
import earth.support.RSAUtils;
import earth.utils.FileUtils;
import org.apache.poi.ss.formula.functions.T;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.scanners.TypeAnnotationsScanner;
import org.reflections.scanners.TypeElementsScanner;
import org.reflections.vfs.Vfs;import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.Key;
import java.util.*;/*** @author zhangjiaqi* @desc: 加密容器类 全程流操作 不会中途生成文件* @time 2021/4/9 11:18*/
public class ContainerApp {private static ContainerConfig config = null;public static final String USER_DIR = System.getProperty("user.dir");private static final String encryptPath = USER_DIR + File.separator + "encrypt.tar.gz";public static final String CONTAINER_FILE = "CONTAINER_FILE";public static final String CONTAINER_TYPE = "container";private static ContainerLoader loader = null;private ContainerApp() {}public static void main(String[] args) {if (args.length != 2) {System.out.println("args size error !!!");return;}// 初始化配置initConf();// 生成对象为了调用父类加载器ContainerApp app = new ContainerApp();// 解压缩二进制流HashMap<String, byte[]> allFiles = unTarFile();// 网络取RSA数据String data = getRsa();// 分析密文keyKey key = parseAES(data);loader = new ContainerLoader(app.getClass().getClassLoader(), key, allFiles);app.startContainer(allFiles, args[0], args[1]);}public static ContainerLoader getLoader() {return loader;}/*** 初始化配置*/public static void initConf() {String cfgText = null;try {cfgText = FileUtils.getStringFromFile(System.getProperty(CONTAINER_FILE));} catch (Exception e) {e.printStackTrace();}config = JSONObject.parseObject(cfgText, ContainerConfig.class);}/*** 解压缩文件 转换为二进制流** @return*/public static HashMap<String, byte[]> unTarFile() {// 解压HashMap<String, byte[]> allFiles = null;try {File encryptFile = new File(encryptPath);if (!encryptFile.exists()) {System.out.println("encrpt file is null, path:" + encryptPath);System.exit(1);}allFiles = FileUtils.unTarGz(encryptFile);} catch (Exception e) {e.printStackTrace();System.exit(1);}return allFiles;}/*** 开始容器** @param allFiles    加密后的文件二进制流* @param mainClass   游戏服启动类* @param startMethod 游戏服启动方法*/private void startContainer(HashMap<String, byte[]> allFiles, String mainClass, String startMethod) {try {// 解析后的 class二进制流List<Vfs.File> fileList = new ArrayList<>();for (String name : allFiles.keySet()) {byte[] bytes = loader.getData(name);Vfs.File file = new ContainerFile(name, name, bytes);fileList.add(file);}// 自定义容器一个加载类型,Reflections构造方法用ContainerType type = new ContainerType(new ContainerDir(fileList));Vfs.addDefaultURLTypes(type);Class<?> clazz = loader.loadClass(mainClass);Constructor constructor = clazz.getDeclaredConstructor();constructor.setAccessible(true);Method method = clazz.getDeclaredMethod(startMethod);method.setAccessible(true);method.invoke(constructor.newInstance());} catch (Exception e) {e.printStackTrace();}}/*** 分析AES** @param data* @return*/public static Key parseAES(String data) {Key key = null;try {JSONObject resp = JSONObject.parseObject(data);String dataEncrypt = resp.getJSONObject("data").getString("data");String signS = resp.getJSONObject("data").getString("sign");String dataDecrrypt = RSAUtils.decrypt(dataEncrypt, config.getPrivate_key());boolean ok = RSAUtils.verify(dataEncrypt, signS, config.getPublic_key());if (!ok) {System.out.println("sign verify fail!!!");return null;}// 只用16位密文if (dataDecrrypt.length() > 16) {dataDecrrypt = dataDecrrypt.substring(0, 16);}key = new SecretKeySpec(dataDecrrypt.getBytes(), "AES");return key;} catch (Exception e) {e.printStackTrace();}return null;}/*** 获取RSA秘钥** @return*/public static String getRsa() {String cfgText = null;try {cfgText = FileUtils.getStringFromFile(System.getProperty(EnvParamName.CONFIG_FILE.name()));} catch (Exception e) {e.printStackTrace();return null;}JSONObject startJSON = JSONObject.parseObject(cfgText);String gameName = startJSON.getString("game").toUpperCase();String serverId = startJSON.getString("id");String version = startJSON.getString("platform").toUpperCase();String rtn = null;try {String ptext = "gameName" + gameName + "serverId" + serverId + "version" + version;String sign = RSAUtils.sign(ptext, config.getPrivate_key());JSONObject j = new JSONObject();j.put("serverId", serverId);j.put("version", version);String data = RSAUtils.encrypt(j.toJSONString(), config.getPublic_key());Map<String, Object> map = new HashMap<>();map.put("gameName", gameName);map.put("data", data);map.put("sign", sign);rtn = HttpClient.doPost(config.getDecrypt_url(), map);} catch (Exception e) {e.printStackTrace();}return rtn;}/*** 生成一个 容器的类扫描地址** @return*/private static URL createContainerURL() {URL u = null;try {u = new URL(ContainerApp.CONTAINER_TYPE, null, -1, "", new ContainerHandler());} catch (MalformedURLException e) {e.printStackTrace();}return u;}/*** 扫描类文件** @param packagePath* @param clazz* @return*/public static <T> Set<Class<? extends T>> scanClass(String packagePath, Class<T> clazz) {Reflections reflections = null;// 容器启动 添加容器扫描地址if (loader != null) {reflections = new Reflections(packagePath, createContainerURL(), loader);} else {reflections = new Reflections(packagePath);}return reflections.getSubTypesOf(clazz);}/*** 扫描注解文件** @param packagePath* @param annotation* @return*/public static Set<Class<?>> scanAnnotation(String packagePath, Class<? extends Annotation> annotation) {Reflections reflections = null;// 容器启动 添加容器扫描地址if (loader != null) {reflections = new Reflections(packagePath, createContainerURL(), loader);} else {reflections = new Reflections(packagePath);}return reflections.getTypesAnnotatedWith(annotation);}/*** 给定名字扫描class** @param name* @throws ClassNotFoundException*/public static Class<?> forNameClass(String name) throws ClassNotFoundException {Class<?> clazz = null;if (loader == null) {clazz = Class.forName(name);} else {// 使用给定加载器加载clazz = Class.forName(name, true, loader);}return clazz;}
}

 RSA常用方法

package earth.support;import org.apache.commons.codec.binary.Base64;import javax.crypto.Cipher;
import java.io.*;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** RSA常用方法** @date: 2021/4/1*/
public class RSAUtils {/*** 随机生成密钥对* @throws NoSuchAlgorithmException*/public static String[] genKeyPair()   {// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象KeyPairGenerator keyPairGen = null;try {keyPairGen = KeyPairGenerator.getInstance("RSA");} catch (NoSuchAlgorithmException e) {e.printStackTrace();return null;}// 初始化密钥对生成器,密钥大小为96-1024位keyPairGen.initialize(1024,new SecureRandom());// 生成一个密钥对,保存在keyPair中KeyPair keyPair = keyPairGen.generateKeyPair();RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();   // 得到私钥RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  // 得到公钥String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));// 得到私钥字符串String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));String[] rs = new String[]{publicKeyString, privateKeyString};return rs;}public static String encrypt( String str, String publicKey ) throws Exception{return encrypt(str.getBytes("UTF-8"), publicKey);}/*** 公钥加密* @param data* @param publicKey* @return* @throws Exception*/public static String encrypt(byte[] data, String publicKey ) throws Exception{//base64编码的公钥byte[] decoded = Base64.decodeBase64(publicKey);RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));//RSA加密Cipher cipher = Cipher.getInstance("RSA");cipher.init(Cipher.ENCRYPT_MODE, pubKey);String outStr = Base64.encodeBase64String(cipher.doFinal(data));return outStr;}public static String decrypt(String str, String privateKey) throws Exception{//64位解码加密后的字符串byte[] inputByte = Base64.decodeBase64(str.getBytes("UTF-8"));return decrypt(inputByte, privateKey);}/*** 私钥解密* @param data* @param privateKey* @return* @throws Exception*/public static String decrypt(byte[] data, String privateKey) throws Exception{//base64编码的私钥byte[] decoded = Base64.decodeBase64(privateKey);RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));//RSA解密Cipher cipher = Cipher.getInstance("RSA");cipher.init(Cipher.DECRYPT_MODE, priKey);String outStr = new String(cipher.doFinal(data));return outStr;}/*** 私钥签名* @param plainText* @param priKey* @return* @throws Exception*/public static String sign(String plainText, String priKey) throws Exception {byte[] decoded = Base64.decodeBase64(priKey);RSAPrivateKey privateKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));Signature privateSignature = Signature.getInstance("SHA256withRSA");privateSignature.initSign(privateKey);privateSignature.update(plainText.getBytes("UTF-8"));byte[] signature = privateSignature.sign();return Base64.encodeBase64String(signature);}/*** 公钥校验* @param plainText* @param signature* @param pubKey* @return* @throws Exception*/public static boolean verify(String plainText, String signature, String pubKey) throws Exception {byte[] decoded = Base64.decodeBase64(pubKey);RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));Signature publicSignature = Signature.getInstance("SHA256withRSA");publicSignature.initVerify(publicKey);publicSignature.update(plainText.getBytes("UTF-8"));byte[] signatureBytes = Base64.decodeBase64(signature);return publicSignature.verify(signatureBytes);}/**私钥加密** @param str* @param privateKey* @return* @throws Exception*/public static String encryptPri( String str, String privateKey ) throws Exception{//base64编码的公钥byte[] decoded = Base64.decodeBase64(privateKey);RSAPrivateKey pubKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));//RSA加密Cipher cipher = Cipher.getInstance("RSA");cipher.init(Cipher.ENCRYPT_MODE, pubKey);String outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes("UTF-8")));return outStr;}/*** 公钥解密* @param str* @param publicKey* @return* @throws Exception*/public static String decryptPub(String str, String publicKey) throws Exception{//64位解码加密后的字符串byte[] inputByte = Base64.decodeBase64(str.getBytes("UTF-8"));//base64编码的私钥byte[] decoded = Base64.decodeBase64(publicKey);RSAPublicKey priKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));//RSA解密Cipher cipher = Cipher.getInstance("RSA");cipher.init(Cipher.DECRYPT_MODE, priKey);String outStr = new String(cipher.doFinal(inputByte));return outStr;}/*** RSA算法对文件加密* @param file* @param saveFileName* @param key*/public static void encryptByRSA(File file, String saveFileName, Key key) {try {File saveFile = new File(saveFileName);if (!saveFile.exists()) saveFile.createNewFile();DataOutputStream out = new DataOutputStream(new FileOutputStream(saveFile));InputStream in = new FileInputStream(file);Cipher cipher = Cipher.getInstance("AES");cipher.init(Cipher.ENCRYPT_MODE, key);crypt(in, out, cipher);in.close();out.close();} catch (GeneralSecurityException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}/*** 对数据块加密* @param in* @param out* @param cipher*/public static void crypt(InputStream in, OutputStream out, Cipher cipher) {try {int blockSize = cipher.getBlockSize();int outputSize = cipher.getOutputSize(blockSize);byte[] inBytes = new byte[blockSize];byte[] outBytes = new byte[outputSize];int inLength = 0;boolean next = true;while (next) {inLength = in.read(inBytes);if (inLength == blockSize) {int outLength = cipher.update(inBytes, 0, blockSize, outBytes);out.write(outBytes, 0, outLength);} else {next = false;}}if (inLength > 0) {outBytes = cipher.doFinal(inBytes, 0, inLength);} else {outBytes = cipher.doFinal();}out.write(outBytes);} catch (Exception e) {e.printStackTrace();}}/*** AES算法对文件解密 使用二进制数据流* @param encryptBytes* @param key* @return* @throws Exception*/public static byte[] decryptByAES(byte[] encryptBytes, Key key) throws Exception {try {DataInputStream in = new DataInputStream(new ByteArrayInputStream(encryptBytes));Cipher cipher = Cipher.getInstance("AES");cipher.init(Cipher.DECRYPT_MODE, key);byte[] bytes = decrypt(in, cipher);in.close();return bytes;} catch (GeneralSecurityException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return null;}/*** 对数据块解密 转换成二进制流* @param in* @param cipher* @return* @throws IOException* @throws GeneralSecurityException*/public static byte[] decrypt(InputStream in, Cipher cipher) throws IOException, GeneralSecurityException {int blockSize = cipher.getBlockSize();int outputSize = cipher.getOutputSize(blockSize);byte[] inBytes = new byte[blockSize];byte[] outBytes = new byte[outputSize];byte[] rstBytes = new byte[outputSize];try {int inLength = 0;int cpLength = 0;boolean next = true;while (next) {inLength = in.read(inBytes);if (inLength == blockSize) {int outLength = cipher.update(inBytes, 0, blockSize, outBytes);// 扩容if(cpLength + outLength > rstBytes.length){rstBytes = Arrays.copyOf(rstBytes, cpLength + outLength);}System.arraycopy(outBytes, 0, rstBytes, cpLength, outLength);cpLength += outLength;} else {next = false;}}if (inLength > 0) {outBytes = cipher.doFinal(inBytes, 0, inLength);} else {outBytes = cipher.doFinal();}// 扩容if(cpLength + outBytes.length > rstBytes.length){rstBytes = Arrays.copyOf(rstBytes, cpLength + outBytes.length);}System.arraycopy(outBytes, 0, rstBytes, cpLength, outBytes.length);}catch (Exception e){e.printStackTrace();}return rstBytes;}}

3 使用Reflections扫描容器自定义加载器中的class

        Reflections reflections = null;// 容器启动 添加容器扫描地址if(ContainerApp.getLoader() != null){reflections = new Reflections(packagePath, ContainerApp.createContainerURL(), ContainerApp.getLoader());}else{reflections = new Reflections(packagePath);}Set<Class<? extends IBizHandler>> classes = reflections.getSubTypesOf(IBizHandler.class);

     这里我们需要注意,如果只 是这样使用 Reflections reflections = new Reflections(packagePath);的话,是无法扫描到容器中加载的class的,这里看Reflections的构造函数片段,传入不同的参数的不同加载逻辑

while(var7.hasNext()) {param = var7.next();if (param instanceof String) {builder.addUrls(ClasspathHelper.forPackage((String)param, classLoaders));filter.includePackage(new String[]{(String)param});} else if (param instanceof Class) {if (Scanner.class.isAssignableFrom((Class)param)) {try {builder.addScanners((Scanner)((Class)param).newInstance());} catch (Exception var11) {}}builder.addUrls(ClasspathHelper.forClass((Class)param, classLoaders));filter.includePackage((Class)param);} else if (param instanceof Scanner) {scanners.add((Scanner)param);} else if (param instanceof URL) {builder.addUrls((URL)param);} else if (!(param instanceof ClassLoader)) {if (param instanceof Predicate) {filter.add((Predicate)param);} else if (param instanceof ExecutorService) {builder.setExecutorService((ExecutorService)param);} else if (Reflections.log != null) {throw new ReflectionsException("could not use param " + param);}}}

     对于只传入String类型的path,会进行如下加载

public static Collection<URL> forResource(String resourceName, ClassLoader... classLoaders) {List<URL> result = new ArrayList();ClassLoader[] loaders = classLoaders(classLoaders);ClassLoader[] var4 = loaders;int var5 = loaders.length;for(int var6 = 0; var6 < var5; ++var6) {ClassLoader classLoader = var4[var6];try {Enumeration urls = classLoader.getResources(resourceName);while(urls.hasMoreElements()) {URL url = (URL)urls.nextElement();int index = url.toExternalForm().lastIndexOf(resourceName);if (index != -1) {result.add(new URL(url.toExternalForm().substring(0, index)));} else {result.add(url);}}} catch (IOException var11) {if (Reflections.log != null) {Reflections.log.error("error getting resources for " + resourceName, var11);}}}return distinctUrls(result);}

调用了classloader的getresource方法去扫描class的路径,这个classloader哪来的?

    public static ClassLoader[] classLoaders(ClassLoader... classLoaders) {if (classLoaders != null && classLoaders.length != 0) {return classLoaders;} else {ClassLoader contextClassLoader = contextClassLoader();ClassLoader staticClassLoader = staticClassLoader();return contextClassLoader != null ? (staticClassLoader != null && contextClassLoader != staticClassLoader ? new ClassLoader[]{contextClassLoader, staticClassLoader} : new ClassLoader[]{contextClassLoader}) : new ClassLoader[0];}}

    这里可以看到,如果在构造函数传入了classloader就会使用自定的类加载器,如果没传入就会使用 上下文类加载器,而这个上下文类加载器默认是系统类加载器(不要想着去改上下文类加载器的默认加载器,会导致很多问题,spi依赖这个实现自己的功能,spring,jdbc什么的好多框架都会不好使),是我们自定义类加载器的父加载器,所以自定义加载器的路径就会扫描不到。到这里,你可能会想着在构造方法中传入自定义的类加载器,然后实现自定义类加载器的getResources()方法,提供一个URL对象,(这是一个思路,我尝试过这么做,但是ClassLoader的getResources()方法会被其他java类默认调用,并调用生成的URL中的一些connect和stream相关的方法,而这些方法对我们的需求来说并不是必须要实现的,实现起来也比较困难)

  我们看到,在Reflections的构造函数中还可以直接传入URL对象,我们可以以此为切入点进行分析,这个传入的URL是在什么时候被调用的:

 protected void scan() {for (final URL url : configuration.getUrls()) {try {if (executorService != null) {futures.add(executorService.submit(new Runnable() {public void run() {if (log != null && log.isDebugEnabled()) log.debug("[" + Thread.currentThread().toString() + "] scanning " + url);scan(url);}}));} else {scan(url);}scannedUrls++;} catch (ReflectionsException e) {if (log != null && log.isWarnEnabled()) log.warn("could not create Vfs.Dir from url. ignoring the exception and continuing", e);}}}

构造函数中调用scan()方法扫描所有路径,去扫描里面的class文件

 

对于每个URLscan做如下处理: 1是加载URL,2是扫描File

    protected void scan(URL url) {Vfs.Dir dir = Vfs.fromURL(url);try {for (final Vfs.File file : dir.getFiles()) {// scan if inputs filter accepts file relative path or fqnPredicate<String> inputsFilter = configuration.getInputsFilter();String path = file.getRelativePath();String fqn = path.replace('/', '.');if (inputsFilter == null || inputsFilter.apply(path) || inputsFilter.apply(fqn)) {Object classObject = null;for (Scanner scanner : configuration.getScanners()) {try {if (scanner.acceptsInput(path) || scanner.acceptResult(fqn)) {classObject = scanner.scan(file, classObject);}} catch (Exception e) {if (log != null && log.isDebugEnabled())log.debug("could not scan file " + file.getRelativePath() + " in url " + url.toExternalForm() + " with scanner " + scanner.getClass().getSimpleName(), e.getMessage());}}}}} finally {dir.close();}}

可以看到 Vfs.fromURL(url) 这句是加载URL路径的关键:

   private static List<UrlType> defaultUrlTypes = Lists.<UrlType>newArrayList(DefaultUrlTypes.values());public static Dir fromURL(final URL url) {return fromURL(url, defaultUrlTypes);}/** tries to create a Dir from the given url, using the given urlTypes*/public static Dir fromURL(final URL url, final List<UrlType> urlTypes) {for (UrlType type : urlTypes) {try {if (type.matches(url)) {Dir dir = type.createDir(url);if (dir != null) return dir;}} catch (Throwable e) {if (Reflections.log != null) {Reflections.log.warn("could not create Dir using " + type + " from url " + url.toExternalForm() + ". skipping.", e);}}}throw new ReflectionsException("could not create Vfs.Dir from url, no matching UrlType was found [" + url.toExternalForm() + "]\n" +"either use fromURL(final URL url, final List<UrlType> urlTypes) or " +"use the static setDefaultURLTypes(final List<UrlType> urlTypes) or addDefaultURLTypes(UrlType urlType) " +"with your specialized UrlType.");}

内部实现遍历了默认的 URLTypes的列表,所以在启动自定义类加载器之前要自定义一个加载类型,具体如下:

            // 自定义容器一个加载类型,Reflections构造方法用ContainerType type = new ContainerType(new ContainerDir(fileList));Vfs.addDefaultURLTypes(type);

这里面涉及的一些接口都要自己实现:

package earth.pack.container.reflection;import org.reflections.vfs.Vfs;
import java.util.ArrayList;
import java.util.List;/*** @desc: 虚拟目录* @author zhangjiaqi* @time 2021/4/12 20:59*/
public class ContainerDir implements Vfs.Dir {List<Vfs.File> list = new ArrayList<>();public ContainerDir(List<Vfs.File> list) {this.list = list;}@Overridepublic String getPath() {return null;}@Overridepublic Iterable<Vfs.File> getFiles() {return list;}@Overridepublic void close() {}
}
package earth.pack.container.reflection;import org.reflections.vfs.Vfs;import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;/*** @desc: 虚拟文件* @author zhangjiaqi* @time 2021/4/12 20:59*/
public class ContainerFile implements Vfs.File{String name;String path;// 这里不能传 InputStream过来,扫描class时,多次读取流的读取下标不会重置(查这个bug查了一整天)byte[] bytes;public ContainerFile(String name, String path, byte[] bytes){this.name = name;this.path = path;this.bytes = bytes;}@Overridepublic String getName() {return name;}@Overridepublic String getRelativePath() {return path;}@Overridepublic InputStream openInputStream() throws IOException {return new ByteArrayInputStream(bytes);}
}
package earth.pack.container.reflection;import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.jar.JarFile;/*** @desc: 虚拟文件流处理* @author zhangjiaqi* @time 2021/4/12 21:00*/
public class ContainerHandler extends URLStreamHandler {@Overrideprotected URLConnection openConnection(URL u) throws IOException {return new JarURLConnection(u) {@Overridepublic JarFile getJarFile() throws IOException {return null;}@Overridepublic void connect() throws IOException {}};}
}
package earth.pack.container.reflection;import earth.pack.container.ContainerApp;
import org.reflections.vfs.Vfs;import java.net.URL;/*** @desc: 容器扫描地址* @author zhangjiaqi* @time 2021/4/12 21:00*/
public class ContainerType implements Vfs.UrlType {Vfs.Dir dir;public ContainerType(Vfs.Dir dir){this.dir = dir;}@Overridepublic boolean matches(URL url) throws Exception {return url.getProtocol().indexOf(ContainerApp.CONTAINER_TYPE) != -1;}@Overridepublic Vfs.Dir createDir(URL url) throws Exception {return dir;}
}

这些实现的关键是 ContainerDir 类里面的 List<Vfs.File> list,这是解密后的二进制流,而Reflections中的scan正是通过File的openStream()方法获取class的二进制流,去定义Class文件。

    public ClassFile getOfCreateClassObject(File file) {InputStream inputStream = null;ClassFile var4;try {inputStream = file.openInputStream();DataInputStream dis = new DataInputStream(new BufferedInputStream(inputStream));var4 = new ClassFile(dis);} catch (IOException var8) {throw new ReflectionsException("could not create class file from " + file.getName(), var8);} finally {Utils.close(inputStream);}return var4;}

至此,一个自定义的类加载器已经实现,也通过了解底层,实现了自定义的URL,实现Reflections扫描自定义的类加载器中加载的class。

 

2021.04.30 又发现一个问题:

如果使用fastjson  需要设置默认类加载器:

ParserConfig.getGlobalInstance().setDefaultClassLoader(loader);

这篇关于自定义类加载器加载加密jar包,使用Reflections扫描自定义加载器加载的Class,RSA加密解密签名AES密文,AES加密解密文件的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1093137

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

git使用的说明总结

Git使用说明 下载安装(下载地址) macOS: Git - Downloading macOS Windows: Git - Downloading Windows Linux/Unix: Git (git-scm.com) 创建新仓库 本地创建新仓库:创建新文件夹,进入文件夹目录,执行指令 git init ,用以创建新的git 克隆仓库 执行指令用以创建一个本地仓库的

【北交大信息所AI-Max2】使用方法

BJTU信息所集群AI_MAX2使用方法 使用的前提是预约到相应的算力卡,拥有登录权限的账号密码,一般为导师组共用一个。 有浏览器、ssh工具就可以。 1.新建集群Terminal 浏览器登陆10.126.62.75 (如果是1集群把75改成66) 交互式开发 执行器选Terminal 密码随便设一个(需记住) 工作空间:私有数据、全部文件 加速器选GeForce_RTX_2080_Ti