主要內容:
- filter的部署
- 包裝設計模式對request和response進行包裝增強
一、Filter的部署-注冊Filter
<filter-name>
:指定過濾器的名字,內容不能為空。<filter-class>
: 指定過濾器的完整的限定類名<init-param>
指定初始化參數,其子元素<param-name>
指定參數的名字,<param-value>
指定參數的值。可以使用FilterConfig
接口對象來訪問初始化參數。-
<filter-mapping>
設置一個Filter所負責攔截的資源。一個Filter攔截的資源可以通過兩種方式來指定:Servlet名稱和資源訪問的請求路徑。-
<filter-name>
子元素用于設置Filter注冊名稱,和上面一樣。 -
<url-pattern>
設置Filter所攔截的請求路徑;/表示攔截所有,.jsp表示攔截jsp文件。等等。 -
<dispatcher>
指定過濾器所攔截的資源被Servlet容器調用的方式,可以是REQUEST,INCLUDE,FORWARD,ERROR
之一(必須是大寫),默認是REQUEST
。用戶可以設置多個<dispatcher>
子元素來指定Filter對資源的多種調用方式進行攔截。
-
-
<dispatcher>
子元素可以設置的值及其意義-
REQUEST
:當用戶直接訪問頁面時,web容器將會調用過濾器。如果目標資源是通過RequestDispatcher
的include或forward方法訪問時,那么該過濾器就不會被調用。 -
INCLUDE
:如果目標資源是通過RequestDispatcher
的include方法訪問時,那么該過濾器將被調用,除此之外,該過濾器將不會被調用。 -
FORWARD
:如果目標資源是通過RequestDispatcher
的forward方法訪問時,那么該過濾器將被調用,除此之外,該過濾器將不會被調用。 -
ERROR
:如果目標資源是通過聲明式異常處理機制調用時,那么該過濾器將被調用。除此之外,過濾器不會被調用。
-
示例:
FilterDemo4.java
package cn.itcast.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;
public class FilterDemo4 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("hahah");
}
@Override
public void destroy() {
}
}
配置:
<filter>
<filter-name>FilterDemo4</filter-name>
<filter-class>cn.itcast.web.filter.FilterDemo4</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterDemo4</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<error-page>
<exception-type>java.lang.ArithmeticException</exception-type>
<location>/2.jsp</location>
</error-page>
1.jsp
<body>
<%
int x = 1/0;
%>
</body>
2.jsp
<body>
error page
</body>
說明:
- 1.本來我們是在
1.jsp
中配置errorPage="/2.jsp"
,但是這樣配置不起作用。于是我們在web.xml
中進行配置。試驗時我們訪問1.jsp
,那么就會被過濾器攔截下來,不會到達2.jsp
。 - 2.在以后的開發中我們經常要攔截forward轉發的資源,注意在配置文件中進行配置,不然是不會起作用的。在上個例子中我們可以看到。
二、filter高級開發
由于開發人員在Filter中可以得到代表用戶請求和響應的request、response對象,因此在編程中可以使用Decorator(裝飾器)模式對這些對象進行包裝,再把包裝后的對象傳給目標資源,從而實現一些特殊需求。
注意:有四種方式訪問web資源,正常的通過瀏覽器直接訪問(request方式)、forward方式、include方式和error方式。
2.1回顧包裝開發模式
之前我們在筆記20中講到過包裝開發模式,這里再次回顧一下。使用包裝設計模式對BufferedReader類進行包裝增強。
BufferedReaderWrapper.java
package cn.itcast.demo;
import java.io.BufferedReader;
import java.io.IOException;
//使用包裝設計模式對BufferedReader類進行包裝增強
/*
* 1.實現與被增強對象相同的接口,如果接口方法太多,也可以繼承一個類
* 2.定義一個變量記住被增強對象
* 3.定義一個構造器,接收被增強對象
* 4.覆蓋需要增強的方法
* 5.對于不想增強的方法,直接調用被增強對象的方法
*
* */
import java.io.Reader;
public class BufferedReaderWrapper extends BufferedReader {
private BufferedReader br;
private int linenum = 1;
public BufferedReaderWrapper(BufferedReader br) {
super(br);
// 子類在使用構造函數的時候會調用父類的構造函數,但是
// 這里不知道調用父類哪個構造函數,于是就調用無參構造
// 函數,但是父類又沒有無參構造函數,這樣就會報錯,所
// 以這里我們要指定調用父類哪個構造函數
this.br = br;
}
@Override
public String readLine() throws IOException {// 增強此方法
String line = br.readLine();
if (line == null) {
return line;
}
return linenum++ + ":" + line;
}
}
測試:TestBufferedReaderWrapper.java
package cn.itcast.demo;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class TestBufferedReaderWrapper {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("src/cn/itcast/demo/BufferedReaderWrapper.java"));
BufferedReaderWrapper wrapper = new BufferedReaderWrapper(br);
/*String line = null;
while((line = wrapper.readLine()) != null ){
System.out.println(line);
}*/
FileWriter fw = new FileWriter("D:\\1.java");
String line = null;
while((line = wrapper.readLine()) != null ){
fw.write(line + "\r\n");//注意要換行
}
fw.close();
wrapper.close();
br.close();
}
}
說明:BufferedReader類中的readLine方法可以讀取一行文本,這里我們想讓readLine讀取一行文本時還在本行最前面加上行號,于是我們使用包裝設計模式對此方法進行包裝。注意:這里當此方法讀到空行的時候返回的是"",而不是null。
記住:能用包裝設計模式就不要用子類的方式
2.2Decorator(包裝)設計模式
-
當某個對象的方法不適應業務需求時,通常有兩種方式可以對方法進行增強:
- 編寫子類,覆蓋需要增強的方法
- 使用Decorator設計模式對方法進行增強
- 使用動態代理(這里先不講)
-
在實際開發中遇到需要增強對象的方法時,到底選擇用哪種方式
- 沒有具體的定式,不過有一種情況下,必須使用Decorator設計模式,即被增強對象,開發人員只能得到它的對象,無法得到它的class文件。
- 比如request、response對象,開發人員之所以在Servlet中能通過sun公司定義的
HttpServletRequest\HttpServletResponse
接口去操作這些對象,是因為tomcat服務器廠商編寫了request、response接口的實現類。Web服務器在調用Servlet時,會用這些接口的實現類創建出對象,然后傳遞給Servlet程序。 - 此種情況下,由于開發人員根本不知道服務器廠商編寫的request、response接口的實現類是哪個,在程序中只能拿到其提供的對象,因此就只能采用Decorator設計模式對這些對象進行增強。
Decorator設計模式的實現
1.首先看需要被增強對象繼承了什么接口或父類,編寫一個類也去繼承這些接口或父類。
2.在類中定義一個變量,變量類型即需要增強對象類型。
3.在類中定義一個構造函數,接收需要增強的對象。
4.覆蓋需要增強的方法,編寫增強的代碼。
使用此設計模式為BufferedReader類的readLine方法添加行號的功能。在上面的例子中我們可以看到。
三、對request對象的增強(工程day18_2
)
- ServletAPI中提供了一個request對象的Decorator設計模式的默認實現類
HttpServletRequestWrapper
(HttpServletRequestWrapper
類實現了request接口中的所有方法,但這些方法的內部實現都是僅僅調用了一下所包裝的request對象的對應方法)以避免用戶在對request對象進行增強時需要實現request接口中的所有方法(這樣太麻煩)。 - 使用Decorator模式包裝request對象,完全解決get、post請求方式下的亂碼問題(前面有過這樣一個例子,但是那個例子中只能解決post方式下的亂碼問題)。
3.1 包裝request對象,解決亂碼問題
過濾器:CharacterEncodingFilter.java
public class CharacterEncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
// 這里我們先解決post方式的亂碼問題
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 解決get方式的亂碼問題
MyCharacterEncodingRequest requestWrapper = new MyCharacterEncodingRequest(
request);
chain.doFilter(requestWrapper, response);
}
@Override
public void destroy() {
}
}
class MyCharacterEncodingRequest extends HttpServletRequestWrapper {
// 這里我們使用一個變量記住Servlet傳遞的request對象。
private HttpServletRequest request;
public MyCharacterEncodingRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
@Override
// 增強此方法,此方法得到表單提交的數據
public String getParameter(String name) {
String value = this.request.getParameter(name);
if (value == null) {// 如果為空直接返回空即可
return null;
}
if (!this.request.getMethod().equalsIgnoreCase("get")) {
// 如果不是get方式則沒必要轉換
return value;
}
try {
/*
* value = new
* String(value.getBytes("ISO8859-1"),"UTF-8");//手工轉換,不要寫死
*/
value = new String(value.getBytes("ISO-8859-1"),
this.request.getCharacterEncoding());// 和Request設置的編碼一致
return value;// 這里使用ISO-8859-1沒有解決亂碼,這里瀏覽器
// 提交的本就是UTF-8編碼,不需要轉換
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
說明:由于使用request.setCharacterEncoding("UTF-8");
方式對get方式提交的數據無效,所以之前那個過濾器只能解決post方式提交的數據亂碼問題。既然這種方式不能解決get方式提交的數據的亂碼問題,那么我們可以將HttpServletRequest包裝之后再給用戶使用。這里我們主要是對其方法getParameter
進行增強。這樣就解決了get方式提交的亂碼問題。
3.2 包裝request對象,實現html標簽轉義功能
(E:\apache\apache-tomcat-8.0.28-src\webapps\examples\WEB-INF\classes\util\HTMLFilter.java
提供相應的例子)
HtmlFilter .java
public class HtmlFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
MyHtmlRequest myHtmlRequest = new MyHtmlRequest(request);
chain.doFilter(myHtmlRequest, response);
}
@Override
public void destroy() {
}
}
class MyHtmlRequest extends HttpServletRequestWrapper{
private HttpServletRequest request;
public MyHtmlRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
@Override
public String getParameter(String name) {
String value = this.request.getParameter(name);
if(value == null){
return null;
}
return filter(value);
}
public static String filter(String message) {
if (message == null)
return (null);
char content[] = new char[message.length()];
message.getChars(0, message.length(), content, 0);
StringBuilder result = new StringBuilder(content.length + 50);
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case '<':
result.append("<");
break;
case '>':
result.append(">");
break;
case '&':
result.append("&");
break;
case '"':
result.append(""");
break;
default:
result.append(content[i]);
}
}
return (result.toString());
}
}
3.3 包裝request對象,實現對臟話進行過濾
DirtyFilter.java
public class DirtyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
DirtyRequest dirtyRequest = new DirtyRequest(request);
chain.doFilter(dirtyRequest, response);
}
@Override
public void destroy() {
}
}
class DirtyRequest extends HttpServletRequestWrapper{
private List<String> dirtyWords = Arrays.asList("sb","畜生");
private HttpServletRequest request ;
public DirtyRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
@Override
public String getParameter(String name) {
String value = this.request.getParameter(name);
if(value == null){
return null;
}
for(String dirtyWord : dirtyWords){
if(value.contains(dirtyWord)){
value = value.replace(dirtyWord, "***");
}
}
return value;
}
}
四、包裝response對象
4.1 包裝response對象,實現壓縮響應
GzipFilter.java
package cn.itcast.web.filter;
//解決全站的壓縮問題,對Response進行增強,但是這個過濾器只是解決壓縮字符,需要把數據都寫到內存中去,如果是下載一個大文件,內存可能會崩,這里我們可以在
//配置文件中設定只對字符壓縮有效,這攔截jsp,js,css,html
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
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.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
public class GzipFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
BufferResponse myResponse = new BufferResponse(response);
chain.doFilter(request, myResponse);
byte out[] = myResponse.getBuffer();//先獲得數據
ByteArrayOutputStream bout = new ByteArrayOutputStream();
GZIPOutputStream gout = new GZIPOutputStream(bout);//進行壓縮
gout.write(out);
gout.close();//一定要關閉,這樣數據才會從緩存中寫入到底層流中去
byte gzip[] = bout.toByteArray();//從底層流中取得數據
response.setHeader("content-encoding", "gzip");//這里需要告訴瀏覽器這是一個壓縮數據
response.setContentLength(gzip.length);
response.getOutputStream().write(gzip);//寫出到瀏覽器
}
@Override
public void destroy() {}
}
/*OutputStream out = response.getOutputStream();
out.write("aaaaaa".getBytes());
*之后servlet在使用Response的時候其實是使用我們自己定義的Response,而調用的getOutputStream方法也是我們自己定義的,這個方法返回的是
*MyServletOutputStream,然后調用write方法也是調用我們自己定義的write方法,此方法是將數據寫到ByteArrayOutputStream底層流中。
*
*/
class BufferResponse extends HttpServletResponseWrapper{
private HttpServletResponse response;
private ByteArrayOutputStream bout = new ByteArrayOutputStream();//字節流
private PrintWriter pw;
public BufferResponse(HttpServletResponse response) {
super(response);
this.response = response;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
//這里我們對此方法進行了增強,不管是圖片還是文本等數據都會進行壓縮,但是如果直接訪問jsp卻不會,因為jsp一般是調用getWriter
//方法,所以這里我們需要對getWriter方法進行增強
return new MyServletOutputStream(bout);
}
@Override
public PrintWriter getWriter() throws IOException {
/*return new PrintWriter(bout);//因為PrintWriter有接受一個底層流的構造函數,所以這里我們不需要重寫一個,但是這個方法也是一個包裝類
//這個類當緩存沒有寫滿的時候是不會講數據寫到底層流中去,所以這里我們需要強制關閉此類*/
//pw = new PrintWriter(bout);//jsp中的漢字是一個字符流,這里會將其先轉換為字節流,查的碼表是gb2312的碼表,但是我們設置的碼表是UTF-8
//而PrintWriter有一個接受一個字符流的方法,而字符流就會有設置碼表的方法,而OutputStreamWriter是字符流到字節流的一個轉換流,里面就可以指定碼表
pw = new PrintWriter(new OutputStreamWriter(bout,this.response.getCharacterEncoding()));
return pw;
}
public byte[] getBuffer(){
try{
if(pw != null){
pw.close();
}
if(bout != null){
bout.flush();
return bout.toByteArray();
}
return null;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class MyServletOutputStream extends ServletOutputStream{
private ByteArrayOutputStream bout ;
public MyServletOutputStream(ByteArrayOutputStream bout) {
this.bout = bout;
}
@Override
public void write(int arg0) throws IOException {
this.bout.write(arg0);
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener listener) {}
}
說明:
- 這個包裝類的實現有點難理解,這里我們詳細說明一下。這里是實現數據的壓縮之后再輸出給瀏覽器,而我們完全可以在servlet中先從緩存中拿到數據壓縮之后再輸出,但是那樣的話每個需要壓縮資源的servlet都需要編寫重復的代碼,所以這里我們使用過濾器進行簡化。
- 首先服務器將資源寫給瀏覽器的時候(注意這里和request過濾器的方向是反的),會被這個瀏覽攔截到。攔截到之后我們對response進行增強。
- 而一般會調用
getOutputStream
方法和getWriter
方法向瀏覽器中寫數據,于是這里我們對這兩個方法進行增強。 - 方法
getOutputStream
會返回一個ServletOutputStream
流,我們需要增強,我們要讓此方法返回一個我們自己定義的一個流,于是對此類也進行包裝。 - 方法
getWriter
中我們將自己定義的流傳遞給PrintWriter
方法。 - 我們將數據壓縮之后存入到底層流中,之后用戶在調用
getOutputStream
方法和getWriter
時拿到的數據就是我們壓縮之后的數據。 - 其實整個過程就是當用戶調用方法向瀏覽器輸出數據的時候我們將response的相關方法進行增強(實現數據壓縮)之后再去調用真正response的方法進行輸出,這樣就可以實現壓縮。
配置:
<filter>
<filter-name>GzipFilter</filter-name>
<filter-class>cn.itcast.web.filter.GzipFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.js</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.css</url-pattern>
</filter-mapping>
注意:這里我們需要配置FORWARD和REQUEST,用于攔截forward請求。因為大多數時候我們都是轉發過來的請求。
4.2 包裝response對象,緩存數據到內存
CacheFiltet.java
public class CacheFilter implements Filter {
//實際開發中我們可以使用一些專業的緩存工具
private Map<String, byte[]> map = new HashMap<String, byte[]>();
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//1.得到用戶請求的uri
String uri = request.getRequestURI();
//2.看換出中有沒有uri對應的數據
byte b[] = map.get(uri);
//3.如果有,則直接拿緩存的數據送給瀏覽器,程序返回
if(b != null){
response.getOutputStream().write(b);
return ;
}
//4.如果沒有,讓目標資源執行,并捕獲目標資源的輸出
BufferResponse1 myresResponse1 = new BufferResponse1(response);
chain.doFilter(request, myresResponse1);
byte out[] = myresResponse1.getBuffer();
//5.把資源的數據以用戶請求的uri的關鍵字保存到緩存中
map.put(uri, out);
//6.把數據送給瀏覽器
response.getOutputStream().write(out);
}
@Override
public void destroy() {}
}
class BufferResponse1 extends HttpServletResponseWrapper{
private HttpServletResponse response;
private ByteArrayOutputStream bout = new ByteArrayOutputStream();//字節流
private PrintWriter pw;
public BufferResponse1(HttpServletResponse response) {
super(response);
this.response = response;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return new MyServletOutputStream1(bout);
}
@Override
public PrintWriter getWriter() throws IOException {
pw = new PrintWriter(new OutputStreamWriter(bout,this.response.getCharacterEncoding()));
return pw;
}
public byte[] getBuffer(){
try{
if(pw != null){
//如果pw不為空,則我們需要關閉一下,讓其將數據從緩存寫到底層流中去
pw.close();
}
if(bout != null){
bout.flush();
return bout.toByteArray();
}
return null;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class MyServletOutputStream1 extends ServletOutputStream{
private ByteArrayOutputStream bout ;
public MyServletOutputStream1(ByteArrayOutputStream bout) {
this.bout = bout;
}
@Override
public void write(int arg0) throws IOException {
//其實write方法有三種重載形式,但是其內部都是調用的這種形式,所以我們只需要重載這種形式即可
this.bout.write(arg0);
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener listener) {}
}
說明:
- 對于頁面中很少更新的數據,例如商品分類,為避免每次都要從數據庫查詢分類數據,因此可以把分類數據緩存在內存或文件中,以此來減輕數據庫壓力,提高系統響應速度。相關書名直接看程序中的注釋即可,這里不再細說。
五、動態代理
在java里,每個對象都有一個類與之對應。
現在要生成某一個對象的代理對象,這個代理對象也要通過一個類來生成,所以首先要編寫用于生成代理對象的類。
-
如何編寫生成代理對象的類,兩個要素:
- 代理誰
- 如何生成代理對象
代理誰?
設計一個類變量,以及一個構造函數,記住代理類代理哪個對象。如何生成代理對象?
設計一個方法生成代理對象(在方法內編寫代碼生成代理對象是此處編程的難點)-
java提供了一個Proxy類,調用它的newInstance方法可以生成某個對象的代理對象,使用該方法生成代理對象時,需要三個參數:
- 1.生成代理對象使用哪個類裝載器
- 2.生成哪個對象的代理對象,通過接口指定
- 3.生成的代理對象的方法里干什么事,由開發人員編寫handler接口的實現來指定。
-
初學者必須理解(記住)
- Proxy類負責創建代理對象時,如果指定了handler(處理器),那么不管用戶調用代理對象的什么方法,該方法都是調用處理器的invoke方法。
- 由于invoke方法被調用需要三個參數:代理對象、方法、方法的參數,因此不管代理對象哪個方法調用處理器的invoke方法,都必須把自己所在的對象、自己(調用invoke方法的方法)、方法的參數傳遞進來。
六、動態代理應用
在動態代理技術里,由于不管用戶調用代理對象的什么方法,都是調用開發人員編寫的處理器的invoke方法(這相當于invoke方法攔截到了代理對象的方法調用)。
并且,開發人員通過invoke方法的參數,才可以在攔截的同時,知道用戶調用的是什么方法,因此利用這兩個特征,就可以實現一些特殊需求。例如:攔截用戶的訪問請求,以檢查用戶是否有訪問權限、動態為某個對象添加額外的功能。