java 反射工具类 可以修改 final修饰的属性 及 类静态final修饰的属性

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

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值