专项攻克——重写 Redis 依赖包方法的 6 种实现方式 文章目录方式一继承重写最简单优先考虑示例重写 Jedis 的 set 方法示例重写 Spring Data Redis 的 RedisTemplate方式二装饰器模式组合比继承更灵活示例包装 RedisTemplate 的 opsForValue方式三Spring AOP 切面拦截无侵入推荐用于增强示例拦截 RedisTemplate 的所有 set 操作方式四同包同类名覆盖类加载优先级黑科技示例覆盖 Jedis 的某个类方式五Java Agent 字节码增强终极方案示例用 ByteBuddy 拦截 Jedis 的 set 方法方式六动态代理JDK 动态代理 / CGLIB示例JDK 动态代理包装 JedisCommands 接口方案选择决策树面试口述标准答案先确认你用的是哪个 Redis 客户端Jedis / Lettuce / Spring Data Redis / Redisson不同方式适用场景不同。下面按推荐优先级逐一给出可落地代码。方式一继承重写最简单优先考虑适用条件目标类不是final目标方法不是final/static/private。示例重写 Jedis 的set方法import redis.clients.jedis.Jedis; public class CustomJedis extends Jedis { public CustomJedis(String host, int port) { super(host, port); } Override public String set(String key, String value) { // 前置增强比如加日志、统计、权限校验 System.out.println(自定义set方法被调用key key); // 调用原方法 String result super.set(key, value); // 后置增强 System.out.println(set执行结果 result); return result; } }使用try (CustomJedis jedis new CustomJedis(localhost, 6379)) { jedis.set(key1, value1); // 走你重写的方法 }示例重写 Spring Data Redis 的 RedisTemplateimport org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; public class CustomRedisTemplateK, V extends RedisTemplateK, V { Override public ValueOperationsK, V opsForValue() { // 包装一层对 opsForValue 返回的对象做增强 return new CustomValueOperations(super.opsForValue()); } }坑点如果类是final比如 Lettuce 的很多类此方式不可用如果方法是static不能重写只能隐藏不推荐框架内部如果直接 new 原类而不是用你的子类重写不生效方式二装饰器模式组合比继承更灵活适用条件目标类是final无法继承或只想增强部分方法。示例包装 RedisTemplate 的 opsForValueimport org.springframework.data.redis.core.ValueOperations; import java.util.concurrent.TimeUnit; public class CustomValueOperationsK, V implements ValueOperationsK, V { private final ValueOperationsK, V delegate; // 被包装的原对象 public CustomValueOperations(ValueOperationsK, V delegate) { this.delegate delegate; } Override public void set(K key, V value) { System.out.println(装饰器拦截 setkey key); delegate.set(key, value); // 委托给原对象 } Override public Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit) { System.out.println(装饰器拦截 setIfAbsentkey key); return delegate.setIfAbsent(key, value, timeout, unit); } // 其余方法全部委托给 delegate不做改动 Override public V get(Object key) { return delegate.get(key); } Override public Long increment(K key) { return delegate.increment(key); } // ... 接口里所有方法都要实现逐个委托 }Spring 中注入使用Configuration public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; } // 直接暴露包装后的 ValueOperations Bean public ValueOperationsString, Object customValueOperations(RedisTemplateString, Object redisTemplate) { return new CustomValueOperations(redisTemplate.opsForValue()); } }坑点接口方法很多时实现起来繁琐可以用 LombokDelegate简化如果有多个重载方法每个都要单独处理方式三Spring AOP 切面拦截无侵入推荐用于增强适用条件只想加日志、监控、缓存、权限等横切逻辑不改原方法核心逻辑。示例拦截 RedisTemplate 的所有 set 操作import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; Aspect Component public class RedisMethodAspect { // 拦截 RedisTemplate 中所有以 set 开头的方法 Around(execution(* org.springframework.data.redis.core.RedisTemplate.set*(..))) public Object aroundSetMethods(ProceedingJoinPoint pjp) throws Throwable { long start System.currentTimeMillis(); String methodName pjp.getSignature().getName(); Object[] args pjp.getArgs(); System.out.println([Redis AOP] 方法 methodName , 参数 java.util.Arrays.toString(args)); try { Object result pjp.proceed(); // 执行原方法 long cost System.currentTimeMillis() - start; System.out.println([Redis AOP] 执行成功耗时 cost ms); return result; } catch (Throwable e) { System.out.println([Redis AOP] 执行失败: e.getMessage()); throw e; } } // 也可以拦截具体某个方法 Around(execution(* org.springframework.data.redis.core.ValueOperations.set(..))) public Object aroundValueSet(ProceedingJoinPoint pjp) throws Throwable { // 你的增强逻辑 return pjp.proceed(); } }坑点Spring AOP 只能拦截 Spring 容器管理的 Bean自己new出来的对象不生效只能拦截public方法CGLIB 可以拦截 protected但不推荐同类内部方法调用this.xxx()不会触发 AOP如果要完全替换方法逻辑不调用原方法不调用pjp.proceed()即可但要自己返回正确类型方式四同包同类名覆盖类加载优先级黑科技适用条件目标类是final 方法是final且你想彻底替换整个类的实现。原理在你的项目源码中创建一个包名、类名完全相同的类编译后你的 class 会优先被类加载器加载因为 target/classes 在 classpath 中排在依赖 jar 前面。示例覆盖 Jedis 的某个类假设要覆盖redis.clients.jedis.Jedis类// 在你的项目 src/main/java 下创建完全相同的包路径 package redis.clients.jedis; // 包名必须完全一致 public class Jedis { // 类名必须完全一致 // 重写整个类的实现或者只改你要的方法 // 注意这个类会完全替换 jar 包里的 Jedis 类 public String set(String key, String value) { System.out.println(完全替换的set方法); // 你的实现... return OK; } // 其他方法也要实现否则调用会报错 }坑点极多慎用这是完全替换不是增强原类的所有方法你都得自己实现依赖包升级后原类结构变了你的覆盖类可能直接报错团队协作时其他人很难发现这个黑科技维护成本极高如果有类加载器隔离比如 Tomcat、OSGi可能不生效除非万不得已否则不要用这种方式方式五Java Agent 字节码增强终极方案适用条件需要在运行时动态修改任意类包括 final 类、JDK 类且无法通过其他方式实现。工具ByteBuddy推荐、ASM、Javassist示例用 ByteBuddy 拦截 Jedis 的 set 方法import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.asm.Advice; import net.bytebuddy.matcher.ElementMatchers; import java.lang.instrument.Instrumentation; public class RedisAgent { public static void premain(String agentArgs, Instrumentation inst) { new AgentBuilder.Default() .type(ElementMatchers.named(redis.clients.jedis.Jedis)) .transform((builder, typeDescription, classLoader, module, protectionDomain) - builder.method(ElementMatchers.named(set) .and(ElementMatchers.takesArguments(String.class, String.class))) .intercept(Advice.to(RedisSetAdvice.class)) ) .installOn(inst); } }import net.bytebuddy.asm.Advice; public class RedisSetAdvice { Advice.OnMethodEnter public static void enter(Advice.Argument(0) String key, Advice.Argument(1) String value) { System.out.println([ByteBuddy] 进入set方法key key , value value); } Advice.OnMethodExit(onThrowable Exception.class) public static void exit(Advice.Return String result, Advice.Thrown Throwable throwable) { if (throwable ! null) { System.out.println([ByteBuddy] set方法异常: throwable.getMessage()); } else { System.out.println([ByteBuddy] set方法返回: result); } } }使用方式# 打成 agent jar启动时附加 java -javaagent:redis-agent.jar -jar your-app.jar坑点复杂度高需要理解字节码和类加载机制调试困难出问题很难排查对 JDK 版本敏感不同 JDK 版本的字节码格式可能有差异一般只有中间件、APM 工具如 SkyWalking、Pinpoint才会用方式六动态代理JDK 动态代理 / CGLIB适用条件目标类实现了接口用 JDK 动态代理没有接口用 CGLIB。示例JDK 动态代理包装 JedisCommands 接口import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisCommands; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class RedisProxyFactory { public static JedisCommands createProxy(Jedis jedis) { return (JedisCommands) Proxy.newProxyInstance( jedis.getClass().getClassLoader(), new Class?[]{JedisCommands.class}, new RedisInvocationHandler(jedis) ); } static class RedisInvocationHandler implements InvocationHandler { private final Object target; public RedisInvocationHandler(Object target) { this.target target; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (set.equals(method.getName())) { System.out.println([动态代理] 拦截set方法参数 java.util.Arrays.toString(args)); } return method.invoke(target, args); } } }使用Jedis jedis new Jedis(localhost, 6379); JedisCommands proxy RedisProxyFactory.createProxy(jedis); proxy.set(key, value); // 走代理坑点JDK 动态代理只能代理接口不能代理具体类CGLIB 不能代理 final 类和 final 方法代理对象和原对象是两个对象框架内部如果用原对象代理不生效方案选择决策树你想做什么 ├── 只加日志/监控/统计等横切逻辑 → 方式三Spring AOP推荐 ├── 改方法逻辑类和方法都不是final → 方式一继承重写最简单 ├── 类是final或只想增强部分方法 → 方式二装饰器模式 ├── 目标有接口 → 方式六动态代理 ├── 类是final 方法是final必须彻底替换 → 方式四同包同类名覆盖慎用 └── 运行时动态修改无法通过其他方式 → 方式五Java Agent ByteBuddy面试口述标准答案重写 Redis 依赖包的方法按场景有六种方式。最常用的是继承重写如果类不是 final、方法不是 final直接继承后 Override 就行。如果类是 final 或者只想增强部分方法用装饰器模式把原对象组合进来接口方法逐个委托。如果只是加日志、监控这类横切逻辑用 Spring AOP 拦截最方便无侵入。如果目标有接口可以用 JDK 动态代理没有接口用 CGLIB。极端情况下类和方法都是 final只能用同包同类名覆盖但这个是黑科技维护成本高不推荐。终极方案是 Java Agent 加 ByteBuddy 做字节码增强一般只有 APM 工具才会用。实际项目里 90% 的场景用继承或装饰器就能解决。