自定义RequestBodyAdvice实现接口打印

注意:只能接受@RequestBody类型的input参数。

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.example.demo.demos.web;

import org.springframework.web.bind.annotation.*;

@RestController
public class BasicController {

@RequestMapping("/hello")
@ResponseBody
public String hello(@RequestBody String name) {
return "Hello " + name;
}
}

Advice

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
package com.example.demo.demos.web;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Arrays;

@Slf4j
@RestControllerAdvice
public class RequestBodyAdviceImpl implements RequestBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}

@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
RequestMapping requestMapping = parameter.getMethodAnnotation(RequestMapping.class);
log.info("url: " + Arrays.toString(requestMapping.value()));
log.info("method: " + parameter.getExecutable());
return inputMessage;
}

@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
log.info("param: " + body);
return body;
}

@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
log.info("body: body is Empty");
return body;
}
}

执行结果

Junit测试通过,MavenTest测试未通过

Run Unit Test和Maven test的区别

差异1:

在IDE中通过选中单元测试路径,点击右键选择run test和点击maven中的test是有区别的。在Maven执行测试的过程中,是不允许测试cases访问其他项目的测试类和其他项目的resources下文件的。也就是说,在a/src/test/java下的测试用例,是不能引用b/src/test/java中的类的,同时也不允许访问b/src/test/resources下的资源的。但是在IDE中的Run Unit Test几乎是没有这样的限制的。

差异2:

Maven强制要求src/test/java下不能存在resource的文件,必须放到src/test/reources文件夹下,但是IDE却很少有对应的约束。

解决方法

在maven插件配置:(surefire2.14以下版本)

1
2
3
4
5
6
7
8
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12</version>
<configuration>
<forkMode>always</forkMode>
</configuration>
</plugin>

重点加入configureation的配置部分

在maven插件配置:(surefire2.14及其以上版本)

1
2
3
4
5
6
7
8
9
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<reuseForks>false</reuseForks>
<forkCount>1</forkCount>
</configuration>
</plugin>

在2.14以上的版本中,forkMode配置项已经废弃了。

Java流内容替换

业务上有需求,需要将日语外字替换成指定字符,利用FilterInputStream实现。

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
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

public class App {
public static void main(String[] args) throws Exception {
String str = "abc";
InputStream stream = new ByteArrayInputStream(str.getBytes());
// abc -> acc
replaceString(stream).stream().forEach( elt -> System.out.println(elt));
}

private static List<String> replaceString(InputStream inputStream) throws UnsupportedEncodingException {
List<String> result = new ArrayList<>();
FilterInputStream filterInputStream = new FilterInputStream(inputStream) {
@Override
public int read(byte[] b, int off, int len) throws IOException {
int bytesRead = super.read(b, off, len);
if (bytesRead != -1) {
for (int i = off; i < off + bytesRead - 1; i++) {
// ab -> ac
if (b[i] == (byte) 0x61 && b[i + 1] == (byte) 0x62) {
b[i] = (byte) 0x61;
b[i + 1] = (byte) 0x63;
}
}
}
return bytesRead;
}
};

BufferedReader br = new BufferedReader(new InputStreamReader(filterInputStream, "UTF8"));
br.lines().forEach( elt -> {
result.add(elt);
});
return result;
}
}

java外字处理爬坑

背景

日语内外字不在常规Unicode编码集内,需要手动更换成编码集内的文字。

坑点

外字在本地文档/服务器文档编码集符合文档提供的规则,但是在Stream流内会自动解析成\\uFFFD\\uxxxx。例如SJIS编码下F141的文字,在UTF8下编码为E08D,但是进入Stream流后变成了\\uFFFD\\u0041,所以要在代码内实际确认一下对应的编码。同时因为外字转成了\\uFFFD\\uxxxx,所以位数变成了2位,而且外字变换常规字符后可能会由一个字符变成多个字符,对应Byte的切分要注意位数变更。

相关代码段

打印字符串unicode编码

1
2
3
4
5
6
7
public static void printUnicode(String str) {
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
System.out.printf(String.format("\\u%04X ", (int) c));
}
System.out.println();
}

变换外字

1
2
3
4
5
6
7
8
9
10
Map<String, String> convertMap = new HashMap<>(); 
convertMap.put("\\uFFFD\\u0041", "さい");
convertMap.put("\\uFFFD\\u0042", "そね");

for (Map.Entry<String, String> entry : convertMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
// repaceAll支持直接变换Unicode
String info = info.replaceAll(key, value);
}

final无法阻止数组值变换

final是引用不可变,值还是可以改变的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class App {

public static final String[] array = {"1", "2"};

public static void main(String[] args) throws Exception {

for (int i = 0; i < array.length; i ++) {
System.out.print(array[i] + " ");
}
System.out.print("\n");
array[0] = "3";
for (int i = 0; i < array.length; i ++) {
System.out.print(array[i] + " ");
}

}
}
// 输出结果
// 1 2
// 3 2

web.xml中load-on-startup的作用

  1. load-on-startup 元素标记容器是否应该在web应用程序启动的时候就加载这个servlet,(实例化并调用其init()方法)。
  2. 它的值必须是一个整数,表示servlet被加载的先后顺序。
  3. 如果该元素的值为负数或者没有设置,则容器会当Servlet被请求时再加载。
  4. 如果值为正整数或者0时,表示容器在应用启动时就加载并初始化这个servlet,值越小,servlet的优先级越高,就越先被加载。值相同时,容器就会自己选择顺序来加载。

SpringBoot解决跨域问题

跨域问题

前后端分离的时候出现了跨域问题。。。。虽然可以用Jsonp的方式解决,但是axios推荐利用CORS方式解决。

解决方法

添加一个拦截器

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
package com.wordcard.filter;

import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @author 19745
*/
@Component
public class CORSFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE,PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "accept,x-requested-with,Content-Type");
response.setHeader("Access-Control-Allow-Credentials", "true");
chain.doFilter(req, res);
}

@Override
public void destroy() {

}

}

NotNull等校验注解不生效

背景

学习SpringBoot项目中,单元测试时发现@NotNull等注解没有生效。

原因

没有在调用处添加@Validated@Valid注解。

示例

Entity

1
2
3
4
public Class User {
@NotBlank(message = "用户名不能为空")
private String username;
}

调用

  1. Controller类上添加@Validated注解。
  2. 如果是Entity类型的校验,需要在参数前加上@Valid。普通类型(如String)则不用。
1
2
3
4
5
@Validated
public class UserController {
public String getUsername(@Valid User user) {}
public String getStr(@NotNull String str) {}
}