zoukankan      html  css  js  c++  java
  • CGLib动态代理引起的空指针异常

      一个同事将公司的开发框架基于最新的Spring、Tomcat、Java版本作了部分修改,拿来开发运行之后,发现一个奇怪的空指针异常。

      还原一下当时的场景,代码大概如下,所有的Servlet继承自BaseServlet。以DefaultServlet为例,当有DefaultServlet请求到达时,会映射到一个ServletProxy的servlet,然后再转发至DefaultServlet。在转发之前已经调用了DefaultServlet的setLogger方法,假设转发到doGet方法,doGet方法先调用了response(req,res)又转到了execute方法,execute方法打印一行log,至此都没有问题。接下来doGet方法也打印一行log"@@@@@@doGet",在这里就报了空指针异常。logger为null,这就奇怪了,怎么会为null呢。

    public abstract class BaseServlet extends HttpServlet {
        protected Log logger;
        
        @Override
        public final void doGet( HttpServletRequest req, HttpServletResponse res ) throws IOException {
            response( req, res );
            logger.debug( "@@@@@@doGet");
        }
    
        @Override
        public final void doPost( HttpServletRequest req, HttpServletResponse res ) throws IOException {
            response( req, res );
            logger.debug( "@@@@@@doPost");
        }
    
        private void response( HttpServletRequest req, HttpServletResponse res ) throws IOException {
            
            String result = "";
            try {
                result = execute( req, res );
            }
            finally {
                //返回结果
            }
        }
    
        public void setLogger( Log logger ) {
            this.logger = logger;
        }
        
        public abstract String execute( HttpServletRequest req, HttpServletResponse res ) throws IOException;
    }
    
    public class DefaultServlet extends BaseServlet {
        @Override
        public String execute(HttpServletRequest req, HttpServletResponse res)
                throws IOException {
                logger.info("@@@@@@@DefaultServlet");
        }
    }

      倒腾了半天也找不出个原因,最后与原来的框架比较了一下,发现doGet与doPost被加上了final修饰符。我去,这样一想就有点头绪了,因为在Spring配置文件中配置了动态代理做切面。动态代理又是用的CGLib。

      下面顺便说一下CGLib的大概原理(有部分猜测的成分,错误之处请指正),假设有个类A,如果用CGLib做动态代理,将会在字节码的层面上动态生成一个类B并加载。模拟代码如下,B继承自A同时又有对A的引用,B类所有可重写的方法都要重写并调用A类型target的同名方法,当然在调用target方法之前可以做调用前操作和调用后操作,这才是代理的用途,这里就省略掉了。

      在main函数里new了一个B类的实例,并调用了setName方法,实际上执行的是target的setName方法,设置的是target的字段name,B实例的字段name仍然为空。调用notFinalMethod方法也是调用target的方法并能把target的字段name打印出来。但是finalMethod方法由于有final修饰符,所以不能在B中重写,当调用finalMethod方法时,就只能乖乖地调用B本身的finalMethod方法而不能调用target的finalMethod方法,这时由于B实例的name为空,所以打印出来的值也就为空了。

    public class CGLibSimulate {
    
        public static void main(String[] args) {
            A a=new B();
            a.setName("aa");
            a.notFinalMethod();
            a.finalMethod();
        }
    
        public static class A {
            protected String name = null;
            
            public final void finalMethod() {
                System.out.println(name);
            }
    
            public void notFinalMethod() {
                System.out.println(name);
            }
            
            public void setName(String name){
                this.name=name;
            }
        }
    
        public static class B extends A {
            private A target=new A();
            
            @Override
            public void notFinalMethod() {
                target.notFinalMethod();
            }
            
            @Override
            public void setName(String name){
                target.setName(name);
            }
        }
    }

       理解了这个应该就理解了上边空指针问题的原因了吧,希望能帮到遇到此问题的人。

  • 相关阅读:
    关于size_t
    图的搜索算法之迷宫问题和棋盘马走日问题
    螺旋矩阵与螺旋队列
    内存分配问题
    质数的判断
    全局变量、静态全局变量、静态局部变量和局部变量的区别
    程序员必知之代码规范标准
    字符串查找与类型转换(C/C++)
    sizeof与strlen()的用法与区别
    关于C++的输入输出流(cin、sstream和cout)
  • 原文地址:https://www.cnblogs.com/luguo3000/p/3951144.html
Copyright © 2011-2022 走看看