一個同事將公司的開發框架基於最新的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呢。ide
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方法以前能夠作調用前操做和調用後操做,這纔是代理的用途,這裏就省略掉了。this
在main函數裏new了一個B類的實例,並調用了setName方法,實際上執行的是target的setName方法,設置的是target的字段name,B實例的字段name仍然爲空。調用notFinalMethod方法也是調用target的方法並能把target的字段name打印出來。可是finalMethod方法因爲有final修飾符,因此不能在B中重寫,當調用finalMethod方法時,就只能乖乖地調用B自己的finalMethod方法而不能調用target的finalMethod方法,這時因爲B實例的name爲空,因此打印出來的值也就爲空了。spa
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); } } }
理解了這個應該就理解了上邊空指針問題的緣由了吧,但願能幫到遇到此問題的人。debug