300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > [解锁新姿势] 封装通用返回值

[解锁新姿势] 封装通用返回值

时间:2021-06-21 05:44:49

相关推荐

[解锁新姿势] 封装通用返回值

前言

最近入坑了一家坑爹的公司,巨型单体,那代码,脑壳痛。

💩山遍地是,竟没有一片净土!

差点被同化了

致敬那些在屎山开发的程序猿

谁说站在光里的才算英雄 ?

你们才是!

场景

如果

我们的表,没有冗余数据

且实时查询时对应值时,都需要手动的 查询对应值,并赋值,如下代码所示:

public CardVO query(QueryDTO dto) {Card card = CardRepository.findById(dto.getId());CardVO cardVO = new CardVO();cardVO.setId(card.getId());//... 省略set 步骤User user = userRepository.findById(dto.getUserId());cardVO.setUsername(user.getName());}

此时查询评论接口也需要赋值 username

public CommentVO query(QueryDTO dto) {Comment comment = CommentRepository.findById(dto.getId());CommentVO commentVO = new CommentVO();commentVO.setId(card.getId());//... 省略set 步骤User user = userRepository.findById(dto.getUserId());commentVO.setUsername(user.getName());}

这时候,另外接口也需要…

那么这段代码不出意外的话会被 copy 到其他接口。

这种事情,我们怎么允许发生呢?

简单优化

public interface UsernameWrapObject {Long getUserId();void setUsername(String userName);}@Component@RequiredArgsConstructor(onConstructor = @__(@Autowired))public class UsernameWrap {private final UserRepository userRepository;public void wrap(UsernameWrapObject object) {User user = userRepository.findById(object.getUserId());object.setUsername(user.getName());}}@Data // 实现对应接口 public class CardVO implements UsernameWrapObject {@ApiModelProperty("打卡id")private Long id;@ApiModelProperty("打卡人")private String username;... 省略其他参数}

提取赋值到 UsernameWrap,简单封装一个通用wrap方法

入参为UsernameWrapObject接口(便于后续其他对象赋值使用,所以抽取成接口)

传入所需 UsernameWrapObject 的对象,就可以赋值username值。

然后对应调用改为,如下代码所示:

public CardVO query(QueryDTO dto) {Card card = CardRepository.findById(dto.getId());CardVO cardVO = new CardVO();cardVO.setId(card.getId());//... 省略set 步骤// User user = userRepository.findById(dto.getUserId());// cardVO.setUsername(user.getName());// 替换为 wrap 通用类usernameWrap.wrap(cardVO);}

至此,优化结束,相信很多人就心满意足,开始愉快的摸鱼了。

🐟

🐟

🐟

但是,回过头来想,每次都需要手动调用 UsernameWrap 进行赋值,难免有些重复(主要还是因为懒,连手动调用都不想)。

你说有没有什么拦截器,可以让我们在返回参数前,对返回值进行拦截,并通过反射赋值。

一顿Goolge+必应+百度,三连后 (暗示三连 :)

发现利器 ->ResponseBodyAdvice,通过 ResponseBodyAdvice,实现最终想要效果,让我们先来看看:

优化2

@Component@RequiredArgsConstructor(onConstructor = @__(@Autowired))public class UserInfoWrap implements IWrap<UsernameWrapObject> {private final UserInfoRepository userInfoRepository;@Overridepublic void wrap(UsernameWrapObject object) {User user = userInfoRepository.findById(object.getUserId());object.setUsername(user.getName());}}public interface UsernameWrapObject {Long getUserId();void setUsername(String userName);}@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.FIELD})public @interface Wrap {Class<? extends IWrap<?>>[] value();}// 给类添加需要 Wrap注解@Wrap(UserInfoWrap.class)@Data // 实现对应接口 public class CardVO implements UsernameWrapObject {@ApiModelProperty("打卡id")private Long id;@ApiModelProperty("打卡人")private String username;... 省略其他参数}

只需在需要赋值对象添加@Wrap注解,声明需要注入 Class,即可实现自动赋值 username

原理

ResponseBodyAdvice 说明

public interface ResponseBodyAdvice<T> {/*** Whether this component supports the given controller method return type* and the selected {@code HttpMessageConverter} type.* @param returnType the return type* @param converterType the selected converter type* @return {@code true} if {@link #beforeBodyWrite} should be invoked;* {@code false} otherwise*/boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);/*** Invoked after an {@code HttpMessageConverter} is selected and just before* its write method is invoked.* @param body the body to be written* @param returnType the return type of the controller method* @param selectedContentType the content type selected through content negotiation* @param selectedConverterType the converter type selected to write to the response* @param request the current request* @param response the current response* @return the body that was passed in or a modified (possibly new) instance*/@NullableT beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,Class<? extends HttpMessageConverter<?>> selectedConverterType,ServerHttpRequest request, ServerHttpResponse response);}

