从开源项目中学习如何自定义 Spring Boot Starter 小组件
前言
今天参考的开源组件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 {
@Resource
private 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 {
@Resource
private 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;
}
@Override
public void setCode(String code) {
this.code = code;
}
@Override
public String getCode() {
return code;
}
@Override
public void setMsg(String msg) {
this.msg = msg;
}
@Override
public String getMsg() {
return msg;
}
}
// 核心实现类
public class DefaultResponseStatusFactoryImpl implements ResponseStatusFactory {
@Resource
private GracefulResponseProperties properties;
// 默认成功响应转态
@Override
public ResponseStatus defaultSuccess() {
DefaultResponseStatus defaultResponseStatus = new DefaultResponseStatus();
defaultResponseStatus.setCode(properties.getDefaultSuccessCode());
defaultResponseStatus.setMsg(properties.getDefaultSuccessMsg());
return defaultResponseStatus;
}
// 默认失败响应转态
@Override
public ResponseStatus defaultError() {
DefaultResponseStatus defaultResponseStatus = new DefaultResponseStatus();
defaultResponseStatus.setCode(properties.getDefaultErrorCode());
defaultResponseStatus.setMsg(properties.getDefaultErrorMsg());
return defaultResponseStatus;
}
// 自定code、msg状态
@Override
public 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;
@Resource
private ResponseStatusFactory responseStatusFactory;
@Resource
private GracefulResponseProperties properties;
@Override
public 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的全限定名,则创建DefaultResponse
return generateDefaultResponse();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 响应格式判断
private Response generateDefaultResponse() {
Integer responseStyle = properties.getResponseStyle();
// 未配置或者配置0
if (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类型");
}
}
@Override
public Response newInstance(ResponseStatus responseStatus) {
Response bean = this.newEmptyInstance();
bean.setStatus(responseStatus);
return bean;
}
@Override
public Response newSuccessInstance() {
Response emptyInstance = this.newEmptyInstance();
emptyInstance.setStatus(responseStatusFactory.defaultSuccess());
return emptyInstance;
}
@Override
public Response newSuccessInstance(Object payload) {
Response bean = this.newSuccessInstance();
bean.setPayload(payload);
return bean;
}
@Override
public 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;
}
@Override
public void setStatus(ResponseStatus responseStatus) {
this.status = responseStatus;
}
@Override
public ResponseStatus getStatus() {
return status;
}
@Override
public void setPayload(Object obj) {
this.payload = obj;
}
@Override
public 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();
@Override
public void setStatus(ResponseStatus statusLine) {
this.code = statusLine.getCode();
this.msg = statusLine.getMsg();
}
@Override
@JsonIgnore
public ResponseStatus getStatus() {
return null;
}
@Override
public void setPayload(Object payload) {
this.data = payload;
}
// 这里直接把 payload 忽略了(因此这种响应格式中没有payload字段)
@Override
@JsonIgnore
public 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);
@Resource
private ResponseStatusFactory responseStatusFactory;
@Resource
private ResponseFactory responseFactory;
private ExceptionAliasRegister exceptionAliasRegister;
// 配置类
@Resource
private 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;
}
@Override
public 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);
@Resource
private ResponseFactory responseFactory;
@Resource
private GracefulResponseProperties properties;
/**
* 路径过滤器
*/
private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();
/**
* 只处理不返回void的,并且MappingJackson2HttpMessageConverter支持的类型.
*
* @param methodParameter 方法参数
* @param clazz 处理器
* @return 是否支持
*/
@Override
public 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返回值,不做任何配置都会封装返回结果)
@Override
public 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();
}
@Bean
public ExceptionAliasRegister exceptionAliasRegister() {
return new ExceptionAliasRegister();
}
@Bean
public 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组件的自动配置类的类路径写在文件上即可。
resources
META-INF
spring.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文件
)
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!