最近还是一直在做Netty。其实是一直在修补自己堆起来的屎山

由于接入了新厂家和新协议,因此需要对原有的系统稍微改造一下。在改动原有的AOP日志的时候,就出现了问题。

问题

改动的地方简单的来说就是将原本在channelRead0方法中调用XXServicexxxxx方法(使用了注解@XXLogger),变为了直接在channelRead0方法上使用注解@XXLogger。导致的结果就是AOP失效了。

原来的代码示例:

public class XXMsgHandler extends SimpleChannelInboundHandler {

    @Autowired
    private XXService xxService;
    
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, XXMsg msg) throws Exception {
       xxService.xxxxx();
    }

}

public class XXServiceImpl implements XXService {
	
    @XXLogger
    public void xxxxx() {
	// ....
    }
}

新的代码示例:

public class XXMsgHandler extends SimpleChannelInboundHandler {
    
    @XXLogger
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, XXMsg msg) throws Exception {
       // ...
    }

}

原因

在发现切面代码没执行的问题后,我又用以前的代码和类似的代码进行测试,结果除了在XXHandler里,其他测试都没发现类似问题。

然后我用我有限的知识做出了"我写的应该没问题呀为啥就不执行呢这破Netty和Spring肯定有Bug"的结论。

......

由于在AOP这方面实在学艺不精,了解的不够深入,决定求救于搜索引擎。
搜索半天没有发现类似的情况,于是换了关键字搜索后发现了若干篇Spring AOP自调用失效问题的博客。

其中内容大致上是: 由于Spring AOP的原理,如果被代理对象在内部通过this调用自己的方法,则无法执行AOP增强处理。

Spring AOP是基于动态代理机制实现的,通过动态代理机制生成目标对象的代理对象,当外部调用目标对象的相关方法时,Spring注入的其实是代理对象Proxy,通过调用代理对象的方法执行AOP增强处理,然后回调目标对象的方法。

为啥?Spring AOP的原理中已经告诉我们了,切面通过代理实现,而通过this调用的时候,实际使用的是被原来的代理对象而非Spring生成的包含增强方法的代理对象。

联想到之前看的NettySimpleChannelInboundHandler的源码,AOP日志的失效原因就很明显了。

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        boolean release = true;

        try {
            if (this.acceptInboundMessage(msg)) {
                this.channelRead0(ctx, msg);
            } else {
                release = false;
                ctx.fireChannelRead(msg);
            }
        } finally {
            if (this.autoRelease && release) {
                ReferenceCountUtil.release(msg);
            }

        }

    }

protected abstract void channelRead0(ChannelHandlerContext var1, I var2) throws Exception;

NettySimpleChannelInboundHandler中在读取消息的时候做了预处理,对消息的类型进行判断,如果当前handler类可以处理此类型的消息,则调用channelRead0方法。而channelRead0方法正是我们需要重写和使用注解的方法。由于并没有使用Spring生成的代理类调用channelRead0,导致了AOP日志失效。最后还是老老实实地改成和原来类似的写法。

总结

  1. 很多注解失效的问题都是类似的原因,例如事务等。解决的办法有好几种,这里就不例举了。
  2. 有时候看看源码还是挺重要的。