2019獨角獸企業重金招聘Python工程師標準>>>
一、代理的概念
動態代理技術是整個java技術中最重要的一個技術,它是學習java框架的基礎,不會動態代理技術,那么在學習Spring這些框架時是學不明白的。
動態代理技術就是用來產生一個對象的代理對象的。在開發中為什么需要為一個對象產生代理對象呢?
舉一個現實生活中的例子:歌星或者明星都有一個自己的經紀人,這個經紀人就是他們的代理人,當我們需要找明星表演時,不能直接找到該明星,只能是找明星的代理人。比如劉德華在現實生活中非常有名,會唱歌,會跳舞,會拍戲,劉德華在沒有出名之前,我們可以直接找他唱歌,跳舞,拍戲,劉德華出名之后,他干的第一件事就是找一個經紀人,這個經紀人就是劉德華的代理人(代理),當我們需要找劉德華表演時,不能直接找到劉德華了(劉德華說,你找我代理人商談具體事宜吧!),只能是找劉德華的代理人,因此劉德華這個代理人存在的價值就是攔截我們對劉德華的直接訪問!
這個現實中的例子和我們在開發中是一樣的,我們在開發中之所以要產生一個對象的代理對象,主要用于攔截對真實業務對象的訪問。那么代理對象應該具有什么方法呢?代理對象應該具有和目標對象相同的方法
所以在這里明確代理對象的兩個概念:
1、代理對象存在的價值主要用于攔截對真實業務對象的訪問。
2、代理對象應該具有和目標對象(真實業務對象)相同的方法。劉德華(真實業務對象)會唱歌,會跳舞,會拍戲,我們現在不能直接找他唱歌,跳舞,拍戲了,只能找他的代理人(代理對象)唱歌,跳舞,拍戲,一個人要想成為劉德華的代理人,那么他必須具有和劉德華一樣的行為(會唱歌,會跳舞,會拍戲),劉德華有什么方法,他(代理人)就要有什么方法,我們找劉德華的代理人唱歌,跳舞,拍戲,但是代理人不是真的懂得唱歌,跳舞,拍戲的,真正懂得唱歌,跳舞,拍戲的是劉德華,在現實中的例子就是我們要找劉德華唱歌,跳舞,拍戲,那么只能先找他的經紀人,交錢給他的經紀人,然后經紀人再讓劉德華去唱歌,跳舞,拍戲。
二、java中的代理
2.1、"java.lang.reflect.Proxy"類介紹
現在要生成某一個對象的代理對象,這個代理對象通常也要編寫一個類來生成,所以首先要編寫用于生成代理對象的類。在java中如何用程序去生成一個對象的代理對象呢,java在JDK1.5之后提供了一個"java.lang.reflect.Proxy"類,通過"Proxy"類提供的一個newProxyInstance方法用來創建一個對象的代理對象,如下所示:
1?static?Object?newProxyInstance(ClassLoader?loader,?Class<?>[]?interfaces,?InvocationHandler?h)
newProxyInstance方法用來返回一個代理對象,這個方法總共有3個參數,ClassLoader loader用來指明生成代理對象使用哪個類裝載器,Class<?>[] interfaces用來指明生成哪個對象的代理對象,通過接口指定,InvocationHandler h用來指明產生的這個代理對象要做什么事情。所以我們只需要調用newProxyInstance方法就可以得到某一個對象的代理對象了。
2.2、編寫生成代理對象的類
在java中規定,要想產生一個對象的代理對象,那么這個對象必須要有一個接口,所以我們第一步就是設計這個對象的接口,在接口中定義這個對象所具有的行為(方法)
1、定義對象的行為接口
package?cn.gacl.proxy;/**
*?@ClassName:?Person
*?@Description:?定義對象的行為
*?@author:?孤傲蒼狼
*?@date:?2014-9-14?下午9:44:22
*
*/?
public?interface?Person?{/***?@Method:?sing*?@Description:?唱歌*?@Anthor:孤傲蒼狼**?@param?name*?@return*/?String?sing(String?name);/***?@Method:?sing*?@Description:?跳舞*?@Anthor:孤傲蒼狼**?@param?name*?@return*/?String?dance(String?name);
}
2、定義目標業務對象類
package?cn.gacl.proxy;/**
*?@ClassName:?LiuDeHua
*?@Description:?劉德華實現Person接口,那么劉德華會唱歌和跳舞了
*?@author:?孤傲蒼狼
*?@date:?2014-9-14?下午9:22:24
*
*/?
public?class?LiuDeHua?implements?Person?{public?String?sing(String?name){System.out.println("劉德華唱"+name+"歌!!");return?"歌唱完了,謝謝大家!";}public?String?dance(String?name){System.out.println("劉德華跳"+name+"舞!!");return?"舞跳完了,多謝各位觀眾!";}
}
3、創建生成代理對象的代理類
package?cn.gacl.proxy;import?java.lang.reflect.InvocationHandler;
import?java.lang.reflect.Method;
import?java.lang.reflect.Proxy;/**
*?@ClassName:?LiuDeHuaProxy
*?@Description:?這個代理類負責生成劉德華的代理人
*?@author:?孤傲蒼狼
*?@date:?2014-9-14?下午9:50:02
*
*/?
public?class?LiuDeHuaProxy?{//設計一個類變量記住代理類要代理的目標對象private?Person?ldh?=?new?LiuDeHua();/***?設計一個方法生成代理對象*?@Method:?getProxy*?@Description:?這個方法返回劉德華的代理對象:Person?person?=?LiuDeHuaProxy.getProxy();//得到一個代理對象*?@Anthor:孤傲蒼狼**?@return?某個對象的代理對象*/?public?Person?getProxy()?{//使用Proxy.newProxyInstance(ClassLoader?loader,?Class<?>[]?interfaces,?InvocationHandler?h)返回某個對象的代理對象return?(Person)?Proxy.newProxyInstance(LiuDeHuaProxy.class.getClassLoader(),?ldh.getClass().getInterfaces(),new?InvocationHandler()?{/***?InvocationHandler接口只定義了一個invoke方法,因此對于這樣的接口,我們不用單獨去定義一個類來實現該接口,*?而是直接使用一個匿名內部類來實現該接口,new?InvocationHandler()?{}就是針對InvocationHandler接口的匿名實現類*//***?在invoke方法編碼指定返回的代理對象干的工作*?proxy?:?把代理對象自己傳遞進來?*?method:把代理對象當前調用的方法傳遞進來?*?args:把方法參數傳遞進來*?*?當調用代理對象的person.sing("冰雨");或者?person.dance("江南style");方法時,*?實際上執行的都是invoke方法里面的代碼,*?因此我們可以在invoke方法中使用method.getName()就可以知道當前調用的是代理對象的哪個方法*/@Overridepublic?Object?invoke(Object?proxy,?Method?method,Object[]?args)?throws?Throwable?{//如果調用的是代理對象的sing方法if?(method.getName().equals("sing"))?{System.out.println("我是他的經紀人,要找他唱歌得先給十萬塊錢!!");//已經給錢了,經紀人自己不會唱歌,就只能找劉德華去唱歌!return?method.invoke(ldh,?args);?//代理對象調用真實目標對象的sing方法去處理用戶請求}//如果調用的是代理對象的dance方法if?(method.getName().equals("dance"))?{System.out.println("我是他的經紀人,要找他跳舞得先給二十萬塊錢!!");//已經給錢了,經紀人自己不會唱歌,就只能找劉德華去跳舞!return?method.invoke(ldh,?args);//代理對象調用真實目標對象的dance方法去處理用戶請求}return?null;}});}
}
測試代碼:
package?cn.gacl.proxy;public?class?ProxyTest?{public?static?void?main(String[]?args)?{LiuDeHuaProxy?proxy?=?new?LiuDeHuaProxy();//獲得代理對象Person?p?=?proxy.getProxy();//調用代理對象的sing方法String?retValue?=?p.sing("冰雨");System.out.println(retValue);//調用代理對象的dance方法String?value?=?p.dance("江南style");System.out.println(value);}
}
運行結果如下:
? ? ? Proxy類負責創建代理對象時,如果指定了handler(處理器),那么不管用戶調用代理對象的什么方法,該方法都是調用處理器的invoke方法。
由于invoke方法被調用需要三個參數:代理對象、方法、方法的參數,因此不管代理對象哪個方法調用處理器的invoke方法,都必須把自己所在的對象、自己(調用invoke方法的方法)、方法的參數傳遞進來。
三、動態代理應用
在動態代理技術里,由于不管用戶調用代理對象的什么方法,都是調用開發人員編寫的處理器的invoke方法(這相當于invoke方法攔截到了代理對象的方法調用)。并且,開發人員通過invoke方法的參數,還可以在攔截的同時,知道用戶調用的是什么方法,因此利用這兩個特性,就可以實現一些特殊需求,例如:攔截用戶的訪問請求,以檢查用戶是否有訪問權限、動態為某個對象添加額外的功能。
3.1、在字符過濾器中使用動態代理解決中文亂碼
在平時的JavaWeb項目開發中,我們一般會寫一個CharacterEncodingFilter(字符過濾器)來解決整個JavaWeb應用的中文亂碼問題,如下所示:
package?me.gacl.web.filter;import?java.io.IOException;import?javax.servlet.Filter;
import?javax.servlet.FilterChain;
import?javax.servlet.FilterConfig;
import?javax.servlet.ServletException;
import?javax.servlet.ServletRequest;
import?javax.servlet.ServletResponse;/**
*?@ClassName:?CharacterEncodingFilter
*?@Description:?解決中文亂碼的字符過濾器
*?@author:?孤傲蒼狼
*?@date:?2014-9-14?下午10:38:12
*
*/?
public?class?CharacterEncodingFilter?implements?Filter?{@Overridepublic?void?init(FilterConfig?filterConfig)?throws?ServletException?{}@Overridepublic?void?doFilter(ServletRequest?request,?ServletResponse?response,FilterChain?chain)?throws?IOException,?ServletException?{//解決以Post方式提交的中文亂碼問題request.setCharacterEncoding("UTF-8");response.setCharacterEncoding("UTF-8");response.setContentType("text/html;charset=UTF-8");chain.doFilter(request,?response);}@Overridepublic?void?destroy()?{}
}
但是這種寫法是沒有辦法解決以get方式提交中文參數時的亂碼問題的,我們可以用如下的代碼來證明上述的解決中文亂碼過濾器只對以post方式提交中文參數時有效,而對于以get方式提交中文參數時無效
jsp測試頁面如下:
<%@?page?language="java"?pageEncoding="UTF-8"%>
<%--引入jstl標簽庫?--%>
<%@taglib?uri="http://java.sun.com/jsp/jstl/core"?prefix="c"%>
<!DOCTYPE?HTML>
<html><head><title>使用字符過濾器解決解決get、post請求方式下的中文亂碼問題</title></head><body><%--使用c:url標簽構建url,構建好的url存儲在servletDemo1變量中--%><c:url?value="/servlet/ServletDemo1"?scope="page"?var="servletDemo1"><%--構建的url的附帶的中文參數?,參數名是:username,值是:孤傲蒼狼--%><c:param?name="username"?value="孤傲蒼狼"></c:param></c:url><%--使用get的方式訪問?--%><a?href="${servletDemo1}">超鏈接(get方式請求)</a><hr/><%--使用post方式提交表單?--%><form?action="${pageContext.request.contextPath}/servlet/ServletDemo1"?method="post">用戶名:<input?type="text"?name="username"?value="孤傲蒼狼"?/><input?type="submit"?value="post方式提交"></form></body>
</html>
處理請求的ServletDemo1代碼如下:
package?me.gacl.web.controller;import?java.io.IOException;
import?java.io.PrintWriter;import?javax.servlet.ServletException;
import?javax.servlet.http.HttpServlet;
import?javax.servlet.http.HttpServletRequest;
import?javax.servlet.http.HttpServletResponse;public?class?ServletDemo1?extends?HttpServlet?{public?void?doGet(HttpServletRequest?request,?HttpServletResponse?response)throws?ServletException,?IOException?{//?接收參數String?username?=?request.getParameter("username");//?獲取請求方式String?method?=?request.getMethod();//?獲取輸出流PrintWriter?out?=?response.getWriter();out.write("請求的方式:"?+?method);out.write("<br/>");out.write("接收到的參數:"?+?username);}public?void?doPost(HttpServletRequest?request,?HttpServletResponse?response)throws?ServletException,?IOException?{doGet(request,?response);}
}
在web.xml中注冊上述的CharacterEncodingFilter和ServletDemo1
<filter><filter-name>CharacterEncodingFilter</filter-name><filter-class>me.gacl.web.filter.CharacterEncodingFilter</filter-class></filter><filter-mapping><filter-name>CharacterEncodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><servlet><servlet-name>ServletDemo1</servlet-name><servlet-class>me.gacl.web.controller.ServletDemo1</servlet-class></servlet><servlet-mapping><servlet-name>ServletDemo1</servlet-name><url-pattern>/servlet/ServletDemo1</url-pattern></servlet-mapping>
測試結果如下所示:
從運行結果可以看出,上述的過濾器的確是不能解決以get方式提交中文參數的亂碼問題,下面使用動態代理技術改造上述的過濾器,使之能夠解決以get方式提交中文參數的亂碼問題,改造后的過濾器代碼如下:
package?me.gacl.web.filter;import?java.io.IOException;
import?java.lang.reflect.InvocationHandler;
import?java.lang.reflect.Method;
import?java.lang.reflect.Proxy;import?javax.servlet.Filter;
import?javax.servlet.FilterChain;
import?javax.servlet.FilterConfig;
import?javax.servlet.ServletException;
import?javax.servlet.ServletRequest;
import?javax.servlet.ServletResponse;
import?javax.servlet.http.HttpServletRequest;
import?javax.servlet.http.HttpServletResponse;/**
*?@ClassName:?CharacterEncodingFilter
*?@Description:?解決中文亂碼的字符過濾器
*?@author:?孤傲蒼狼
*?@date:?2014-9-14?下午10:38:12
*
*/?
public?class?CharacterEncodingFilter?implements?Filter?{@Overridepublic?void?init(FilterConfig?filterConfig)?throws?ServletException?{}@Overridepublic?void?doFilter(ServletRequest?req,?ServletResponse?resp,FilterChain?chain)?throws?IOException,?ServletException?{final?HttpServletRequest?request?=?(HttpServletRequest)?req;HttpServletResponse?response?=?(HttpServletResponse)?resp;//解決以Post方式提交的中文亂碼問題request.setCharacterEncoding("UTF-8");response.setCharacterEncoding("UTF-8");response.setContentType("text/html;charset=UTF-8");//獲取獲取HttpServletRequest對象的代理對象ServletRequest?requestProxy?=?getHttpServletRequestProxy(request);/***?傳入代理對象requestProxy給doFilter方法,*?這樣用戶在使用request對象時實際上使用的是HttpServletRequest對象的代理對象requestProxy*/chain.doFilter(requestProxy,?response);}/***?@Method:?getHttpServletRequestProxy*?@Description:?獲取HttpServletRequest對象的代理對象*?@Anthor:孤傲蒼狼**?@param?request*?@return?HttpServletRequest對象的代理對象*/?private?ServletRequest?getHttpServletRequestProxy(final?HttpServletRequest?request){ServletRequest?proxy??=?(ServletRequest)?Proxy.newProxyInstance(CharacterEncodingFilter.class.getClassLoader(),request.getClass().getInterfaces(),new?InvocationHandler(){@Overridepublic?Object?invoke(Object?proxy,?Method?method,?Object[]?args)throws?Throwable?{//如果請求方式是get并且調用的是getParameter方法if?(request.getMethod().equalsIgnoreCase("get")?&&?method.getName().equals("getParameter"))?{//調用getParameter方法獲取參數的值String?value?=?(String)?method.invoke(request,?args);if(value==null){return?null;}//解決以get方式提交的中文亂碼問題return?new?String(value.getBytes("iso8859-1"),"UTF-8");}else?{//直接調用相應的方法進行處理return?method.invoke(request,?args);}}});//返回HttpServletRequest對象的代理對象return?proxy;}@Overridepublic?void?destroy()?{}
}
我們在過濾器中使用動態代理技術生成一個HttpServletRequest對象的代理對象requestProxy,然后把代理對象requestProxy進行chain.doFilter(requestProxy, response)傳遞給用戶使用,這樣用戶實際上使用的就是HttpServletRequest對象的代理對象requestProxy。然而這一過程對于用戶來說是透明的,用戶是不知道自己使用的HttpServletRequest對象是一個代理對象requestProxy,由于代理對象requestProxy和目標對象HttpServletRequest具有相同的方法,當用戶調用getParameter方法接收中文參數時,實際上調用的就是代理對象requestProxy的invoke方法,因此我們就可以在invoke方法中就判斷當前的請求方式以及用戶正在調用的方法,如果判斷當前的請求方式是get方式并且用戶正在調用的是getParameter方法,那么我們就可以手動處理get方式提交中文參數的中文亂碼問題了。
測試結果如下所示:
?3.2、在字符過濾器中使用動態代理壓縮服務器響應的內容后再輸出到客戶端
壓縮過濾器的代碼如下:
package?me.gacl.web.filter;import?java.io.ByteArrayOutputStream;
import?java.io.IOException;
import?java.io.OutputStreamWriter;
import?java.io.PrintWriter;
import?java.lang.reflect.InvocationHandler;
import?java.lang.reflect.Method;
import?java.lang.reflect.Proxy;
import?java.util.zip.GZIPOutputStream;import?javax.servlet.Filter;
import?javax.servlet.FilterChain;
import?javax.servlet.FilterConfig;
import?javax.servlet.ServletException;
import?javax.servlet.ServletOutputStream;
import?javax.servlet.ServletRequest;
import?javax.servlet.ServletResponse;
import?javax.servlet.http.HttpServletRequest;
import?javax.servlet.http.HttpServletResponse;/**
*?@ClassName:?GzipFilter
*?@Description:?壓縮過濾器,將web應用中的文本都經過壓縮后再輸出到瀏覽器
*?@author:?孤傲蒼狼
*?@date:?2014-9-15?下午9:35:36
*
*/?
public?class?GzipFilter?implements?Filter?{@Overridepublic?void?init(FilterConfig?filterConfig)?throws?ServletException?{}@Overridepublic?void?doFilter(ServletRequest?req,?ServletResponse?resp,FilterChain?chain)?throws?IOException,?ServletException?{final?HttpServletRequest?request?=?(HttpServletRequest)?req;final?HttpServletResponse?response?=?(HttpServletResponse)?resp;final?ByteArrayOutputStream?bout?=?new?ByteArrayOutputStream();final?PrintWriter?pw?=?new?PrintWriter(new?OutputStreamWriter(bout,"UTF-8"));chain.doFilter(request,?getHttpServletResponseProxy(response,?bout,?pw));pw.close();//拿到目標資源的輸出byte?result[]?=?bout.toByteArray();System.out.println("原始大小:"?+?result.length);ByteArrayOutputStream?bout2?=?new?ByteArrayOutputStream();GZIPOutputStream?gout?=?new?GZIPOutputStream(bout2);gout.write(result);gout.close();//拿到目標資源輸出的壓縮數據byte?gzip[]?=?bout2.toByteArray();System.out.println("壓縮大小:"?+?gzip.length);response.setHeader("content-encoding",?"gzip");response.setContentLength(gzip.length);response.getOutputStream().write(gzip);}/***?@Method:?getHttpServletResponseProxy*?@Description:?獲取HttpServletResponse對象的代理對象*?@Anthor:孤傲蒼狼**?@param?response*?@param?bout*?@param?pw*?@return?HttpServletResponse對象的代理對象*/?private?ServletResponse?getHttpServletResponseProxy(final?HttpServletResponse?response,final?ByteArrayOutputStream?bout,?final?PrintWriter?pw)?{return?(ServletResponse)?Proxy.newProxyInstance(GzipFilter.class.getClassLoader(),?response.getClass().getInterfaces(),?new?InvocationHandler(){@Overridepublic?Object?invoke(Object?proxy,?Method?method,?Object[]?args)throws?Throwable?{if(method.getName().equals("getWriter")){return?pw;?}else?if(method.getName().equals("getOutputStream")){return?new?MyServletOutputStream(bout);}else{return?method.invoke(response,?args);}}});}@Overridepublic?void?destroy()?{}class?MyServletOutputStream?extends?ServletOutputStream{private?ByteArrayOutputStream??bout?=?null;public?MyServletOutputStream(ByteArrayOutputStream??bout){this.bout?=?bout;}@Overridepublic?void?write(int?b)?throws?IOException?{bout.write(b);}}
}
在web.xml中注冊上述的GzipFilter
<filter><description>配置壓縮過濾器</description><filter-name>GzipFilter</filter-name><filter-class>me.gacl.web.filter.GzipFilter</filter-class></filter><!--jsp文件的輸出的內容都經過壓縮過濾器壓縮后才輸出?--><filter-mapping><filter-name>GzipFilter</filter-name><url-pattern>*.jsp</url-pattern><!--?配置過濾器的攔截方式--><!--?對于在Servlet中通過request.getRequestDispatcher("jsp頁面路徑").forward(request,?response)?方式訪問的Jsp頁面的要進行攔截?--><dispatcher>FORWARD</dispatcher><!--對于直接以URL方式訪問的jsp頁面進行攔截,過濾器的攔截方式默認就是REQUEST--><dispatcher>REQUEST</dispatcher></filter-mapping><!--js文件的輸出的內容都經過壓縮過濾器壓縮后才輸出?--><filter-mapping><filter-name>GzipFilter</filter-name><url-pattern>*.js</url-pattern></filter-mapping><!--css文件的輸出的內容都經過壓縮過濾器壓縮后才輸出?--><filter-mapping><filter-name>GzipFilter</filter-name><url-pattern>*.css</url-pattern></filter-mapping><!--html文件的輸出的內容都經過壓縮過濾器壓縮后才輸出?--><filter-mapping><filter-name>GzipFilter</filter-name><url-pattern>*.html</url-pattern></filter-mapping>
GzipFilter過濾器會將*.jsp,*.js,*.css,*.html這些文件里面的文本內容都經過壓縮后再輸出到客戶端顯示。