Hi, I’m Faithlv

@Autowired注入为null的问题

现象 在类中使用 @Autowired 注入 Bean,运行时为 null。 代码如下: @Service public class UserService { @Autowired private OrderService orderService; public void test() { orderService.create(); } } 运行时抛出: N u l l P o i n t e r E x c e p t i o n 排查 发现该类是通过 new 手动创建的: UserService userService = new UserService(); userService.test(); 这种方式不会经过 Spring 容器管理。 ...

April 16, 2025 · 1 min · Faithlv

遍历Map时删除元素报错问题

问题:遍历时直接remove报异常 测试代码: Map<String, Integer> map = new HashMap<>(); map.put("a", 1); map.put("b", 2); map.put("c", 3); for (String key : map.keySet()) { if ("b".equals(key)) { map.remove(key); } } 运行时报错: j a v a . u t i l . C o n c u r r e n t M o d i f i c a t i o n E x c e p t i o n 原因排查 使用增强for循环时,底层其实使用的是 Iterator。 ...

March 18, 2025 · 1 min · Faithlv

String、StringBuilder区别测试

问题1:循环拼接字符串为什么变慢 测试代码: public class Test { public static void main(String[] args) { long start = System.currentTimeMillis(); String s = ""; for (int i = 0; i < 100000; i++) { s += i; } long end = System.currentTimeMillis(); System.out.println("耗时:" + (end - start)); } } 当循环次数变大时,耗时明显增加。 改成 StringBuilder: public class Test { public static void main(String[] args) { long start = System.currentTimeMillis(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < 100000; i++) { sb.append(i); } long end = System.currentTimeMillis(); System.out.println("耗时:" + (end - start)); } } 对比测试,多次运行后 StringBuilder 明显更快。 ...

December 26, 2024 · 1 min · Faithlv

@Transactional不生效的几种情况

现象 方法上加了 @Transactional,但数据库操作报错后没有回滚。 代码如下: @Service public class OrderService { @Transactional public void createOrder() { orderMapper.insert(...); int a = 1 0; } } 执行后数据已插入,没有回滚。 排查一:方法内部调用 调用方式: @Service public class OrderService { public void test() { this.createOrder(); } @Transactional public void createOrder() { orderMapper.insert(...); int a = 1 0; } } 同类内部调用不会走代理,事务不生效。 需要通过代理对象调用。 排查二:异常类型不对 如果捕获异常: @Transactional public void createOrder() { try { orderMapper.insert(...); int a = 1 0; } catch (Exception e) { System.out.println(异常); } } 异常被捕获,事务不会回滚。 ...

September 22, 2024 · 1 min · Faithlv

闲鱼App逆向1:模拟器和抓包环境搭建

模拟器安装 模拟器选择安装夜神模拟器,其他模拟器可能会有frida兼容问题,这里尽量减少变量。 注意模拟器安卓系统有32位和64位区别,这里要和后面frida的版本对应,这里选择32位的版本,后面部分脚本在32位才有作用。 修改模拟器代理,将模拟器的wifi代理设置为宿主机ip,端口设置为8888,后面charles抓包需要8888端口,修改代理参考 https://zhuanlan.zhihu.com/p/611867924 Charles安装 软件百度自行安装,这部分主要是安装证书,到宿主机和模拟器,否则https的包抓出来都是加密的 安装证书到本机 frida框架 需要安装python环境,自行百度。 frida 安装 pip install frida-tools pip install frida frida-ps -U ## 验证安装 frida-ps -U 能列出进程就没问题 Charles抓包 使用frida+python不走spdy协议,淘宝系软件都会走spdy协议,导致charles会抓不到包,所以需要让app走http协议 运行下面python脚本,记得先启动app,否则会hook不到,该脚本让软件强制走http协议 该脚本只对32位的闲鱼有效,64位需要hook的方法可能变了,可以解包找到对应方法,替换该方法路径 Java.use("mtopsdk.mtop.global.SwitchConfig"); import frida, sys def on_message(message, data): print("[%s] => %s" % (message, data)) pid = 0 device = frida.get_usb_device(1000) process = device.enumerate_processes() for data in process: print(data) if data.name == "闲鱼": pid = data.pid break session = device.attach(pid) uuid = ''' Java.perform( function(){ console.log("############################ Frida 开启 ############################"); var session = Java.use("mtopsdk.mtop.global.SwitchConfig"); if (session != undefined) { console.log(`------>定位到类名: ${session}`); session.A.implementation = function(){ console.log(`------>定位到函数: is_enableSpdy`); return false; } }; } ) ''' script = session.create_script(uuid) script.on('message', on_message) script.load() sys.stdin.read() 打开charles,随便点app就可以看到抓到的http请求。 ...

July 13, 2024 · 1 min · Faithlv

闲鱼App逆向2:接口参数分析

