前言
最近入坑了一家坑爹的公司,巨型单体,那代码,脑壳痛。
💩山遍地是,竟没有一片净土!
差点被同化了
致敬那些在屎山开发的程序猿
谁说站在光里的才算英雄 ?
你们才是!
场景
如果
我们的表,没有冗余数据
且实时查询时对应值时,都需要手动的 查询对应值,并赋值,如下代码所示:
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行代码,加入一堆类,值得吗?
有这时间摸鱼不香吗?
这是过度设计,走火入魔!!!
封装这一会确实可以摸好多鱼,没毛病,但是你点进文章就是你的不对了 :)
最后
以上就是全部内容,希望能帮助到你~
如有不妥,欢迎指出,大家一起交流学习。
感谢阅读,下次再见。