有个第三方工具类,不支持多例模式。但是又不能直接改第三方工具类的代码,因此可以通过反射破坏第三方工具类的单例。
第三方工具类反编译如下
可以看到构造函数进行了私有化,不允许外部new,只能通过newInstance进行实例化。并且newInstance是单例的。
DataHandler.class
public class DataHandler {
private static volatile DataHandler INSTANCE;
private final byte[] encryptKeys;
private final String publicKeys;
private final String privateKeys;
private final String appID;
private final String secret;
private String version = "2.0.0";
private boolean skipVerify = false;
private DataHandler(String appID, String secret, String publicKey, String privateKey) throws IOException {
//.....
}
public static DataHandler newInstance(String appID, String secret, String publicKey, String privateKey) throws IOException {
if (INSTANCE == null) {
Class var4 = DataHandler.class;
synchronized(DataHandler.class) {
if (INSTANCE == null) {
INSTANCE = new DataHandler(appID, secret, publicKey, privateKey);
}
}
}
return INSTANCE;
}
}
通过反射破坏单例模式
import java.lang.reflect.Constructor;
public class DataHandlerFactory {
public static DataHandler createNewInstance(String appId, String appSecret, String publicKey, String privateKey) {
try {
// 获取DataHandler类的构造函数
Constructor<DataHandler> constructor = DataHandler.class.getDeclaredConstructor(String.class, String.class, String.class, String.class);
constructor.setAccessible(true);
// 通过反射创建新的实例
return constructor.newInstance(appId, appSecret, publicKey, privateKey);
} catch (Exception e) {
return null;
}
}
}
使用方法
//原本使用第三方工具类的方式,默认单例模式
DataHandler dataHandler = DataHandler.newInstance(appId, appSecret, publicKey, privateKey);
//修改后,允许多例。createNewInstance方法如果抛出异常,那么是有可能为null
DataHandler dataHandler = DataHandlerFactory.createNewInstance(appId, appSecret, publicKey, privateKey);
通过缓存构造函数、缓存重复的实例进一步提升性能和减少反射所带来的性能开销
优化DataHandlerFactory类
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Constructor;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
public final class DataHandlerFactory {
// 使用线程安全的 ConcurrentHashMap 存储实例
public static final ConcurrentHashMap<String, DataHandler> instanceCache = new ConcurrentHashMap<>();
// 缓存构造函数,第一次通过反射调用,性能会比较低,但是第一次过后,都是从缓存获取的。性能接近直接调用。
private static Constructor<DataHandler> cachedConstructor;
//静态块里面的代码只会执行一次
static {
try {
//通过反射获取四个参数的构造函数
cachedConstructor = DataHandler.class.getDeclaredConstructor(String.class, String.class, String.class, String.class);
//设置允许访问,相当于让privite关键字失效。让构造函数允许被newInstance静态方法以外的方式进行实例化。
cachedConstructor.setAccessible(true);
} catch (NoSuchMethodException | SecurityException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
public static DataHandler createNewInstance(String appId, String appSecret, String publicKey, String privateKey) {
//缓存键
String cacheKey = appId + appSecret + publicKey + privateKey;
// 如果缓存没有该缓存键,则创建并返回实例。如果有,则直接返回。
return instanceCache.computeIfAbsent(cacheKey, k -> {
try {
return cachedConstructor.newInstance(appId, appSecret, publicKey, privateKey);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
log.error(e.getMessage(), e);
return null;
}
});
}
}