ResponseBodyAdvice相当于拦截器,可以在注解@ResponseBody将返回值处理成相应格式之前,操作返回值。该接口提供两个方法

supports:判断是否支持需要返回参数类型,返回 True,则进入后续beforeBodyWrite方法。beforeBodyWrite:在这里可以对返回值,做一些特殊处理。

刚好符合我们的需求,我们在 VO 返回之前,需要对 返回值进行 set 赋值

PS:这时候有个"聪明"的小朋友可以会问,我的 Controller 没有加@ResponseBody注解啊,怎么会拦截呢?

早期项目 Controller 是需要在每个 Contoller 加入 @ResponseBody 注解,后续 Springboot 为简化这流程,组合 注解@Controller@ResponseBody注解 为@RestController

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Controller@ResponseBody public @interface RestController {/*** The value may indicate a suggestion for a logical component name,* to be turned into a Spring bean in case of an autodetected component.* @return the suggested component name, if any (or empty String otherwise)* @since 4.0.1*/@AliasFor(annotation = Controller.class)String value() default "";}

实战

public interface IWrap<T> {void wrap(T object);}@Componentpublic class WrapStore {private final Map<Class<?>, IWrap<?>> wrapMap;@Autowiredpublic WrapStore(List<IWrap<?>> wraps) {wrapMap = new HashMap<>();wraps.forEach(wrap -> wrapMap.put(wrap.getClass(), wrap));}@SuppressWarnings("unchecked")public <V> void wrap(Class<?> clazz, Object object) {IWrap<V> iWrap = (IWrap<V>) wrapMap.get(clazz);iWrap.wrap((V) object);}}

创建一个 WrapStore 类,主要是管理 IWrap 实现类,通过Spring 构造函数注入对应 IWrap 实现类,然后根据 Class 转化成对应的 Map,便于后续 wrap 处理。

@ControllerAdvice@RequiredArgsConstructor(onConstructor = @__(@Autowired))public class WrapResponseBodyAdvice implements ResponseBodyAdvice<Object> {private final WrapStore wrapStore;@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return returnType.getParameterType().equals(Response.class);}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {Response<?> result = (Response<?>) body;Object data = result.getData();if (data != null) {wrapCurrentClass(data);}return body;}private void wrapCurrentClass(Object data) {Class<?> aClass = data.getClass();Wrap wrap = aClass.getAnnotation(Wrap.class);if (wrap == null) {return;}Class<?>[] wrapClasses = wrap.value();for (Class<?> clazz : wrapClasses) {wrapStore.wrap(clazz, data);}}}

创建一个ResponseBodyAdvice 接口实现类——WrapResponseBodyAdvice

supports ,过滤出需要处理的 Response 类型 返回值。

然后 beforeBodyWrite方法,通过返回值 Class,获取对应@Wrap注解。

然后调用对应wrapStore的 wrap,即可赋值。

注意

WrapResponseBodyAdvice 只展示了简单的功能,后续如果返回值是 Response<PageVO>,需要自行适配。

适合的场景,写适合的代码,切勿拿着锤子看那都是钉子。

提问环节

每次写一些优化代码文章,总有的人会问到:

为了省 2行代码,加入一堆类,值得吗?

有这时间摸鱼不香吗?

这是过度设计,走火入魔!!!

封装这一会确实可以摸好多鱼,没毛病,但是你点进文章就是你的不对了 :)

最后

以上就是全部内容,希望能帮助到你~

如有不妥,欢迎指出,大家一起交流学习。

感谢阅读,下次再见。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。