接口分析 以搜索闲鱼接口为例 https://g-acs.m.goofish.com/gw/mtop.taobao.idlemtopsearch.search/1.0/ 模拟器内用闲鱼多次请求一个接口,在Charles中比较请求头不同,发现x-sgext、x-mini-wua、x-c-traceid、x-t、x-umt、x-sign变化 只要找到找到构造这些参数的方法,就可以模拟闲鱼app发起请求 其中x-t是时间戳,x-c-traceid不带也能正常请求,所以只需要知道x-sgext、x-mini-wua、x-umt、x-sign即可。 App逆向 在知道需要那些参数后,再就是从app源码中找到构造这些参数的方法 首先需要拿到app的源码,使用jadx逆向app,这步就是拿到app代码,jadex github地址https://github.com/skylot/jadx/releases ,下载后把app扔进去就行。 加密方法定位 从app代码中找到上面x-sgext、x-mini-wua、x-umt、x-sign的构造方法 定位到 mtopsdk.security.InnerSignImpl类下面的getUnifiedSign方法,该方法就是构造上面加密参数。 定位加密方法可以通过全局搜索,以x-sign为例,全局搜索字符串x-sign,找到那些看起来像是加密的方法,然后用frida hook方法,验证入参和出参,基本就能判断是不是加密方法。 firda hook代码 import frida, sys jscode = ''' Java.perform( function(){ function javaMapToJsObject(map){ //遍历保存为js对象 var res = {}; var keySet = map.keySet(); var it = keySet.iterator(); while(it.hasNext()){ var key = it.next().toString(); var value = map.get(key); if(value == null){ res[key] = null; }else{ res[key] = value.toString(); } } return res; } console.log("############################ Frida 开启 ############################"); var InnerSignImpl = Java.use("mtopsdk.security.InnerSignImpl"); if(InnerSignImpl != undefined){ console.log("############################ 定位到类 InnerSignImpl ############################"); InnerSignImpl.getUnifiedSign.implementation = function(map1, map2, string1, string2,bool,string3){ console.log("############################ my打印入参 ############################"); console.log("map1: " + JSON.stringify(javaMapToJsObject(map1))); console.log("map2: " + JSON.stringify(javaMapToJsObject(map2))); console.log("string1: " + string1); console.log("string2: " + string2); console.log("bool: " + bool); console.log("string3: " + string3); var ret = this.getUnifiedSign(map1,map2,string1,string2,bool,string3); console.log("############################ my打印返参 ############################"); console.log("ret " + ret); return ret; } } } ) ''' def on_message(message, data): print("[%s] => %s" % (message, data)) device = frida.get_usb_device(1000) process = device.attach("闲鱼") script = process.create_script(jscode) script.on('message', on_message) script.load() sys.stdin.read()

July 13, 2024 · 1 min · Faithlv

分布式锁死锁问题

现象 接口偶发卡死,请求一直等待不返回。 排查后发现某个 key 长时间存在,业务线程始终获取不到锁。 原始实现 使用 setnx 实现分布式锁: Boolean lock = redisTemplate.opsForValue() .setIfAbsent("order_lock", "1"); if (Boolean.TRUE.equals(lock)) { try { // 执行业务 } finally { redisTemplate.delete("order_lock"); } } 问题原因 当服务在执行过程中异常退出(例如进程重启), finally 块没有执行,锁未删除。 由于没有设置过期时间,key 会一直存在。 后续请求全部获取锁失败,形成类似“死锁”的情况。 调整方案 增加过期时间: Boolean lock = redisTemplate.opsForValue() .setIfAbsent("order_lock", "1", 30, TimeUnit.SECONDS); 避免锁永久存在。 进一步问题 如果业务执行超过 30 秒, 锁提前过期,其他线程可能获取锁,造成并发执行。 改进方式 使用唯一标识作为 value: String requestId = UUID.randomUUID().toString(); Boolean lock = redisTemplate.opsForValue() .setIfAbsent("order_lock", requestId, 30, TimeUnit.SECONDS); 释放锁时校验 value: ...

May 21, 2024 · 1 min · Faithlv

缓存击穿简单复现

现象 某个热点数据设置了过期时间。 过期瞬间并发请求同时打到数据库,数据库压力突然升高。 简单模拟 伪代码如下: public String getData(String key) { String value = redisTemplate.opsForValue().get(key); if (value == null) { value = queryFromDb(key); redisTemplate.opsForValue().set(key, value, 30, TimeUnit.SECONDS); } return value; } 当 key 过期时,多个线程同时进入 if 判断。 并发测试 简单用多线程模拟: ExecutorService pool = Executors.newFixedThreadPool(20); for (int i = 0; i < 20; i++) { pool.execute(() -> { getData("hot_key"); }); } 在 key 失效瞬间,可以看到数据库查询被执行多次。 ...

March 19, 2024 · 1 min · Faithlv

查询字段映射不上问题

现象 数据库中字段有值,但接口返回对象对应字段为 null。 SQL 单独在数据库执行是正常的。 原始SQL <select id="selectUser" resultType="com.example.User"> select user_id, user_name from user where user_id = #{id} </select> 实体类: public class User { private Long userId; private String userName; } 查询后发现 userName 为 null。 排查 数据库字段为: u u s s e e r r _ _ i n d a m e 实体类为: u u s s e e r r I N d a m e MyBatis 默认不会自动把下划线转为驼峰。 ...

February 18, 2024 · 1 min · Faithlv