来一发测试:
@Test public void helloJdkProxy() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { IHelloService proxy = (IHelloService) retryProxyHandler.getProxy(HelloService.class); String hello = proxy.hello(); log.info("hello:{}", hello); } hello times:1 发生异常,time:14:40:27.540672200 retry times:1,time:14:40:27.541167400 hello times:2 发生异常,time:14:40:28.541584600 retry times:2,time:14:40:28.542033500 hello times:3 发生异常,time:14:40:29.542161500 retry times:3,time:14:40:29.542161500 hello times:4 hello:hello Frank完美,这样就不用担心依赖注入的问题了,因为从Spring容器中拿到的Bean对象都是已经注入配置好的。当然,这里仅考虑了单例Bean的情况,可以考虑的更加完善一点,判断一下容器中Bean的类型是Singleton还是Prototype,如果是Singleton则像上面这样进行操作,如果是Prototype则每次都新建代理类对象。
另外,这里使用的是JDK动态代理,因此就存在一个天然的缺陷,如果想要被代理的类,没有实现任何接口,那么就无法为其创建代理对象,这种方式就行不通了。
CGLib 动态代理既然已经说到了JDK动态代理,那就不得不提CGLib动态代理了。使用JDK动态代理对被代理的类有要求,不是所有的类都能被代理,而CGLib动态代理则刚好解决了这个问题。
创建一个CGLib动态代理类:
@Slf4j public class CGLibRetryProxyHandler implements MethodInterceptor { private Object target;//需要代理的目标对象 //重写拦截方法 @Override public Object intercept(Object obj, Method method, Object[] arr, MethodProxy proxy) throws Throwable { int times = 0; while (times < RetryConstant.MAX_TIMES) { try { return method.invoke(target, arr); } catch (Exception e) { times++; log.info("cglib retry :{},time:{}", times, LocalTime.now()); if (times >= RetryConstant.MAX_TIMES) { throw new RuntimeException(e); } } // 延时一秒 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } return null; } //定义获取代理对象方法 public Object getCglibProxy(Object objectTarget){ this.target = objectTarget; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(objectTarget.getClass()); enhancer.setCallback(this); Object result = enhancer.create(); return result; } }想要换用CGLib动态代理,替换一下这两行代码即可:
// 3. 不存在则生成代理对象 // bean = RetryInvocationHandler.getProxy(source); CGLibRetryProxyHandler proxyHandler = new CGLibRetryProxyHandler(); bean = proxyHandler.getCglibProxy(source);开始测试:
@Test public void helloCGLibProxy() { IHelloService proxy = (IHelloService) retryProxyHandler.getProxy(HelloService.class); String hello = proxy.hello(); log.info("hello:{}", hello); hello = proxy.hello(); log.info("hello:{}", hello); } hello times:1 发生异常,time:15:06:00.799679100 cglib retry :1,time:15:06:00.800175400 hello times:2 发生异常,time:15:06:01.800848600 cglib retry :2,time:15:06:01.801343100 hello times:3 发生异常,time:15:06:02.802180 cglib retry :3,time:15:06:02.802180 hello times:4 hello:hello Frank hello times:5 发生异常,time:15:06:03.803933800 cglib retry :1,time:15:06:03.803933800 hello times:6 发生异常,time:15:06:04.804945400 cglib retry :2,time:15:06:04.805442 hello times:7 发生异常,time:15:06:05.806886500 cglib retry :3,time:15:06:05.807881300 hello times:8 hello:hello Frank这样就很棒了,完美的解决了JDK动态代理带来的缺陷。优雅指数上涨了不少。
但这个方案仍旧存在一个问题,那就是需要对原来的逻辑进行侵入式修改,在每个被代理实例被调用的地方都需要进行调整,这样仍然会对原有代码带来较多修改。
Spring AOP想要无侵入式的修改原有逻辑?想要一个注解就实现重试?用Spring AOP不就能完美实现吗?使用AOP来为目标调用设置切面,即可在目标方法调用前后添加一些额外的逻辑。
先创建一个注解:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Retryable { int retryTimes() default 3; int retryInterval() default 1; }有两个参数,retryTimes 代表最大重试次数,retryInterval代表重试间隔。