前言
今天参考的开源组件Graceful Response——Spring Boot接口优雅响应处理器。
具体用法可以参考github以及官方文档。
基本使用
引入Graceful Response组件
项目中直接引入如下maven依赖,即可使用其相关功能。
<dependency><groupId>com.feiniaojin</groupId><artifactId>graceful-response</artifactId><version>{latest.version}</version></dependency>
开始使用
配置文件
:可不配置,直接全部默认配置。
graceful-response:response-style: 1 # 配置响应格式类型(0 or 1)后续介绍print-exception-in-global-advice: true # 在全局异常处理器中打印异常,默认不打印
一切之前需在主启动类添加该注解开启功能:
@EnableGracefulResponse
。(后续介绍)
Controller直接返回我们查询的对象即可,Graceful Response会帮我们封装响应格式(响应格式可配置
)
Controller:
@RestController
public class HelloController {@Resourceprivate UserMapper userMapper;@GetMapping("/userList")public List<User> userList() {return userMapper.selectList(null);}
}
调用接口:
默认成功/失败状态码和提示消息也都是可以配置的,是不是感觉非常的神奇(一切皆可配)
其他具体用法见官网。
废话不多说,接下来我们直接步入主题探究下自定义starter组件核心步骤
。
自定义starter组件核心步骤
首先明确一下,自定义starter组件的三个核心步骤:
- 自定义注解
- 结合Spring AOP实现注解逻辑
- 实现starter组件自动装配以及可配置
接下来看开源组件都是怎么做的吧~
看看源码目录结构(麻雀虽小但五脏俱全):
0. 注解使用
以
@ExceptionMapper
注解为例,介绍一下相关功能及实现原理。
Graceful Response引入@ExceptionMapper注解,通过该注解将异常和错误码关联起来,这样Service方法就不需要再维护Response的响应码了,直接抛出业务异常,由Graceful Response进行异常和响应码的关联。 @ExceptionMapper的用法如下:
自定义业务类异常:
/*** NotFoundException的定义,使用@ExceptionMapper注解修饰* code:代表接口的异常码* msg:代表接口的异常提示*/
@ExceptionMapper(code = "1404", msg = "找不到对象")
public class NotFoundException extends RuntimeException {}
Service接口定义:
public interface QueryService {UserInfoView queryOne(Query query);
}
Service接口实现:
@Service
public class QueryServiceImpl implements QueryService {@Resourceprivate UserInfoMapper mapper;public UserInfoView queryOne(Query query) {UserInfo userInfo = mapper.findOne(query.getId());if (Objects.isNull(userInfo)) {// 这里直接抛自定义异常throw new NotFoundException();}// ……后续业务操作}
}
当Service层的queryOne方法抛出NotFoundException时,Graceful Response会进行异常捕获,并将NotFoundException对应的异常码和异常信息封装到统一的响应对象中,最终接口返回以下JSON(默认响应格式)
{"status": {"code": "1404","msg": "找不到对象"},"payload": {}
}
使用起来十分方便,接下来我们看下具体实现原理。
1. 自定义注解
首先看下注解定义:
/*** 异常映射注解.*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ExceptionMapper {/*** 异常对应的错误码.** @return 异常对应的错误码*/String code() default "ERROR";/*** 异常信息.** @return 异常对应的提示信息*/String msg() default "Poor network quality!";/*** 异常信息是否支持替换* 仅当msgReplaceable==ture,且异常实例的message不为空时才能替换*/boolean msgReplaceable() default false;
}
2. 结合Spring AOP实现注解逻辑(核心)
配置类
配置类
GracefulResponseProperties
跟可配置项相关,所有配置项都映射为该类
@ConfigurationProperties(prefix = "graceful-response") // 配置前缀
public class GracefulResponseProperties {/*** 在全局异常处理器中打印异常,默认不打印*/private boolean printExceptionInGlobalAdvice = false;/*** 默认的Response实现类名称,配置了responseClassFullName,则responseStyle不生效*/private String responseClassFullName;/*** responseStyle的风格,responseClassFullName为空时才会生效* responseStyle==null或者responseStyle==0,Response风格为 DefaultResponseImplStyle0* responseStyle=1,Response风格为 DefaultResponseImplStyle1*/private Integer responseStyle;/*** 默认的成功返回码 默认0*/private String defaultSuccessCode = DefaultConstants.DEFAULT_SUCCESS_CODE;/*** 默认的成功提示 默认OK*/private String defaultSuccessMsg = DefaultConstants.DEFAULT_SUCCESS_MSG;/*** 默认的失败码 默认1*/private String defaultErrorCode = DefaultConstants.DEFAULT_ERROR_CODE;/*** 默认的失败提示 默认error*/private String defaultErrorMsg = DefaultConstants.DEFAULT_ERROR_MSG;/*** Validate异常码,不提供的话默认DefaultConstants.DEFAULT_ERROR_CODE*/private String defaultValidateErrorCode = DefaultConstants.DEFAULT_ERROR_CODE;/*** 例外包路径*/private List<String> excludePackages;/*** 不使用@ExceptionMapper和@ExceptionAliasFor修饰的原生异常* 是否使用异常信息Throwable类的detailMessage进行返回* originExceptionUsingDetailMessage=false,则msg=defaultErrorMsg*/private Boolean originExceptionUsingDetailMessage = false;// getter / setter
}
设置响应状态
ResponseStatusFactory
:响应状态工厂(仅定义code和msg),默认实现成功默认响应、失败默认响应以及自定义code、msg响应状态
默认实现类:
// 响应状态
public interface ResponseStatus {void setCode(String code);String getCode();void setMsg(String msg);String getMsg();
}// 默认响应状态
public class DefaultResponseStatus implements ResponseStatus {private String code;private String msg;public DefaultResponseStatus() {}public DefaultResponseStatus(String code, String msg) {this.code = code;this.msg = msg;}@Overridepublic void setCode(String code) {this.code = code;}@Overridepublic String getCode() {return code;}@Overridepublic void setMsg(String msg) {this.msg = msg;}@Overridepublic String getMsg() {return msg;}
}
// 核心实现类
public class DefaultResponseStatusFactoryImpl implements ResponseStatusFactory {@Resourceprivate GracefulResponseProperties properties;// 默认成功响应转态@Overridepublic ResponseStatus defaultSuccess() {DefaultResponseStatus defaultResponseStatus = new DefaultResponseStatus();defaultResponseStatus.setCode(properties.getDefaultSuccessCode());defaultResponseStatus.setMsg(properties.getDefaultSuccessMsg());return defaultResponseStatus;}// 默认失败响应转态@Overridepublic ResponseStatus defaultError() {DefaultResponseStatus defaultResponseStatus = new DefaultResponseStatus();defaultResponseStatus.setCode(properties.getDefaultErrorCode());defaultResponseStatus.setMsg(properties.getDefaultErrorMsg());return defaultResponseStatus;}// 自定code、msg状态@Overridepublic ResponseStatus newInstance(String code, String msg) {return new DefaultResponseStatus(code, msg);}
}
设置响应格式
ResponseFactory
:根据配置项,设置响应格式
默认实现类:
public class DefaultResponseFactory implements ResponseFactory {private final Logger logger = LoggerFactory.getLogger(DefaultResponseFactory.class);private static final Integer RESPONSE_STYLE_0 = 0;private static final Integer RESPONSE_STYLE_1 = 1;@Resourceprivate ResponseStatusFactory responseStatusFactory;@Resourceprivate GracefulResponseProperties properties;@Overridepublic Response newEmptyInstance() {try {String responseClassFullName = properties.getResponseClassFullName();// 配置了Response的全限定名,即实现Response接口,用配置的进行返回if (StringUtils.hasLength(responseClassFullName)) {Object newInstance = Class.forName(responseClassFullName).getConstructor().newInstance();return (Response) newInstance;} else {// 没有配Response的全限定名,则创建DefaultResponsereturn generateDefaultResponse();}} catch (Exception e) {throw new RuntimeException(e);}}// 响应格式判断private Response generateDefaultResponse() {Integer responseStyle = properties.getResponseStyle();// 未配置或者配置0if (Objects.isNull(responseStyle) || RESPONSE_STYLE_0.equals(responseStyle)) {return new DefaultResponseImplStyle0();} else if (RESPONSE_STYLE_1.equals(responseStyle)) {return new DefaultResponseImplStyle1();} else {logger.error("不支持的Response style类型,responseStyle={}", responseStyle);throw new IllegalArgumentException("不支持的Response style类型");}}@Overridepublic Response newInstance(ResponseStatus responseStatus) {Response bean = this.newEmptyInstance();bean.setStatus(responseStatus);return bean;}@Overridepublic Response newSuccessInstance() {Response emptyInstance = this.newEmptyInstance();emptyInstance.setStatus(responseStatusFactory.defaultSuccess());return emptyInstance;}@Overridepublic Response newSuccessInstance(Object payload) {Response bean = this.newSuccessInstance();bean.setPayload(payload);return bean;}@Overridepublic Response newFailInstance() {Response bean = this.newEmptyInstance();bean.setStatus(responseStatusFactory.defaultError());return bean;}}
对应配置文件中两种响应格式:
默认响应格式:
public interface Response {void setStatus(ResponseStatus statusLine);ResponseStatus getStatus();void setPayload(Object payload);Object getPayload();
}
默认格式实现类:
{"status": {"code": "","msg": ""},"payload": {}
}
public class DefaultResponseImplStyle0 implements Response {private ResponseStatus status;private Object payload = Collections.emptyMap();public DefaultResponseImplStyle0() {}public DefaultResponseImplStyle0(Object payload) {this.payload = payload;}@Overridepublic void setStatus(ResponseStatus responseStatus) {this.status = responseStatus;}@Overridepublic ResponseStatus getStatus() {return status;}@Overridepublic void setPayload(Object obj) {this.payload = obj;}@Overridepublic Object getPayload() {return payload;}
}
style设置为1,响应格式:
{"code": "1404","msg": "找不到对象""data": {}
}
另一种响应实现类
public class DefaultResponseImplStyle1 implements Response {private String code;private String msg;private Object data = Collections.emptyMap();@Overridepublic void setStatus(ResponseStatus statusLine) {this.code = statusLine.getCode();this.msg = statusLine.getMsg();}@Override@JsonIgnorepublic ResponseStatus getStatus() {return null;}@Overridepublic void setPayload(Object payload) {this.data = payload;}// 这里直接把 payload 忽略了(因此这种响应格式中没有payload字段)@Override@JsonIgnorepublic Object getPayload() {return null;}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public Object getData() {return data;}public void setData(Object data) {this.data = data;}
}
核心处理逻辑类
核心处理逻辑类
:全局异常处理
/*** 全局异常处理.*/
@ControllerAdvice
@Order(200)
public class GlobalExceptionAdvice implements ApplicationContextAware {private final Logger logger = LoggerFactory.getLogger(GlobalExceptionAdvice.class);@Resourceprivate ResponseStatusFactory responseStatusFactory;@Resourceprivate ResponseFactory responseFactory;private ExceptionAliasRegister exceptionAliasRegister;// 配置类@Resourceprivate GracefulResponseProperties properties;/*** 异常处理逻辑. 【统一捕获所有异常】** @param throwable 业务逻辑抛出的异常* @return 统一返回包装后的结果*/@ExceptionHandler({Throwable.class})@ResponseBody public Response exceptionHandler(Throwable throwable) {// 配置项是否打印异常信息(默认false)if (properties.isPrintExceptionInGlobalAdvice()) {logger.error("Graceful Response:GlobalExceptionAdvice捕获到异常,message=[{}]", throwable.getMessage(), throwable);}ResponseStatus statusLine;if (throwable instanceof GracefulResponseException) {statusLine = fromGracefulResponseExceptionInstance((GracefulResponseException) throwable);} else {// 校验异常转自定义异常statusLine = fromExceptionInstance(throwable);}// 设置完code、msg之后直接返回return responseFactory.newInstance(statusLine);}private ResponseStatus fromGracefulResponseExceptionInstance(GracefulResponseException exception) {String code = exception.getCode();if (code == null) {code = properties.getDefaultErrorCode();}return responseStatusFactory.newInstance(code,exception.getMsg());}private ResponseStatus fromExceptionInstance(Throwable throwable) {Class<? extends Throwable> clazz = throwable.getClass();// 【直接获取抛出异常上的注解】ExceptionMapper exceptionMapper = clazz.getAnnotation(ExceptionMapper.class);// 1.有@ExceptionMapper注解,直接设置结果的状态if (exceptionMapper != null) {boolean msgReplaceable = exceptionMapper.msgReplaceable();//异常提示可替换+抛出来的异常有自定义的异常信息if (msgReplaceable) {String throwableMessage = throwable.getMessage();if (throwableMessage != null) {return responseStatusFactory.newInstance(exceptionMapper.code(), throwableMessage);}}return responseStatusFactory.newInstance(exceptionMapper.code(),exceptionMapper.msg());}// 2.有@ExceptionAliasFor异常别名注解,获取已注册的别名信息if (exceptionAliasRegister != null) {ExceptionAliasFor exceptionAliasFor = exceptionAliasRegister.getExceptionAliasFor(clazz);if (exceptionAliasFor != null) {return responseStatusFactory.newInstance(exceptionAliasFor.code(),exceptionAliasFor.msg());}}ResponseStatus defaultError = responseStatusFactory.defaultError();// 3. 原生异常 + originExceptionUsingDetailMessage=true// 如果有自定义的异常信息,原生异常将直接使用异常信息进行返回,不再返回默认错误提示if (properties.getOriginExceptionUsingDetailMessage()) {String throwableMessage = throwable.getMessage();if (throwableMessage != null) {defaultError.setMsg(throwableMessage);}}return defaultError;}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.exceptionAliasRegister = applicationContext.getBean(ExceptionAliasRegister.class);}
}
非空返回值封装处理类(ResponseBodyAdvice)
ResponseBodyAdvice:泛型T为响应data类型,直接Object即可
@ControllerAdvice
@Order(value = 1000)
public class NotVoidResponseBodyAdvice implements ResponseBodyAdvice<Object> {private final Logger logger = LoggerFactory.getLogger(NotVoidResponseBodyAdvice.class);@Resourceprivate ResponseFactory responseFactory;@Resourceprivate GracefulResponseProperties properties;/*** 路径过滤器*/private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();/*** 只处理不返回void的,并且MappingJackson2HttpMessageConverter支持的类型.** @param methodParameter 方法参数* @param clazz 处理器* @return 是否支持*/@Overridepublic boolean supports(MethodParameter methodParameter,Class<? extends HttpMessageConverter<?>> clazz) {Method method = methodParameter.getMethod();// method为空、返回值为void、非JSON,直接跳过if (Objects.isNull(method)|| method.getReturnType().equals(Void.TYPE)|| !MappingJackson2HttpMessageConverter.class.isAssignableFrom(clazz)) {logger.debug("Graceful Response:method为空、返回值为void、非JSON,跳过");return false;}// 有ExcludeFromGracefulResponse注解修饰的,也跳过if (method.isAnnotationPresent(ExcludeFromGracefulResponse.class)) {if (logger.isDebugEnabled()) {logger.debug("Graceful Response:方法被@ExcludeFromGracefulResponse注解修饰,跳过:methodName={}", method.getName());}return false;}// 配置了例外包路径,则该路径下的controller都不再处理List<String> excludePackages = properties.getExcludePackages();if (!CollectionUtils.isEmpty(excludePackages)) {// 获取请求所在类的的包名String packageName = method.getDeclaringClass().getPackage().getName();if (excludePackages.stream().anyMatch(item -> ANT_PATH_MATCHER.match(item, packageName))) {logger.debug("Graceful Response:匹配到excludePackages例外配置,跳过:packageName={},", packageName);return false;}}logger.debug("Graceful Response:非空返回值,需要进行封装");return true;}// 满足上述条件,封装返回对象(只要是非Void返回值,不做任何配置都会封装返回结果)@Overridepublic Object beforeBodyWrite(Object body,MethodParameter methodParameter,MediaType mediaType,Class<? extends HttpMessageConverter<?>> clazz,ServerHttpRequest serverHttpRequest,ServerHttpResponse serverHttpResponse) {if (body == null) {return responseFactory.newSuccessInstance();} else if (body instanceof Response) {return body;} else {// 设置data数据return responseFactory.newSuccessInstance(body);}}}
…
3. 实现starter组件自动装配以及可配置
全局自动装配类
该类可以将我们自定义的所有类全部加载到Spring容器中。
/*** 全局返回值处理的自动配置.*/
@Configuration
// 将外部配置文件中的属性注入到配置类中(将配置类加载到Spring容器中)
@EnableConfigurationProperties(GracefulResponseProperties.class)
public class AutoConfig {@Bean// 这个注解很是重要@ConditionalOnMissingBean(value = GlobalExceptionAdvice.class)public GlobalExceptionAdvice globalExceptionAdvice() {return new GlobalExceptionAdvice();}@Bean@ConditionalOnMissingBean(value = ValidationExceptionAdvice.class)public ValidationExceptionAdvice validationExceptionAdvice() {return new ValidationExceptionAdvice();}@Bean@ConditionalOnMissingBean(NotVoidResponseBodyAdvice.class)public NotVoidResponseBodyAdvice notVoidResponseBodyAdvice() {return new NotVoidResponseBodyAdvice();}@Bean@ConditionalOnMissingBean(VoidResponseBodyAdvice.class)public VoidResponseBodyAdvice voidResponseBodyAdvice() {return new VoidResponseBodyAdvice();}@Bean@ConditionalOnMissingBean(value = {ResponseFactory.class})public ResponseFactory responseBeanFactory() {return new DefaultResponseFactory();}@Bean@ConditionalOnMissingBean(value = {ResponseStatusFactory.class})public ResponseStatusFactory responseStatusFactory() {return new DefaultResponseStatusFactoryImpl();}@Beanpublic ExceptionAliasRegister exceptionAliasRegister() {return new ExceptionAliasRegister();}@Beanpublic Init init(){return new Init();}
}
注解启动全局结果处理入口
通过元注解 @Import(AutoConfig.class)
实际上将 AutoConfig
这个配置类引入到标注了 @EnableGracefulResponse
注解的类中。
引入该组件,只有在某个类上添加了
@EnableGracefulResponse
注解时,AutoConfig
中定义的相关 Bean 才会被注册到 Spring 容器中。可以方便地启用特定功能或配置。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfig.class)
public @interface EnableGracefulResponse {
}
SpringBoot主启动类,添加该注解开启功能:
另一种方式META-INF/spring.factories
如果想引入该组件就直接开启所有功能,可以不用上述全局启动注解
直接在组件项目的 resources
中创建 META-INF
目录,并在此目录下创建一个 spring.factories
文件,将starter组件的自动配置类的类路径写在文件上即可。
resourcesMETA-INFspring.factories
// 直接将自动装配类全限定名放入该文件即可
com.feiniaojin.gracefulresponse.AutoConfig
Spring Boot项目启动时,会扫描外部引入的Jar中的
META-INF/spring.factories
文件,将文件中配置的类信息装配到Spring容器中)【用的是Java的SPI机制
,还没研究后续再说吧】
这样只要引入该组件,组件功能也就集成进来了。【这种不够灵活,视情况用哪种方式】(建议:能用全局启动注解就用全局启动注解)
配置智能提示
组件中记得添加该依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><version>${spring.boot.version}</version><optional>true</optional></dependency>
组件中添加上述依赖可以用于处理配置类(@ConfigurationProperties
注解标注的类)以及生成相关的元数据。
IDE 支持: 提供更好的集成开发环境(IDE)支持。通过生成的配置元数据,IDE 可以在配置文件中提供智能提示,我们可以更方便地编辑配置。
引入该组件,在修改配置类可以有智能提示
如下:
该文章只介绍了该组件部分功能,有兴趣可以自行研究呀~
最后看一下组件的目录结构(学习一下):
小结
自定义starter组件的三个核心步骤:
- 自定义注解
- 注解结合AOP实现逻辑
- 自动装配和可配置(
配置类、自动装配类以及全局启动注解/spring.factories文件
)