一個同事將公司的開發框架基於最新的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);
}
}
}
理解了這個應該就理解了上邊空指針問題的原因了吧,希望能幫到遇到此問題的人。