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