基础
关于版本选择
https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
Spring Cloud Version | Spring Cloud Alibaba Version | Spring Boot Version |
---|---|---|
Spring Cloud 2020.0.1 | 2021.1 | 2.4.2 |
Spring Cloud Hoxton.SR9 | 2.2.6.RELEASE | 2.3.2.RELEASE |
Spring Cloud Greenwich.SR6 | 2.1.4.RELEASE | 2.1.13.RELEASE |
Spring Cloud Hoxton.SR3 | 2.2.1.RELEASE | 2.2.5.RELEASE |
Spring Cloud Hoxton.RELEASE | 2.2.0.RELEASE | 2.2.X.RELEASE |
Spring Cloud Greenwich | 2.1.2.RELEASE | 2.1.X.RELEASE |
Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework | Spring Boot |
---|---|---|---|---|
2021.0 (Pascal) | 4.2.1 | 7.12.1 | 5.3.7 | 2.5.x |
2020.0 (Ockham) | 4.1.x | 7.9.3 | 5.3.2 | 2.4.x |
Neumann | 4.0.x | 7.6.2 | 5.2.12 | 2.3.x |
Moore | 3.2.x | 6.8.12 | 5.2.12 | 2.2.x |
Lovelace[1] | 3.1.x[1] | 6.2.2 | 5.1.19 | 2.1.x |
Kay[1] | 3.0.x[1] | 5.5.0 | 5.0.13 | 2.0.x |
Ingalls[1] | 2.1.x[1] | 2.4.0 | 4.3.25 | 1.5.x |
Springboot-Starter
https://start.spring.io/#!type=maven-project&language=java&platformVersion=2.3.2.RELEASE&packaging=jar&jvmVersion=1.8&groupId=com.intbee&artifactId=sys-base-api&name=sys-base-api&description=Demo%20project%20for%20Spring%20Boot&packageName=com.intbee.sys-base-api&dependencies=devtools,lombok,web,validation
Spring Cloud常见问题
Fegin/Ribbon的超时与重试配置
# readTimeout和connectTimeout必须同时配置方可生效
feign.client.config.default.readTimeout=3000
feign.client.config.default.connectTimeout=3000
feign.client.config.clientsdk.readTimeout=2000
feign.client.config.clientsdk.connectTimeout=2000
# 参数首字母要大写,和 Feign 的配置不同
# 同时配置 Feign 和 Ribbon 的超时,以 Feign 为准
ribbon.ReadTimeout=4000
ribbon.ConnectTimeout=4000
# GET 请求默认超时会重试一次
ribbon.MaxAutoRetriesNextServer=1
okhttp连接池
OkHttpClient.Builder builder = new OkHttpClient.Builder();
Dispatcher dispatcher = new Dispatcher();
//全部host并发数,默认64
dispatcher.setMaxRequests(dispatcherMaxRequests);
//单个host并发数,默认5
dispatcher.setMaxRequestsPerHost(dispatcherMaxRequestsPerHost);
builder.dispatcher(dispatcher);
//每个地址最大并发数,默认5,保留时间默认5分钟
ConnectionPool connectionPool = new ConnectionPool(connectionPoolMaxIdleCount, connectionPoolMaxIdleMinutes,
TimeUnit.MINUTES);
builder.connectionPool(connectionPool);
//链接超时,默认10秒
builder.connectTimeout(connTimeout, TimeUnit.SECONDS);
//读超时,默认10秒
builder.readTimeout(readTimeout, TimeUnit.SECONDS);
//写超时默认10秒
builder.writeTimeout(writeTimeout, TimeUnit.SECONDS);
常见问题
对象复制
org.springframework.cglib.beans.BeanCopier:基于cglib
org.springframework.beans.BeanUtils:基于反射机制
AopContext.currentProxy()无法获取问题
@EnableAspectJAutoProxy(exposeProxy = true,proxyTargetClass = true) # 开启Cglib代理
@Async无效,不起作用
1.没有在@SpringBootApplication启动类当中添加注解@EnableAsync注解。
2.异步方法使用注解@Async的返回值只能为void或者Future。
3.没有走Spring的代理类。因为@Transactional和@Async注解的实现都是基于Spring的AOP,而AOP的实现是基于动态代理模式实现的。
解决办法:
1.注解的方法必须是public方法。
2.方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的。
3.如果需要从类的内部调用,需要先获取其代理类-手动获取spring bean再调用
dynamic-datasource无事务
注意:开启Spring事务无法切换数据源
dynamic-datasource-spring-boot-starter不支持原生Spring事务,不过在第三方seata的支持下可用
https://github.com/baomidou/dynamic-datasource-spring-boot-starter/wiki/Integration-With-Seata
@Transactional不起作用
1、同一类中,一个方法调用另外一个有事务的方法,事务不起作用
2、同一类中,一个有事务的方法调用另外一个方法,另外的方法内的事务不起作用
3、只有运行时异常才能触发事务
4、多个数据库源的情况下事务不起作用
解决版本:
1、两个同时需要事务的方法不要写在同一个类中
2、把事务的注释写在类上
3、注入本身调用,注意添加@Lazy
spring-data-mongo使用问题
保存出现重复保存问题
如果Document中包含version字段时,在进行更新操作version必须保持一致,否则会当做一条新数据进行插入
MongoTemplate查询字段名需和数据库一致
使用Query、Criteria、Update时必须和数据库字段名称保持一致,如ID使用 _id
hibernate.validator统一参数校验问题
- 对于Get参数必须在类上添加@Validated注解
- 对于Post参数在参数或方法添加@Valid注解
- 校验类提示放在resouces目录下ValidationMessages_zh_CN.properties,占位符使用{0…}
- 提示类消息放在resouces/i18n目录下messages_zh_CN.properties,占位符使用{0…}
-
RestTemplate使用Okhttp3
@Slf4j @Configuration @ConfigurationProperties(prefix = "spring.httpclient.okhttp") public class RestTemplateConfig { private int maxIdleConnections = 50; private int keepAliveDuration = 600; private int connectTimeout = 30; private int readTimeout = 40; private int writeTimeout = 600; @Bean public RestTemplate restTemplate(ClientHttpRequestFactory factory) { return new RestTemplate(factory); } @Bean public ClientHttpRequestFactory simpleClientHttpRequestFactory() { log.info( "spring.httpclient.okhttp Configuration [maxIdleConnections={},keepAliveDuration={},connectTimeout={},readTimeout={},writeTimeout={}]", maxIdleConnections, keepAliveDuration, connectTimeout, readTimeout, writeTimeout); OkHttpClient okHttpClient = new OkHttpClient.Builder() .connectionPool(new ConnectionPool(maxIdleConnections, keepAliveDuration, TimeUnit.SECONDS)) .connectTimeout(connectTimeout, TimeUnit.SECONDS).readTimeout(readTimeout, TimeUnit.SECONDS) .writeTimeout(writeTimeout, TimeUnit.SECONDS).build(); return new OkHttp3ClientHttpRequestFactory(okHttpClient); } }
统一响应格式
```java @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE }) public @interface WapperResult {
}
@Data @Accessors(chain = true) public class Result { private int code = 0; private String msg = “SUCCESS”; private String errcode; private String errmsg; private Object data; private long timestmp = System.currentTimeMillis() / 1000;
public static Result ok(Object data) {
return new Result().setData(data);
}
}
@RestControllerAdvice public class WapperResultHandlerAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return null != returnType.getMethodAnnotation(WapperResult.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 判断响应的Content-Type为JSON格式的body
if (MediaType.APPLICATION_JSON.equals(selectedContentType)
|| MediaType.APPLICATION_JSON_UTF8.equals(selectedContentType)) {
if (body instanceof Result) { // 如果响应返回的对象为统一响应体,则直接返回body
return body;
}
// 只有正常返回的结果才会进入这个判断流程,所以返回正常成功的状态码
return Result.ok(body);
}
// 非JSON格式body直接返回即可
return body;
}
}
<a name="EBj1b"></a>
### 统一脱敏处理
```java
import java.util.function.Function;
/**
* 脱敏类型
*/
public enum SensitiveType {
/**
* Username sensitive strategy.
*/
USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),
/**
* Id card sensitive type.
*/
ID_CARD(s -> s.replaceAll("(\\d{4})\\d{2,10}(\\w{4})", "$1****$2")),
/**
* Phone sensitive type.
*/
PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
/**
* Address sensitive type.
*/
ADDRESS(s -> s.replaceAll("(\\S{3})\\S{3}(\\S*)\\S{2}", "$1****$2****"));
private final Function<String, String> function;
SensitiveType(Function<String, String> function) {
this.function = function;
}
/**
* @param text
* 待脱敏字符串
* @return 脱敏处理结果
*/
public String apply(String text) {
return function.apply(text);
}
}
//-----------
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
/**
* JSON 脱敏注解
*/
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveSerialize.class)
public @interface Sensitive {
SensitiveType value();
}
//-----------
import java.io.IOException;
import java.util.Objects;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
/**
*
* jackson 字段脱敏处理
*
*/
public class SensitiveSerialize extends JsonSerializer<String> implements ContextualSerializer {
private SensitiveType type;
public SensitiveSerialize(SensitiveType type) {
this.type = type;
}
public SensitiveSerialize() {
}
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers)
throws IOException, JsonProcessingException {
gen.writeString(type.apply(value));
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException {
if (property == null) {
// 为空直接跳过
return prov.findNullValueSerializer(property);
}
if (Objects.equals(property.getType().getRawClass(), String.class)) {
Sensitive sensitive = property.getAnnotation(Sensitive.class);
if (sensitive == null) {
sensitive = property.getContextAnnotation(Sensitive.class);
}
if (sensitive != null) {
return new SensitiveSerialize(sensitive.value());
}
}
return prov.findValueSerializer(property.getType(), property);
}
}
//使用示例
public class DemoVO {
@Sensitive(SensitiveType.USERNAME)
private String name;
@Sensitive(SensitiveType.PHONE)
private String mobile;
@Sensitive(SensitiveType.ID_CARD)
private String idcard;
@Sensitive(SensitiveType.ADDRESS)
private String address;
}
统一异常处理
/**
* 必须设置:spring.mvc.throw-exception-if-no-handler-found=true
*/
@ExceptionHandler(NoHandlerFoundException.class)
public ResultVO<String> handlerNoFoundException(NoHandlerFoundException e) {
log.error("NoFoundException: {}", e.getMessage());
return ResultVO.error(404, "路径不存在,请检查路径是否正确");
}
springboot 实现拦截
Filter
@Bean
public FilterRegistrationBean customerFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
// 设置过滤器
registration.setFilter(new CustomerFilter());
// 拦截路由规则
registration.addUrlPatterns("/intercept/*");
// 设置初始化参数
registration.addInitParameter("name", "customFilter");
registration.setName("CustomerFilter");
registration.setOrder(1);
return registration;
}
public class CustomerFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(CustomerFilter.class);
private String name;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
name = filterConfig.getInitParameter("name");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
logger.info("Filter {} handle before", name);
chain.doFilter(request, response);
logger.info("Filter {} handle after", name);
}
}
@WebFilter
需要配合@ServletComponentScan才能生效
@Component
@ServletComponentScan
@WebFilter(urlPatterns = "/intercept/*", filterName = "annotateFilter")
public class AnnotateFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(AnnotateFilter.class);
private final String name = "annotateFilter";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
logger.info("Filter {} handle before", name);
chain.doFilter(request, response);
logger.info("Filter {} handle after", name);
}
}
HanlderInterceptor
public class CustomHandlerInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(CustomHandlerInterceptor.class);
/*
* Controller方法调用前,返回true表示继续处理
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
HandlerMethod method = (HandlerMethod) handler;
logger.info("CustomerHandlerInterceptor preHandle, {}", method.getMethod().getName());
return true;
}
/*
* Controller方法调用后,视图渲染前
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
HandlerMethod method = (HandlerMethod) handler;
logger.info("CustomerHandlerInterceptor postHandle, {}", method.getMethod().getName());
response.getOutputStream().write("append content".getBytes());
}
/*
* 整个请求处理完,视图已渲染。如果存在异常则Exception不为空
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
HandlerMethod method = (HandlerMethod) handler;
logger.info("CustomerHandlerInterceptor afterCompletion, {}", method.getMethod().getName());
}
}
@Configuration
public class InterceptConfig extends WebMvcConfigurerAdapter {
// 注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CustomHandlerInterceptor()).addPathPatterns("/intercept/**");
super.addInterceptors(registry);
}
@ExceptionHandler
用途是捕获方法执行时抛出的异常,通常可用于捕获全局异常,并输出自定义的结果。
需要与 @ControllerAdvice配合使用
@ControllerAdvice(assignableTypes = InterceptController.class)
public class CustomInterceptAdvice {
private static final Logger logger = LoggerFactory.getLogger(CustomInterceptAdvice.class);
/**
* 拦截异常
*/
@ExceptionHandler(value = { Exception.class })
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
public String handle(Exception e, HandlerMethod m) {
logger.info("CustomInterceptAdvice handle exception {}, method: {}", e.getMessage(), m.getMethod().getName());
return e.getMessage();
}
}
RequestBodyAdvice/ResponseBodyAdvice
RequestBodyAdvice 则可用于在请求内容对象转换的前后时刻进行拦截处理
ResponseBodyAdvice 的用途在于对返回内容做拦截处理
@Aspect
@Aspect
@Component
public class InterceptControllerAspect {
private static final Logger logger = LoggerFactory.getLogger(InterceptControllerAspect.class);
@Pointcut("target(org.zales.dmo.boot.controllers.InterceptController)")
public void interceptController() {
}
@Around("interceptController()")
public Object handle(ProceedingJoinPoint joinPoint) throws Throwable {
logger.info("aspect before.");
try {
return joinPoint.proceed();
} finally {
logger.info("aspect after.");
}
}
}
@Pointcut 用于定义切面点,而使用target关键字可以定位到具体的类。
@Around 定义了一个切面处理方法,通过注入ProceedingJoinPoint对象达到控制的目的
注解 | 说明 |
---|---|
@Before | 方法执行之前 |
@After | 方法执行之后 |
@Around | 方法执行前后 |
@AfterThrowing | 抛出异常后 |
@AfterReturing | 正常返回后 |
多线程并发测试-junit5
添加配置文件:junit-platform.properties
#是否允许并行执行true/false junit.jupiter.execution.parallel.enabled=true #是否支持方法级别多线程 same_thread/concurrent junit.jupiter.execution.parallel.mode.default = concurrent #是否支持类级别多线程 same_thread/concurrent junit.jupiter.execution.parallel.mode.classes.default = concurrent #the maximum pool size can bu configured using a ParalleExecutionConfigurationStrategy junit.jupiter.execution.parallel.config.strategy=fixed junit.jupiter.execution.parallel.config.fixed.parallelism = 200
使用@RepeatedTest
@DisplayName("并发测试") @RepeatedTest(10000) // 10为当前用例执行的次数 void batchTest() { System.out.println("Thread-" + Thread.currentThread().getId()); }