java 反射工具类 可以修改 final修饰的属性 及 类静态final修饰的属性
反射操作工具类,用于修改指定的属性值,就算是final修饰的也可以改
修改属性的方案:主要是通过得到 属性的 Field 对象,把Field对象的 final 修饰符去掉,然后用反射给属性赋值
通过指定的构造函数创建实例,就算是private修饰也可以创建实例:采用指定的构造函数创建实例,就算是私有修饰也可以创建
/**
* @author ZhouChuGang
* @version 1.0
* @project home-cloud
* @date 2020/4/21 8:41
* @Description 反射操作工具类 用于修改类中的属性 包括 final 属性
*/
@Slf4j
public abstract class ReflectDeclaredFieldUtils {
/**
* 更新指定类的属性 返回原来的值
*
* @param object
* @param fieldName
* @param newValue
* @return
*/
public static Object declaredFieldUpdate(Object object, final String fieldName, Object newValue) throws Exception {
log.info("开始修改 {} 对象 的 {} 属性值 ,更新为新值 {}", object, fieldName, newValue);
// 获得属性对象 其可能被final修饰
Field prosField = object.getClass().getDeclaredField(fieldName);
// 此时copyOnThreadLocal的modifier的值是17 = 16 + 1 (16代表是final ,1代表是public)
// 以整数形式返回由此 Field 对象表示的字段的 Java 语言修饰符
log.info("没修改前的modifiers值即修饰符的值:" + prosField.getModifiers());
log.info("修改前的修饰符为: " + Modifier.toString(prosField.getModifiers()));
// 获得Field类的字节码对象,然后获取它的modifiers属性
Field modifiers = Field.class.getDeclaredField("modifiers");
// modifiers属性是field类私有的,需要设置setAccessible(true)
modifiers.setAccessible(true);
// 开始手动修改此属性的修饰符 ------------------------------
// ~按位取反 去掉 final 修饰
modifiers.setInt(prosField, modifiers.getInt(prosField) & (~Modifier.FINAL));
// 将final属性剔除以后的modifier值
log.info("将final属性剔除后modifiers的值:" + prosField.getModifiers());
log.info("修改后的修饰符为: " + Modifier.toString(prosField.getModifiers()));
//去掉JVM约束
prosField.setAccessible(true);
//读取之前的值
Object oldValue = prosField.get(object);
// 修改属性的值 设置为 阿里的线程本地存储
prosField.set(object, newValue);
return oldValue;
}
/**
* 通过指定的构造参数方法创建实例,这里会先把构造方法改为 PUBLIC 后再调用
*
* @param clazz 要创建的类的class
* @param parameters 构造函数的参数
* @return
*/
public static <T> T getInstanceByConstructor(Class<T> clazz, Object... parameters) throws Exception {
//先得到构造函数 其可能为 私有 这里需要更新
Constructor<T> declaredConstructor = clazz.getDeclaredConstructor(Arrays.stream(parameters).map(Object::getClass).toArray(Class[]::new));
// 获得Field类的字节码对象,然后获取它的modifiers属性
Field modifiers = Constructor.class.getDeclaredField("modifiers");
// modifiers属性是field类私有的,需要设置setAccessible(true)
modifiers.setAccessible(true);
// 开始手动修改此属性的修饰符 ------------------------------
// 这里直接修改为 public
modifiers.setInt(declaredConstructor, Modifier.PUBLIC);
return declaredConstructor.newInstance(parameters);
}
}
应用场景:
用于替换第三方jar中写好功能的类的属性,而不用为了改别人写的类中的一个属性而把整个代码复制粘贴一份。
用于修改被private修饰的构造方法,且需要修改static的属性
实际例子:
1、在springboot微服务中,因为会有许多服务间相互调用的情况,而无法在日志中定位到各个请求执行的日志,一般的方案采用slf4j 中的MDC来给每个请求分配唯一的logId,每次日志输出都会把这个logId打印出来,这样想相同的logId即为同一个请求。
上面的方案存在以下问题:
MDC的读写是采用 mdcAdapter 静态 属性来处理的 ,且其实现类为 LogbackMDCAdapter ,此类的读写存储都采用
ThreadLocal。ThreadLocal在线程池(异步线程)线程复用情况下会出现脏数据。
阿里提供一个jar包(transmittable-thread-local),其主要是解决异步线程ThreadLocal的问题,所有需要把原来的ThreadLocal类型的属性改为 TransmittableThreadLocal ,我们不需要把LogbackMDCAdapter 类的代码全部复制粘贴一份,只是为了修改两个属性,可以通过上面的反射工具来修改。
同时,如果只是修改了LogbackMDCAdapter 的属性,没有设置给MDC也是没有意义的,但是MDC是一个工具类,构造方法用private修饰,且需要修改的属性是 静态 属性 ,这里也可以用上面的工具类,先创建实例,然后反射修改。
增强默认MDC代码如下:
/**
* @author ZhouChuGang
* @version 1.0
* @project home-cloud
* @date 2020/4/21 9:11
* @Description 修改 LogbackMDCAdapter 类中的属性,替换为阿里的本地线程存储对象
*/
@Slf4j
public class MdcAdapterConfiguration {
/**
* 创建本实例后 需要执行此方法
* @throws Exception
*/
public void init() throws Exception {
LogbackMDCAdapter logbackMDCAdapter = new LogbackMDCAdapter();
//修改属性 替换为 TransmittableThreadLocal
ReflectDeclaredFieldUtils.declaredFieldUpdate(logbackMDCAdapter, "copyOnThreadLocal", new TransmittableThreadLocal<>());
ReflectDeclaredFieldUtils.declaredFieldUpdate(logbackMDCAdapter, "lastOperation", new TransmittableThreadLocal<>());
//创建实例
MDC mdc = ReflectDeclaredFieldUtils.getInstanceByConstructor(MDC.class);
//修改属性 替换为增强的 LogbackMDCAdapter
ReflectDeclaredFieldUtils.declaredFieldUpdate(mdc, "mdcAdapter", logbackMDCAdapter);
}
}
依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>transmittable-thread-local</artifactId> <version>2.11.4</version> </dependency>
注入spring容器,并执行init方法,使MDC增强
@Slf4j
public class LogAutoConfig {
@Bean(initMethod = "init")
public MdcAdapterConfiguration mdcAdapterConfiguration() {
MdcAdapterConfiguration mdcAdapterConfiguration = new MdcAdapterConfiguration();
return mdcAdapterConfiguration;
}
}
配置自动配置
resources/META-INF/spring.factories 文件加入如下内容
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.langangkj.common.log.LogAutoConfig