requestWrapper的用法

定义

requestWrapper实际上是一种装饰器模式的类。该类位于javax.servlet.http下,也是Java官方提供的包,与HttpServletRequest等位于同一个包下。他本身并没有任何功能,其会直接调用包装类的的方法。

作用

该类的主要作用就是提供给开发者一个自定义request的父类。由于其功能是直接调用包装类,所以开发者可以直接继承该类,然后自定义改变原来request中的默认方法。

为什么不直接实现一个类似的包装类?

由于request存在很多的方法,开发者并不知道应用中会使用到哪些方法,所以就必须要像requestWrapper一样全部重写,不需要的自定义的方法还要使用super.xxx()来调用。这实际上是一种冗余的行为。而这一部分由装饰器类来实现,我们直接继承该类,然后去自定义需要修改的方法就行,就免去了不需要的方法重写。

应用

前面提到requestWrapper本身就是一个装饰器,我们可以自定义我们需要的方法,而这个类应用最广泛的地方莫过于request body的多次读取问题。详见SpringMVC参数解析全解与JSON属性分解

解决request body的多次读取问题

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/**
* @Auther: MichaelWang
* @Date: 2022/6/20 19:14
* @Description: XPanRequestWrapper
* @Version 1.0.0
*/
public class XPanRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;

/**
* Construct a wrapper for the specified request.
*
* @param request The request to be wrapped
*/
public XPanRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = IOUtils.toByteArray(super.getInputStream());
}

@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}

@Override
public ServletInputStream getInputStream() throws IOException {
return new RequestBodyCachingInputStream(body);
}

private class RequestBodyCachingInputStream extends ServletInputStream {
private byte[] body;
private int lastIndexRetrieved = -1;
private ReadListener listener;

public RequestBodyCachingInputStream(byte[] body) {
this.body = body;
}

@Override
public int read() throws IOException {
if (isFinished()) {
return -1;
}
int i = body[lastIndexRetrieved + 1];
lastIndexRetrieved++;
if (isFinished() && listener != null) {
try {
listener.onAllDataRead();
} catch (IOException e) {
listener.onError(e);
throw e;
}
}
return i;
}

@Override
public boolean isFinished() {
return lastIndexRetrieved == body.length - 1;
}

@Override
public boolean isReady() {
// This implementation will never block
// We also never need to call the readListener from this method, as this method will never return false
return isFinished();
}

@Override
public void setReadListener(ReadListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener cann not be null");
}
if (this.listener != null) {
throw new IllegalArgumentException("listener has been set");
}
this.listener = listener;
if (!isFinished()) {
try {
listener.onAllDataRead();
} catch (IOException e) {
listener.onError(e);
}
} else {
try {
listener.onAllDataRead();
} catch (IOException e) {
listener.onError(e);
}
}
}

@Override
public int available() throws IOException {
return body.length - lastIndexRetrieved - 1;
}

@Override
public void close() throws IOException {
lastIndexRetrieved = body.length - 1;
body = null;
}
}
}

自定义返回的属性

例如,重写getParameter方法,如果遇到某个值就返回特定的值。

Java:

1
2
3
4
5
6
7
@Override
public String getParameter(String name) {
if(name.equals("xpan")){
return "xpan";
}
return super.getParameter(name);
}

结果:

1
2
request.getParameter("xpan");
--->"xpan"

但是这种用法并不常见,仅作为抛砖引玉。

切面做日志记录

由于这种包装模式,我们也可以用来为日志记录,即调用request的方法时,进行日志记录。

例如,重写getMethod方法,在返回方法之前,打印调用的的函数和结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public String getMethod() {
Throwable ex = new Throwable();
StackTraceElement[] stackElements = ex.getStackTrace();
StackTraceElement stackElement = stackElements[0];
System.out.println(stackElement.getClassName());//返回类的完全限定名,该类包含由该堆栈跟踪元素所表示的执行点。
System.out.println(stackElement.getFileName());//返回源文件名,该文件包含由该堆栈跟踪元素所表示的执行点。
System.out.println(stackElement.getLineNumber());//返回源行的行号,该行包含由该堆栈该跟踪元素所表示的执行点。
System.out.println(stackElement.getMethodName());//返回方法名,此方法包含由该堆栈跟踪元素所表示的执行点。
System.out.println("调用方法:getMethod"+"--- 结果为:"+ super.getMethod());//返回方法名,此方法包含由该堆栈跟踪元素所表示的执行点。
System.out.println("------------------------------------------------------------");
return super.getMethod();
}

结果:

1
2
3
4
5
6
priv.mw.xpan.utils.XPanRequestWrapper
XPanRequestWrapper.java
44
getMethod
调用方法:getMethod--- 结果为:GET
------------------------------------------------------------

总结

综合来说,这种装饰器模式大致就相当于AOP,可以劫持方法,达到Spring中AOP的效果。上面实际上就是缓存优化方法功能修改日志记录。所以在实际场景中,需要对request进行自定义的方法都可以通过该类进行代理。

Powered by Hexo and Hexo-theme-hiker

Copyright © 2019 - 2024 My Wonderland All Rights Reserved.

UV : | PV :