從JDK1.3開始,Java就引入了動態代理的概念。動態代理(Dynamic Proxy)可以協助你減少程式碼數,真正提高代碼的可複用度。例如,你不必為所有的類的方法裡面都寫上相同的Log程式碼,取而代之的是實用類的動態代理類。當然,這種便利是有條件的。本文簡單介紹Java動態代理的原理,並實現一個被代理的Servlet建立,和調用的過程。 1.代理模式(Proxy Pattern)在JDK1.3以前,代理模式就已流行,所以得代理模式是產生一個和類相同介面的代理類,使用者通過使用代理類來封裝某個實作類別。1,其目的是加強實作類別的某個方法的功能,而不必改變原有的原始碼。
2.動態代理(Dynamic Proxy)隨著Proxy的流行,Sun把它納入到JDK1.3實現了Java的動態代理。動態代理和普通的代理模式的區別,就是動態代理中的代理類是由java.lang.reflect.Proxy類在運行期時根據介面定義,採用Java反射功能動態產生的。和java.lang.reflect.InvocationHandler結合,可以加強現有類的方法實現。2,圖中的自訂Handler實現InvocationHandler介面,自訂Handler執行個體化時,將實作類別傳入自訂Handler對象。自訂Handler需要實現invoke方法,該方法可以使用Java反射調用實作類別的實現的方法,同時當然可以實現其他功能,例如在調用實作類別方法前後加入Log。而Proxy類根據Handler和需要代理的介面動態產生一個介面實作類別的對象。當使用者調用這個動態產生的實作類別時,實際上是調用了自訂Handler的invoke方法。
3.動態代理Servlet 雖然Web Application Server的產品很多,但Servlet的處理原理是相似的:動態載入Servlet,調用Servlet的init方法(只被調用一次),並儲存到Servlet容器;Servlet使用時,調用Servlet的service方法。本文動態代理Servlet介面,使其init和service被調用時會在控制台打出方法調用前後資訊。首先實現2個Servlet,DefaultServlet和UserServlet package org.colimas.servlet; import javax.servlet.Servlet;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet; public class DefaultServlet extends HttpServlet implements Servlet { public void init() throws ServletException { super.init(); System.out.println(DefaultServlet.class.getName()+":Running init"); } public String getServletInfo() { return DefaultServlet.class.getName(); }} package org.colimas.servlet; import java.io.IOException; import javax.servlet.Servlet;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse; public class UserServlet extends HttpServlet implements Servlet { private static final long serialVersionUID = -7016554795165038652L; public void init() throws ServletException { super.init(); System.out.println(UserServlet.class.getName()+":Running init"); } protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println(UserServlet.class.getName()+":Do UserSErvlet Get"); } public String getServletInfo() { return UserServlet.class.getName(); } } 然後實現InvocationHandlerpackage org.colimas.webapp; import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method; import javax.servlet.Servlet; public class ServletHandler implements InvocationHandler { private Servlet obj; public ServletHandler(Servlet obj){ this.obj=obj; } public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable { if(arg1.getName().compareTo("init")==0) //調用init時 { System.out.println(obj.getServletInfo()+":Init servlet starting..."); //增加控制台輸出。 arg1.invoke(obj,arg2); //調用init方法 System.out.println(obj.getServletInfo()+":Init servlet ending..."); //增加控制台輸出。 }else if(arg1.getName().compareTo("service")==0){ //調用service時 System.out.println(obj.getServletInfo()+":service starting..."); //增加控制台輸出。 arg1.invoke(obj,arg2); //調用service方法。 System.out.println(obj.getServletInfo()+":service ending..."); //增加控制台輸出。 } return null; } } 實現Servlet的調用package org.colimas.webapp; import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy; import javax.servlet.Servlet;import javax.servlet.ServletConfig;import javax.servlet.ServletContext;import javax.servlet.ServletException; public class ServletWrapperImp { private Class servletClass; private ServletConfig config; private String _servletname; private Servlet _theServlet; private ServletContext context; public ServletWrapperImp(ServletConfig config){ this.config=config; this._servletname=this.config.getServletName(); this.context=this.config.getServletContext(); } public Servlet getServlet() throws ServletException{ destroy(); try { WebAppClassLoader loader=new WebAppClassLoader(this.getClass().getClassLoader()); //自訂class loader String name=getServletName(); //從ServletConfig中獲得Servlet Name synchronized (context) { Servlet theServlet=context.getServlet(name); //在ServletContext中尋找Servlet if(theServlet==null){ //如果ServletContext沒有。 servletClass = loader.loadClass(name); //由Class loader 載入Servlet class。 theServlet = (Servlet) servletClass.newInstance(); //Servlet執行個體化。 WebAppContext.addServlet(name,theServlet); //將Servlet執行個體存入ServletContext。 InvocationHandler handler=new ServletHandler(theServlet); //自訂ServletHandler,參見ServletHandler類。 _theServlet=(Servlet)Proxy.newProxyInstance(theServlet.getClass().getClassLoader(), new Class[]{Servlet.class},handler); //代理類執行個體化。 _theServlet.init(config); //Servlet代理對象調用init方法。參見ServletHandler的invoke方法。 }else{ //ServletContext裡已存在。 InvocationHandler handler=new ServletHandler(theServlet); //自訂ServletHandler,參見ServletHandler類。 _theServlet=(Servlet)Proxy.newProxyInstance(theServlet.getClass().getClassLoader(), new Class[]{Servlet.class},handler); //代理Servlet介面,動態組建代理程式類,並執行個體化。 } } return _theServlet; //返回Servlet代理對象 } catch( ClassNotFoundException ex1 ) { } catch( InstantiationException ex ) { }catch(IllegalAccessException ex2){ } return null; } public void destroy() { if (_theServlet != null) { _theServlet.destroy(); } } protected String getServletName(){ return _servletname; }} 其中的ServletConfig儲存Servlet相關資訊。ServletContext儲存所有的Servlet對象。WebAppClassLoader為自訂class loader,參見http://blog.csdn.net/tyrone1979/archive/2006/09/03/1164262.aspx。 最後編寫測試類別Main,該類類比10個使用者訪問Servlet,5人訪問DefaultServlet,5人訪問UserServlet。package org.colimas.main; import java.io.IOException; import javax.servlet.Servlet;import javax.servlet.ServletConfig;import javax.servlet.ServletException; import org.colimas.webapp.HttpServletRequestWrapper;import org.colimas.webapp.HttpServletResponseWrapper;import org.colimas.webapp.ServletConfigImpl;import org.colimas.webapp.ServletWrapper;import org.colimas.webapp.ServletWrapperImp;import org.colimas.webapp.WebAppContext; public class Main { private ThreadGroup _threadGroup; private Thread[] _threads; String defaultServletName="org.colimas.servlet.DefaultServlet"; String userServletName="org.colimas.servlet.UserServlet"; WebAppContext context=WebAppContext.newInstance(); public void doStart(){ _threadGroup=new ThreadGroup("SERVLETS"); int i=0; _threads=new ServletThread[10]; //類比10位使用者。 for(i=0;i<5;i++){ _threads[i]=new ServletThread(_threadGroup,new Integer(i).toString(), defaultServletName); _threads[i].start(); } for(i=5;i<10;i++){ _threads[i]=new ServletThread(_threadGroup,new Integer(i).toString(), userServletName); _threads[i].start(); } } /** * @param args */ public static void main(String[] args) { Main _main=new Main(); _main.doStart(); } //類比使用者線程 private class ServletThread extends Thread{ private String servletName; public ServletThread(ThreadGroup group,String threadname,String servletname){ super(group,threadname); servletName=servletname; } //調用Servlet的service. public void run() { //使用者調用Servlet ServletConfig config=new ServletConfigImpl(servletName,context); //調用的Servlet資訊。 ServletWrapperImp wrapper=new ServletWrapperImp(config); try { Servlet defaultServlet=wrapper.getServlet(); //獲得Servlet對象,實際是Servlet的代理對象 defaultServlet.service(new HttpServletRequestWrapper(), new HttpServletResponseWrapper()); 調用代理對象的service方法,參見ServletHandler的invoke方法。 } catch (ServletException e) { e.printStackTrace(); } catch(IOException e){ } } }}HttpServletRequestWrapper和HttpServletResponseWrapper實現HttpServletRequest,和HttpServletResponse。測試結果如下org.colimas.servlet.DefaultServlet:Init servlet starting...org.colimas.servlet.DefaultServlet:Running initorg.colimas.servlet.DefaultServlet:Init servlet ending...org.colimas.servlet.UserServlet:Init servlet starting...org.colimas.servlet.UserServlet:Running initorg.colimas.servlet.UserServlet:Init servlet ending...org.colimas.servlet.DefaultServlet:service starting...org.colimas.servlet.DefaultServlet:service ending...org.colimas.servlet.DefaultServlet:service starting...org.colimas.servlet.DefaultServlet:service ending...org.colimas.servlet.UserServlet:service starting...org.colimas.servlet.UserServlet:Do UserSErvlet Getorg.colimas.servlet.UserServlet:service ending...org.colimas.servlet.UserServlet:service starting...org.colimas.servlet.UserServlet:Do UserSErvlet Getorg.colimas.servlet.UserServlet:service ending...org.colimas.servlet.UserServlet:service starting...org.colimas.servlet.UserServlet:Do UserSErvlet Getorg.colimas.servlet.UserServlet:service ending...org.colimas.servlet.UserServlet:service starting...org.colimas.servlet.DefaultServlet:service starting...org.colimas.servlet.UserServlet:service starting...org.colimas.servlet.DefaultServlet:service starting...org.colimas.servlet.DefaultServlet:service starting...org.colimas.servlet.UserServlet:Do UserSErvlet Getorg.colimas.servlet.DefaultServlet:service ending...org.colimas.servlet.UserServlet:Do UserSErvlet Getorg.colimas.servlet.DefaultServlet:service ending...org.colimas.servlet.DefaultServlet:service ending...org.colimas.servlet.UserServlet:service ending...org.colimas.servlet.UserServlet:service ending... 2個Servlet第一次Load時初始化,被調用init,之後儲存到ServletContext中。第二次直接從ServletContext獲得,執行service。紅字表示代理類裡增加的輸出結果。 4.動態代理的限制 JDK的動態代理並不能隨心所欲的代理所有的類。Proxy.newProxyInstance方法的第二個參數只能是介面數組, 也就是Proxy只能代理介面。