网站首页 > 技术文章 正文
目前比较流行的架构大多都是前后端分离,后端提供服务能力,供前端调用打造业务场景功能。前后端根据定义好的协议报文进行数据交互,为了方便处理,一般报文都会是标准化格式,如下:
{
"Code": "string",//成功或失败码值
"ErrorMsg": "string",//失败描述,成功的时候为空
"Data": [ //当ErrorMsg不为空的时候,一般是出现异常的情况,则Data为空
{
"...": "...",
"...": "..."
}
]
}
上述Data才是每个接口返回的不一样的数据,这样标准格式,很方便调用方统一处理。但如果在每一个接口返回的地方都要做一次标准格式的处理,系统中大量存在重复代码。
下面我来给大家讲述通过Spring接口统一进行处理。
认识Spring框架两个接口
RequestBodyAdvice 和 ResponseBodyAdvice 是Spring框架中的两个接口,主要用于实现全局的请求体(RequestBody)和响应体(ResponseBody)的处理,它们在Spring MVC中用于拦截和修改进入控制器方法的请求数据以及从控制器方法返回的响应数据。这两个接口的设计遵循了Spring的AOP(面向切面编程)思想,使得开发者可以在不改动原有业务逻辑的前提下,对数据进行统一的处理,比如日志记录、数据加密解密、结果封装等。
1. RequestBodyAdvice
RequestBodyAdvice 是Spring框架提供的一个接口,主要用于拦截和处理进入控制器方法之前的HTTP请求体(RequestBody)。它是Spring MVC中处理请求体数据的一个灵活且强大的扩展点,允许开发者在请求体被实际的方法参数绑定之前对其进行预处理或者后处理。这对于日志记录、数据转换、安全性检查或是其他任何需要在请求体数据到达具体处理器方法前进行的操作非常有用。
RequestBodyAdvice 接口定义了以下四个方法:
public interface RequestBodyAdvice {
boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException;
Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
@Nullable
Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
}
- supports(MethodParameter, Class<?>):
该方法用于判断当前的RequestBodyAdvice 实例是否支持给定的方法参数和目标类型。返回true表 示支持,此时beforeBodyRead、afterBodyRead 或 handleEmptyBody 方法会被调用;反之则不 会。 - beforeBodyRead(HttpInputMessage, MethodParameter, Type, Class<?>):
在读取请求体之前被调用,可以在这里修改输入消息,比如添加或修改HTTP头信息。 - afterBodyRead(HttpInputMessage, MethodParameter, Type, Class<?>, Object):
在请求体被读取并转换为对象之后被调用。开发者可以在此方法中对转换后的对象进行进一步的处 理或修改。 - handleEmptyBody(HttpInputMessage, MethodParameter, Type, Class<?>):
当请求体为空时被调用,允许自定义如何处理空请求体的情况。
2. ResponseBodyAdvice
ResponseBodyAdvice 是Spring框架提供的另一个接口,与RequestBodyAdvice相对应,它用于在响应体(ResponseBody)发送给客户端之前对其进行拦截和处理。这个接口允许开发者在控制器方法已经处理完业务逻辑并准备返回响应时,对响应内容进行修改、日志记录、数据转换等操作,而不必在每个控制器方法中重复相同的处理逻辑。
ResponseBodyAdvice 接口定义了以下两个方法:
public interface ResponseBodyAdvice<T> {
boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
@Nullable
T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response);
}
- supports(MethodParameter, Class<?>):
同RequestBodyAdvice中的方法,用于判断当前ResponseBodyAdvice 实例是否支持给定的方法参数和 返回类型。返回true表示将介入处理过程。 - beforeBodyWrite(Object, MethodParameter, MediaType, Class<?>, ServerHttpRequest, ServerHttpResponse):
在响应体写回客户端之前被调用。开发者可以在这个方法里修改响应体对象,转换其格式,或者基于响应内容做出其他处理。返回的对象将会被序列化并写入响应流中。
请求结果返回统一格式封装
supports方法:判断是否要交给 beforeBodyWrite 方法执行,ture:需要;false:不需要。
beforeBodyWrite方法:对 response 进行具体的处理。
- 1)定义返回Code枚举类,定义好接口返回的code值和对应的错误描述,通过枚举类来实现。示例代码如下:
import lombok.AllArgsConstructor;
import lombok.ToString;
/**
* 接口返回code枚举类
* @author gzh-吴农软语
* @date 2024/5/27
*/
@ToString
@AllArgsConstructor
public enum CodeEnum {
SUCCESS("001","成功"),
FAIL("000","失败"),
EXCEPTION("999","接口异常");
public final String code;
public final String message;
}
- 2)定义统一返回结果对象,定义一个统一返回结果对象 ResultVO,示例代码如下:
import com.holmium.framwork.core.enums.CodeEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.http.HttpStatus;
/**
* 统一返回结果类
* @author gzh-吴农软语
* @date 2024/5/27
*/
@Data
@AllArgsConstructor
@EqualsAndHashCode
@Builder
public class ResultVO<T> {
/**
* 请求状态
*/
private Integer status;
/**
* 请求响应代码,如:001-成功,000-失败
*/
private String code;
/**
* 请求返回信息描述或异常信息
*/
private String message;
/** * 接口请求返回业务对象数据 */
private T data;
public ResultVO() {
}
/**
* 请求成功,对返回结果进行封装
*/
public static ResultVO<?> success(Object data) {
return build(CodeEnum.SUCCESS.code,CodeEnum.SUCCESS.message,data);
}
/**
* 请求失败,对返回结果进行封装
*/
public static ResultVO<?> failed(String message) {
return build(CodeEnum.FAIL.code,message,null);
}
/**
* 返回结果统一封装
*/
private static ResultVO <?> build(String code,String message,Object data){
return ResultVO.builder()
.status(HttpStatus.OK.value())
.code(code)
.message(message)
.data(data)
.build();
}
}
- 3)定义不需要统一封装返回结果注解,示例代码如下:
//如果不需要进行统一结果处理,可在方法上加上这个注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotResponseBodyAdvice {
}
- 4)通过实现ResponseBodyAdvic接口实现统一返回,示例代码如下:
import com.holmium.framwork.core.response.Result;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* @author gzh-吴农软语
* @date 2024/5/27
*/
public class ResponseBodyHandler implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// response是ResultVO类型,或者 贴上了@NotControllerResponseAdvice注解 都不进行包装
return !(methodParameter.getParameterType().isAssignableFrom(ResultVo.class) ||
methodParameter.hasMethodAnnotation(NotResponseBodyAdvice.class));
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 提供一定的灵活度,如果body已经被包装了,就不进行包装
if (body instanceof ResultVO) {
return body;
}
// String类型不能直接包装,所以要进行些特别的处理
//否则将出现类型转换异常: class ResultVO cannot be cast to class java.lang.String
if (returnType.getGenericParameterType().equals(String.class)) {
// 或者 if (body instanceof String) {
ObjectMapper objectMapper = new ObjectMapper();
try {
// 将数据包装在ResultVo里后转换为json串进行返回
return objectMapper.writeValueAsString(ResultVO.success(body));
} catch (JsonProcessingException e) {
throw new RuntimeException(e.getMessage());
}
}
// 否则直接包装成ResultVO返回
return ResultVO.success(body);
}
}
全局统一异常处理
统一拦截异常的目的一个是为了可以与前面定义下来的统一包装返回结构能对应上,另一个是我们希望无论系统发生什么异常,Http 的状态码都要是 200 ,尽可能由业务来区分系统的异常
- 1) 自定义异常,示例代码如下:
自定义异常是为了后面统一拦截异常时,对业务中的异常有更加细颗粒度的区分,拦截时针对不同的异常作出不同的响应。
/**
* @author gzh-吴农软语
* @date 2024/5/27
* @apiNote 自定义业务异常
*/
@ResponseStatus(value = HttpStatus.OK, reason = "业务异常!")
@Data
@EqualsAndHashCode(callSuper = true)
public class BusinessException extends RuntimeException {
/**
* 业务错误码
*/
private String code;
/**
* 错误提示
*/
private String message;
/**
* 空构造方法,避免反序列化问题
*/
public BusinessException() {
}
public BusinessException(String message) {
super(message);
}
public BusinessException(CodeEnum codeEnum) {
this.code = codeEnum.code;
this.message = codeEnum.message;
}
public BusinessException(String code, String message) {
this.code = code;
this.message = message;
}
}
/**
* @author gzh-吴农软语
* @date 2024/5/27
* @description: 自定义异常
*/
public class CustomizeException extends RuntimeException {
public CustomizeException(String message, Throwable cause) {
super(message, cause);
}
public CustomizeException(String message) {
super(message);
}
}
/**
* @author gzh-吴农软语
* @date 2024/5/27
* @description: 自定义参数异常
*/
@Getter
public class ParamException extends CustomizeException {
private List<String> fieldList;
private List<String> msgList;
public ParamException(String message) {
super(message);
}
public ParamException(String message, Throwable cause) {
super(message, cause);
}
public ParamException(List<String> fieldList, List<String> msgList) throws CustomizeException {
super(generatorMessage(fieldList, msgList));
this.fieldList = fieldList;
this.msgList = msgList;
}
public ParamException(List<String> fieldList, List<String> msgList, Exception ex) throws CustomizeException {
super(generatorMessage(fieldList, msgList), ex);
this.fieldList = fieldList;
this.msgList = msgList;
}
private static String generatorMessage(List<String> fieldList, List<String> msgList) throws CustomizeException {
if (CollectionUtils.isEmpty(fieldList) || CollectionUtils.isEmpty(msgList) || fieldList.size() != msgList.size()) {
return "参数错误";
}
StringBuilder message = new StringBuilder();
for (int i = 0; i < fieldList.size(); i++) {
String field = fieldList.get(i);
String msg = msgList.get(i);
if (i == fieldList.size() - 1) {
message.append(field).append(":").append(msg);
} else {
message.append(field).append(":").append(msg).append(",");
}
}
return message.toString();
}
}
/**
* @author gzh-吴农软语
* @date 2024/5/27
* 服务器异常 Exception
*/
@Data
@EqualsAndHashCode(callSuper = true)
public final class ServerException extends RuntimeException {
/**
* 全局错误码
*/
private String code;
/**
* 错误提示
*/
private String message;
/**
* 空构造方法,避免反序列化问题
*/
public ServerException() {
}
public ServerException(String message) {
super(message);
}
public ServerException(CodeEnum codeEnum) {
this.code = codeEnum.code;
this.message = codeEnum.message;
}
public ServerException(String code, String message) {
this.code = code;
this.message = message;
}
}
- 2)异常统一拦截处理,示例代码如下:
/**
* @author gzh-吴农软语
* @date 2024/5/27
* @description: 统一异常处理类
*/
@Slf4j
@RestControllerAdvice
public class GlobExceptionHandler {
@ExceptionHandler(value = BusinessException.class)
public ResultVO<?> handleBusinessException(BusinessException bx) {
log.info("接口调用业务异常信息{}", bx.getMessage());
// 返回统一处理类
return ResultVO.failed( bx.getMessage());
}
/**
* 捕获 {@code ParamException} 异常
*/
@ExceptionHandler(value = ParamException.class)
public ResultVO<?> paramExceptionHandler(ParamException ex) {
log.info("接口调用异常:{},异常信息{}", ex.getMessage(), ex.getMessage());
// 返回统一处理类
return ResultVO.failed(ex.getMessage());
}
/**
* 顶级异常捕获并统一处理,当其他异常无法处理时候选择使用
*/
@ExceptionHandler(value = Exception.class)
public ResultVO<?> handle(Exception ex) {
log.info("接口调用异常:{},异常信息{}", ex.getMessage(), ex.getMessage());
// 返回统一处理类
return ResultVO.failed(ex.getMessage());
}
}
请求响应结果加密
- 1)定义一个加密数据配置读取类,读取在配置文件中配置的加密秘钥,示例代码如下:
@ConfigurationProperties(prefix = "encrypt.body")
@Configuration
@Data
public class EncryptBodyConfig {
//AES加密秘钥
private String aesKey;
//DES加密秘钥
private String desKey;
private Charset encoding = StandardCharsets.UTF_8;
}
- 2)定义一个判断 类、方法、字段或方法参数是否带有指定类型(支持的加密算法)的注解,示例代码如下:
private boolean hasEncryptAnnotation(AnnotatedElement annotatedElement) {
if (annotatedElement == null) {
return false;
}
return annotatedElement.isAnnotationPresent(EncryptBody.class)
|| annotatedElement.isAnnotationPresent(AESEncryptBody.class)
|| annotatedElement.isAnnotationPresent(DESEncryptBody.class)
|| annotatedElement.isAnnotationPresent(RSAEncryptBody.class)
|| annotatedElement.isAnnotationPresent(MD5EncryptBody.class)
|| annotatedElement.isAnnotationPresent(SHAEncryptBody.class)
|| annotatedElement.isAnnotationPresent(SMEncryptBody.class);
}
- 3)检查类、方法、字段或方法参数是否带有指定类型(支持的加密算法)的注解
private EncryptAnnotationInfoBean getEncryptAnnotation(AnnotatedElement annotatedElement) {
return EncryptAnnotationInfoBeanFactory.getEncryptAnnotationInfoBean(annotatedElement);
}
- 4)选择加密方式并对字符串进行加密,示例代码如下:
private String switchEncrypt(String formatStringBody, EncryptAnnotationInfoBean infoBean) {
EncryptBodyMethod method = infoBean.getEncryptBodyMethod();
if (method == null) {
throw new EncryptMethodNotFoundException();
}
String encrypt = EncryptBodyMethod.valueOf(method.toString()).getEncryptHandler().encrypt(infoBean, config,
formatStringBody);
if (config.isShowLog()) {
log.info("加密前数据:{},加密后数据:{}", formatStringBody, encrypt);
}
return encrypt;
}
- 5) 给supports方法设置返回值,根据返回值判断给定的HTTP消息转换器是否能支持处理这种类型的返回值,为true会进入下述beforeBodyWrite方法,否则不会,则示例代码如下:
@Override
public boolean supports(MethodParameter returnType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
//获取方法所在类的信息
Class<?> declaringClass = returnType.getDeclaringClass();
//判断类上是否有指定类型的注解
if (this.hasEncryptAnnotation(declaringClass)) {
encrypt =true;
return true;
}
Method method = returnType.getMethod();
if (method != null) {
Class<?> returnValueType = method.getReturnType();
//判断方法上是否有指定类型的注解
if(this.hasEncryptAnnotation(method) || this.hasEncryptAnnotation(returnValueType)) {
encrypt =true;
return true;
}
}
//如果无需加解密,返回类型ResponseEntity时也不用处理
return returnType.getMethod().getReturnType() != ResponseEntity.class;
}
- 6)用于在响应体写回客户端之前对响应内容进行修改或处理,在这个方法中调用加密方法,示例代码如下:
@SneakyThrows(EncryptBodyFailException.class)
@Override
public Object beforeBodyWrite(Object body, @NonNull MethodParameter returnType, @NonNull MediaType selectedContentType, @NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType, @NonNull ServerHttpRequest request, @NonNull ServerHttpResponse response) {
log.info("Response返回前数据:{}", body);
if (request.getURI().getPath().startsWith("/actuator")) {
return body;
}
if (encrypt) {
// 如果data为空,直接返回
if (body == null) {
return successBusiness(body);
}
// 提供一定的灵活度,如果body已经被包装了,就不进行包装
if (body instanceof Result) {
body = ((Result<?>) body).getData();
}
// 如果是实体,并且继承了Request,则放入时间戳
if (body instanceof RequestBase) {
((RequestBase) body).setCurrentTimeMillis(System.currentTimeMillis());
}
String str = CommonUtils.convertToStringOrJson(body, objectMapper);
response.getHeaders().setContentType(MediaType.TEXT_PLAIN);
Method method = returnType.getMethod();
String content = null;
if (method != null) {
// 从方法上
EncryptAnnotationInfoBean methodAnnotation = this.getEncryptAnnotation(method);
if (methodAnnotation != null) {
return successBusiness(switchEncrypt(str, methodAnnotation));
}
// 从方法返回值上
Class<?> methodReturnType = method.getReturnType();
EncryptAnnotationInfoBean returnTypeClassAnnotation = this.getEncryptAnnotation(methodReturnType);
if (returnTypeClassAnnotation != null) {
return successBusiness(switchEncrypt(str, returnTypeClassAnnotation));
}
}
// 从声明类上
EncryptAnnotationInfoBean classAnnotation = this.getEncryptAnnotation(returnType.getDeclaringClass());
if (classAnnotation != null) {
return successBusiness(switchEncrypt(str, classAnnotation));
}else {
throw new EncryptBodyFailException();
}
} else {
// 提供一定的灵活度,如果body已经被包装了,就不进行包装
if (body instanceof ResponseEntity
|| returnType.hasMethodAnnotation(NotControllerResponseAdvice.class)) {
return body;
} else {
return successBusiness(body);
}
}
}
注:可在这方法里增加需要公共处理的内容,比如:记录日志、加签等,可根据需要进行增加,统一处理。
请求参数解密和验证
- 定义一个判断 类、方法、字段或方法参数是否带有指定类型(支持的解密密算法)的注解,示例代码如下:
private boolean hasDecryptAnnotation(AnnotatedElement annotatedElement) {
return annotatedElement.isAnnotationPresent(DecryptBody.class)
|| annotatedElement.isAnnotationPresent(AESDecryptBody.class)
|| annotatedElement.isAnnotationPresent(DESDecryptBody.class)
|| annotatedElement.isAnnotationPresent(RSADecryptBody.class)
|| annotatedElement.isAnnotationPresent(SMDecryptBody.class);
}
- 检查类、方法、字段或方法参数是否带有指定类型(支持的解密算法)的注解,示例代码如下:
private DecryptAnnotationInfoBean getDecryptAnnotation(AnnotatedElement annotatedElement) {
return DecryptAnnotationInfoBeanFactory.getDecryptAnnotationInfoBean(annotatedElement);
}
- 选择解密方式并对字符串进行解密,返回解密厚的 字符串。示例代码如下:
private String switchDecrypt(String formatStringBody, DecryptAnnotationInfoBean infoBean) {
DecryptBodyMethod method = infoBean.getDecryptBodyMethod();
if (method == null) {
throw new DecryptMethodNotFoundException();
}
String decrypt = DecryptBodyMethod.valueOf(method.toString()).getDecryptHandler().decrypt(infoBean, config,
formatStringBody);
return decrypt;
}
- 如果body为空,转为空对象
@SneakyThrows(Exception.class)
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
String typeName = targetType.getTypeName();
Class<?> bodyClass = Class.forName(typeName);
return bodyClass.newInstance();
}
- 用于在处理HTTP请求的请求体(RequestBody)之前或之后自定义逻辑,示例代码如下:
此方法应返回一个布尔值,指示给定的RequestBodyAdvice实例是否支持(或适用)于处理具有指定方法参数、目标类型和消息转换器类型的请求。如果返回true,则表示该RequestBodyAdvice的其他方法(如beforeBodyRead, afterBodyRead, onError)将被调用以参与到请求体处理的过程中;如果返回false,则表示此Advice不适用于当前请求处理场景,Spring将不会调用该Advice的其他逻辑。
@Override
public boolean supports(MethodParameter methodParameter, @NonNull Type targetType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
if (this.hasDecryptAnnotation(methodParameter.getDeclaringClass())) {
return true;
}
Method method = methodParameter.getMethod();
if (method != null) {
if (this.hasDecryptAnnotation(method)) {
return true;
}
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
if (this.hasDecryptAnnotation(parameterType)) {
return true;
}
}
}
return false;
}
- 根据指定的解密方式进行解密,示例代码如下:
@SneakyThrows(DecryptBodyFailException.class)
@Override
public HttpInputMessage beforeBodyRead(@NonNull HttpInputMessage inputMessage, @NonNull MethodParameter parameter, @NonNull Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
String body;
try {
body = IoUtil.read(inputMessage.getBody(), config.getEncoding());
} catch (Exception e) {
throw new DecryptBodyFailException("Unable to get request body data,"
+ " please check if the sending data body or request method is in compliance with the specification."
+ " (无法获取请求正文数据,请检查发送数据体或请求方法是否符合规范。)");
}
if (body == null || StrUtil.isEmpty(body)) {
throw new DecryptBodyFailException("The request body is NULL or an empty string, so the decryption failed."
+ " (请求正文为NULL或为空字符串,因此解密失败。)");
}
Class<?> targetTypeClass;
try {
targetTypeClass = Class.forName(targetType.getTypeName());
} catch (ClassNotFoundException e) {
throw new DecryptBodyFailException(e.getMessage());
}
String decryptBody = null;
DecryptAnnotationInfoBean methodAnnotation = this.getDecryptAnnotation(parameter.getMethod());
if (methodAnnotation != null) {
decryptBody = switchDecrypt(body, methodAnnotation);
} else if (this.hasDecryptAnnotation(targetTypeClass)) {
DecryptAnnotationInfoBean classAnnotation = this.getDecryptAnnotation(targetTypeClass);
if (classAnnotation != null) {
decryptBody = switchDecrypt(body, classAnnotation);
}
} else {
DecryptAnnotationInfoBean classAnnotation = this.getDecryptAnnotation(parameter.getDeclaringClass());
if (classAnnotation != null) {
decryptBody = switchDecrypt(body, classAnnotation);
}
}
if (decryptBody == null) {
throw new DecryptBodyFailException(
"Decryption error, " + "please check if the selected source data is encrypted correctly."
+ " (解密错误,请检查选择的源数据的加密方式是否正确。)");
}
try {
return new DecryptHttpInputMessage(IoUtil.toStream(decryptBody, config.getEncoding()),
inputMessage.getHeaders());
} catch (Exception e) {
throw new DecryptBodyFailException("该字符串转换为流格式异常。请检查诸如编码等格式是否正确。(字符串转换成流格式异常,请检查编码等格式是否正确。)");
}
}
- 解密后做相关的验证,比如时间戳等,示例代码如下:
@SneakyThrows(ParamException.class)
@Override
public Object afterBodyRead(@NonNull Object body, @NonNull HttpInputMessage inputMessage, @NonNull MethodParameter parameter, @NonNull Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
// 强制所有实体类必须继承RequestBase类,设置时间戳
if (body instanceof RequestBase) {
// 获取时间戳
Long currentTimeMillis = ((RequestBase) body).getCurrentTimeMillis();
// 有效期 60秒
long effective = 60 * 1000;
// 时间差
long expire = System.currentTimeMillis() - currentTimeMillis;
// 是否在有效期内
if (Math.abs(expire) > effective) {
throw new ParamException("时间戳不合法");
}
} else {
throw new ParamException(String.format("请求参数类型:%s 未继承:%s", body.getClass().getName(), RequestBase.class.getName()));
}
return body;
}
注:可在这个接口类增加一些接口校验的方法,比如:验签、白名单验证、重复提交等等。
最后
Spring框架设计了许多扩展接口,用于提供高度的灵活性和可定制性,允许开发者自定义和扩展其功能以满足特定需求。
- BeanFactoryPostProcessor:允许在所有bean定义加载完成后但在bean实例化之前对BeanFactory进行修改。你可以通过这个接口来读取配置文件,修改bean定义,比如注入属性值等。
- ApplicationContextInitializer:用于在ApplicationContext初始化之前执行自定义的初始化逻辑。这使得开发者能够在应用上下文启动之前进行配置或者准备性工作。
- ApplicationListener:实现此接口的类可以监听Spring ApplicationContext中发布的事件。这使得你能够对特定事件作出反应,比如应用启动、停止事件等。
- InitializingBean和 DisposableBean:这两个接口分别用于定义bean的初始化和销毁方法。afterPropertiesSet()方法在所有必需属性设置之后调用,destroy()方法在bean销毁时调用。
- SmartLifecycle:如果你的bean需要参与Spring应用的启动和关闭的生命周期管理,比如需要延迟启动或按需启动的服务,可以实现此接口。
- ControllerAdvice:虽然严格来说这不是一个扩展接口,而是一个用于全局异常处理和全局数据绑定的注解,但它提供了对所有控制器方法的集中处理能力,如异常统一处理、模型属性添加等。
- HandlerInterceptor和 WebMvcConfigurer:这些接口用于扩展Spring MVC的功能,比如添加拦截器来处理请求前后逻辑,或者自定义视图解析器、消息转换器等。
- RequestMappingHandlerMapping和 RequestMappingHandlerAdapter:可以通过扩展这些类来定制Spring MVC的请求映射和处理逻辑,比如改变默认的URL到控制器方法的映射规则。
- HttpMessageConverter:实现此接口可以自定义HTTP消息的转换,支持不同的数据类型和媒体类型,如JSON、XML等。
- ResponseBodyAdvice和 RequestBodyAdvice:这些接口允许你在响应体写回客户端之前或读取请求体之后进行处理,适合做日志记录、数据转换或安全检查等。
后续我讲用 系列文章来讲述spring的一些常用的扩展接口,以及使用场景。
猜你喜欢
- 2024-12-25 Spring Boot整合Spring Cloud GateWay代理第三方应用的调用接口?
- 2024-12-25 Java 近期新闻:Hibernate 6.0、JobRunr 5.0、JHipster 7.8.0
- 2024-12-25 Keycloak Servlet Filter Adapter使用
- 2024-12-25 如何在Spring Boot中保证RESTful接口的安全性?
- 2024-12-25 Java项目实战第6天:登录业务的实现
- 2024-12-25 JavaEE概述总结:Servlet生命周期+JSP内置对象
- 2024-12-25 SpringBoot 无感刷新 Token springboot的token
- 2024-12-25 若依开发框架解析笔记(7)-jwt的应用
- 2024-12-25 Spring MVC中提供了哪些扩展机制?如何使用这些扩展机制?
- 2024-12-25 49个Spring经典面试题总结(附带答案)
- 1509℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 526℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 492℃MySQL service启动脚本浅析(r12笔记第59天)
- 472℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 469℃启用MySQL查询缓存(mysql8.0查询缓存)
- 450℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 429℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 426℃MySQL server PID file could not be found!失败
- 最近发表
- 标签列表
-
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- chromepost (65)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- js判断是否是json字符串 (67)
- checkout-b (67)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- linux删除一个文件夹 (65)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)