两种方式,推荐第二种

第一种:添加自定义 HandlerMethodReturnValueHandler

原理:spring mvc 的数据处理流程会经过 HandlerMethodReturnValueHandler 的实现方式进行一次数据处理

  1. import org.springframework.beans.factory.InitializingBean;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
  5. import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
  6. import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
  7. import java.util.ArrayList;
  8. import java.util.List;
  9. /**
  10. * spring mvc 在controller处理后会进行下一步 HandlerMethodReturnValueHandler 处理
  11. * 所以在此处进行自己的包装处理,或者完全自定义自己的返回值处理
  12. * 将自己定义的处理器提前
  13. */
  14. @Configuration
  15. public class RestReturnValueHandlerConfigurer implements InitializingBean {
  16. @Autowired
  17. private RequestMappingHandlerAdapter handlerAdapter;
  18. @Override
  19. public void afterPropertiesSet() throws Exception {
  20. List<HandlerMethodReturnValueHandler> list = handlerAdapter.getReturnValueHandlers();
  21. List<HandlerMethodReturnValueHandler> newList = new ArrayList<>();
  22. if (null != list) {
  23. for (HandlerMethodReturnValueHandler valueHandler : list) {
  24. if (valueHandler instanceof RequestResponseBodyMethodProcessor) {
  25. HandlerMethodReturnValueHandlerProxy proxy = new HandlerMethodReturnValueHandlerProxy(valueHandler);
  26. newList.add(proxy);
  27. } else {
  28. newList.add(valueHandler);
  29. }
  30. }
  31. }
  32. handlerAdapter.setReturnValueHandlers(newList);
  33. }
  34. }
  1. import org.springframework.core.MethodParameter;
  2. import org.springframework.web.context.request.NativeWebRequest;
  3. import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
  4. import org.springframework.web.method.support.ModelAndViewContainer;
  5. /**
  6. * 自定义自己的处理方式,此处采用替换系统内的 RequestResponseBodyMethodProcessor实现
  7. */
  8. public class HandlerMethodReturnValueHandlerProxy implements HandlerMethodReturnValueHandler {
  9. private static final int STATUS_CODE_SUCCEEDED = 200;
  10. //返回对象处理方法所在类的白名单
  11. private String[] whiteNames = new String[]{"org.springdoc."};
  12. private HandlerMethodReturnValueHandler proxyObject;
  13. public HandlerMethodReturnValueHandlerProxy(HandlerMethodReturnValueHandler proxyObject) {
  14. this.proxyObject = proxyObject;
  15. }
  16. @Override
  17. public boolean supportsReturnType(MethodParameter returnType) {
  18. return proxyObject.supportsReturnType(returnType);
  19. }
  20. /**
  21. * 此处会根据设置的白名单进行数据的代理包装
  22. * @param returnValue
  23. * @param returnType
  24. * @param mavContainer
  25. * @param webRequest
  26. * @throws Exception
  27. */
  28. @Override
  29. public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
  30. NativeWebRequest webRequest) throws Exception {
  31. boolean isProxy = true;
  32. for (String whiteName : whiteNames) {
  33. if (returnType.getDeclaringClass().getName().contains(whiteName)) {
  34. isProxy = false;
  35. break;
  36. }
  37. }
  38. if (isProxy) {
  39. R hs = new R(returnValue);
  40. proxyObject.handleReturnValue(hs, returnType, mavContainer, webRequest);
  41. } else {
  42. proxyObject.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
  43. }
  44. }
  45. }

第二种

推荐原因

若是采用第一种的话,一些第三方定义的端点接口(例如oauth2的认证、actuator 的端点访问)也会被代理,导致返回的数据不能被其自身组件正确处理,很多时候我们也只是需要处理我们自己业务包内的数据。而第二种方案能够很好的处理这种情况,避免第三方组件使用出现问题。

原理:spring mvc 的数据处理流程经过 HandlerMethodReturnValueHandler 后会进行数据HttpMessageConverter的转换处理. 所以我们在此处进行处理,在spring boot 中通过 实现ResponseBodyAdvice

  1. import com.boya.tancms.common.response.R;
  2. import org.springframework.beans.BeansException;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.context.ApplicationContext;
  5. import org.springframework.context.ApplicationContextAware;
  6. import org.springframework.core.MethodParameter;
  7. import org.springframework.http.MediaType;
  8. import org.springframework.http.converter.HttpMessageConverter;
  9. import org.springframework.http.server.ServerHttpRequest;
  10. import org.springframework.http.server.ServerHttpResponse;
  11. import org.springframework.web.bind.annotation.ControllerAdvice;
  12. import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
  13. import java.util.ArrayList;
  14. import java.util.List;
  15. import java.util.Map;
  16. /**
  17. * 通过判断包名来表明是否支持统一包装返回数据
  18. */
  19. @ControllerAdvice
  20. public class ResponseAdvisor implements ResponseBodyAdvice<Object>, ApplicationContextAware {
  21. //保存需要代理的包
  22. private List<String> basePackage = new ArrayList<>();
  23. @Override
  24. public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
  25. String controllerName = returnType.getDeclaringClass().getPackage().getName();
  26. if (controllerName != null && controllerName.length() > 0) {
  27. for (String p : basePackage) {
  28. if (controllerName.startsWith(p)) {
  29. return true;
  30. }
  31. }
  32. }
  33. return false;
  34. }
  35. @Override
  36. public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
  37. Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
  38. ServerHttpResponse response) {
  39. //数据包装
  40. return new R(body);
  41. }
  42. /**
  43. * 通过从{@link SpringBootApplication#scanBasePackages}接口中获取需要实现数据统一包装的业务包
  44. * @param applicationContext
  45. * @throws BeansException
  46. */
  47. @Override
  48. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  49. //获取有NotAutowired2Service注解的实例
  50. Map<String, Object> beansWithAnnotationMap = applicationContext.getBeansWithAnnotation(SpringBootApplication.class);
  51. Class<? extends Object> clazz = null;
  52. for (Map.Entry<String, Object> entry : beansWithAnnotationMap.entrySet()) {
  53. clazz = entry.getValue().getClass();//获取到实例对象的class信息
  54. SpringBootApplication annotation = clazz.getAnnotation(SpringBootApplication.class);
  55. String[] basePackageArray = annotation.scanBasePackages();
  56. if (basePackageArray.length < 1) {
  57. basePackage.add(clazz.getPackage().getName());
  58. }
  59. }
  60. }
  61. }