程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> CGLib代理引起的空指針異常問題

CGLib代理引起的空指針異常問題

編輯:關於JAVA

一個同事將公司的開發框架基於最新的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);
        }
    }
}

理解了這個應該就理解了上邊空指針問題的原因了吧,希望能幫到遇到此問題的人。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved