为什么要做这个工作:
游戏私服是游戏人最讨厌的一件事,而游戏私服基本上都是内部人员把内部的自启服务器泄露出去,我们现在做的就是,内部发行的服务器版本是加密后的二进制文件,必须用给定的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 allFiles = new HashMap();
/* 密文key */
private Key key = null;
public ContainerLoader(ClassLoader parent, Key key, HashMap allFiles) {
// 这里就算是不设置 也会把自动设置父类加载器为系统加载器
super(parent);
this.key = key;
this.allFiles = allFiles;
}
/**
* 重写findClass方法
*
* @param name 是我们这个类的全路径
* @return
* @throws ClassNotFoundException
*/
@Override
protected 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 allFiles = unTarFile();
// 网络取RSA数据
String data = getRsa();
// 分析密文key
Key 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 unTarFile() {
// 解压
HashMap 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 allFiles, String mainClass, String startMethod) {
try {
// 解析后的 class二进制流
List 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 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 Set> scanClass(String packagePath, Class 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> 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> 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 forResource(String resourceName, ClassLoader... classLoaders) {
List 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 fqn
Predicate 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 defaultUrlTypes = Lists.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 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 urlTypes) or " +
"use the static setDefaultURLTypes(final List 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 list = new ArrayList<>();
public ContainerDir(List list) {
this.list = list;
}
@Override
public String getPath() {
return null;
}
@Override
public Iterable getFiles() {
return list;
}
@Override
public 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;
}
@Override
public String getName() {
return name;
}
@Override
public String getRelativePath() {
return path;
}
@Override
public 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 {
@Override
protected URLConnection openConnection(URL u) throws IOException {
return new JarURLConnection(u) {
@Override
public JarFile getJarFile() throws IOException {
return null;
}
@Override
public 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;
}
@Override
public boolean matches(URL url) throws Exception {
return url.getProtocol().indexOf(ContainerApp.CONTAINER_TYPE) != -1;
}
@Override
public Vfs.Dir createDir(URL url) throws Exception {
return dir;
}
}
这些实现的关键是 ContainerDir 类里面的 List
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);