面向切面,法力無邊,任何臟活累活,都可以從干干凈凈整齊划一的業務代碼中抽出來,無非就是加一層,項目里兩個步驟間可以被分層的設計滲透成篩子。
舉個例子:
最近我們對接某銀行接口,我們的web服務都是標准的restful請求,所以所有的controller層代碼都是整整齊齊的
@Slf4j
@RestController
@RequestMapping("xxx")
@NoticeGroup
public class XXXController {
@Autowired
XXXService xxxService;
@PostMapping("/fff")
public XXXRespVo fff(@RequestBody xxxReqVo reqVo){
log.info("傳入參數:{}",reqVo);
return xxxService.doSomething(reqVo));
}
}
這樣的話請求就是標准的Post ,json ,
不成想,該銀行回調我方的報文竟然是一串字符串,而且請求頭是 text/plain,好家伙所有的請求都要改,這不成,隊伍要整齊,來加一層在過濾器中,不修改的話調用邏輯如此?
我們拿到報文后需要先進行解密,好的,如果有100個接口那解密100次也不現實,
-
我們可以自定義一個過濾器,在過濾器中將報文解密重新塞回請求體
-
實現起來還是比較簡單的上網上摘抄即可:
由於request請求體流讀一次之后就不能再讀了,所以要做一個包裝類,相當於將斷開的流重新接起來,好比再管道上打個口子,取出東西來再放回這個東西。但是我們對這個東西做了點什么,什么也不做也行,就只是檢查下。
參考代碼
public class RequestWrapper extends HttpServletRequestWrapper {
private String body;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
StringBuilder sb =new StringBuilder();
InputStream inputStream=null;
BufferedReader reader=null;
try{
inputStream=cloneInputStream(request.getInputStream());
reader =new BufferedReader(new InputStreamReader(inputStream));
String line="";
while((line=reader.readLine())!=null){
sb.append(line);
}
}catch (IOException e){
e.printStackTrace();
}finally {
if (inputStream != null) {
try {
inputStream.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
body=sb.toString();
}
public InputStream cloneInputStream(ServletInputStream inputStream) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer)) > -1) {
byteArrayOutputStream.write(buffer, 0, len);
}
byteArrayOutputStream.flush();
}
catch (IOException e) {
e.printStackTrace();
}
InputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
return byteArrayInputStream;
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
ServletInputStream servletInputStream = new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
return servletInputStream;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
public String getBody() {
return this.body;
}
public void setBody(String body) throws UnsupportedEncodingException {
this.body = body;
}
}
- 過濾器類怎么寫呢?如下,@WebFilter(urlPatterns = "/XXX/") 注解起的作用是攔截匹配真個url的請求,這里需要再啟動類里面配套加一個@ServletComponentScan(basePackages = "xxx.xxx.xxx.")
不加的話會把所有的請求都攔截,我在當初的某一版本上就沒加,只能在下面doFilter方法里面加補丁,判斷請求url是不是我想要的否則用不走解密的邏輯
@Order(12)
@Slf4j
@WebFilter(urlPatterns = "/XXX/*")
public class XXXFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
try {
RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) request);
String bodyString = requestWrapper.getBody();
requestWrapper.setBody(decrypt(bodyString));
try {
filterChain.doFilter(requestWrapper, servletResponse);
} catch (Exception e) {
log.info("業務中的異常", e.getMessage());
throw e;
}
} catch (Exception e) {
e.printStackTrace();
}
}
@SneakyThrows
public String decrypt(String encrypedStr) {
String result = "根據密文解密后的數據巴拉巴拉";
return result;
}
}
- 這樣程序運行的邏輯就成如下,我們在攔截器中把報文變漂亮了。返回的時候也可以在這里處理,嘿嘿,我們在對外提供服務的時候有使用需要把返回的東西加密,就在這里處理了。
- 這樣只是解決的報文明文的問題,還沒有解決報文頭,但是我們能修改報文體就能修改報文頭不是么?代碼如下: 塞進requestWapper里面
@Override
public Enumeration<String> getHeaders(String name) {
List<String> list=Collections.list(super.getHeaders(name));
//處理一下大小寫,可能是content-type 或 Content-Type
if(name.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE)){
list=new ArrayList<>();
list.add(MediaType.APPLICATION_JSON_VALUE);
}
Enumeration<String> re= Collections.enumeration(list);
return re;
}
- 為啥這么寫呢? debug追溯可以找到類:AbstractMessageConverterMethodArgumentResolver
@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
MediaType contentType;
boolean noContentType = false;
try {
// 這里的inputMessage
contentType = inputMessage.getHeaders().getContentType();
}
...
}
@Override
public HttpHeaders getHeaders() {
if (this.headers == null) {
this.headers = new HttpHeaders();
for (Enumeration<?> names = this.servletRequest.getHeaderNames(); names.hasMoreElements();) {
String headerName = (String) names.nextElement();
for (Enumeration<?> headerValues = this.servletRequest.getHeaders(headerName);
headerValues.hasMoreElements();) {
String headerValue = (String) headerValues.nextElement();
this.headers.add(headerName, headerValue);
}
}
......
}
看上面的代碼,這里是 requestWapper 作為構造參數在創建inputMessage的時候弄進去的。 this.servletRequest==requestWapper, 這樣 this.headers = new HttpHeaders();其實取值的時候就是
this.servletRequest.getHeaders(headerName); 這段代碼了,我們wapper中重寫的也就是這一塊,將原先的報文頭強行修改為了application/json。
到此就完成了。
Enumeration是個啥呢?
Enumeration接口中定義了一些方法,通過這些方法可以枚舉(一次獲得一個)對象集合中的元素。
這種傳統接口已被迭代器取代,雖然Enumeration 還未被遺棄,但在現代代碼中已經被很少使用了。盡管如此,它還是使用在諸如Vector和Properties這些傳統類所定義的方法中,除此之外,還用在一些API類,並且在應用程序中也廣泛被使用。
public interfaceEnumeration<E> {
/**
* Tests if this enumeration contains more elements.
*
*@return<code>true</code>if and only if this enumeration object
* contains at least one more element to provide;
*<code>false</code>otherwise.
*/
booleanhasMoreElements();
/**
* Returns the next element of this enumeration if this enumeration
* object has at least one more element to provide.
*
*@returnthe next element of this enumeration.
*@exceptionNoSuchElementExceptionif no more elements exist.
*/
E nextElement();
}
實現類雖然多但是沒有我認識的呵呵,
Collections.enumeration(list); 這個方法,如下,看來就是用Iterator 來實現。這種寫法還挺妙的。
public static <T> Enumeration<T> enumeration(final Collection<T> c) {
return new Enumeration<T>() {
private final Iterator<T> i = c.iterator();
public boolean hasMoreElements() {
return i.hasNext();
}
public T nextElement() {
return i.next();
}
};
}