【Java EE】统一功能返回

avatar
作者
猴君
阅读量:2

一、拦截器

1.1 拦截器的作用

在对于数据库进行增删查改的时候,如果当前页面不检查用户是否登录,然后就能操作成功是不合理的,解决方法有两个:

  1. 对于已经写好的每个接口都加上一个判断,从Session中获取用户信息,看UserInfo是不是空的,如果是空的,后端就将返回值设为一个错误的状态,然后前端根据这个状态强制跳转到用户登录界面。

这个状态最好是一个类,里面装上:

    1. 状态码(code)
    2. 状态信息(message)
    3. 返回的数据(data)
  1. 可以使用拦截器,对于所有需要用户登录的界面进行拦截。代码如下:

@Slf4j @Component public class LoginInterceptor implements HandlerInterceptor {     @Override     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {         HttpSession session = request.getSession();         UserInfo userInfo = (UserInfo) session.getAttribute(Constants.SESSION_USER_INFO);         if (userInfo == null || userInfo.getId() <= 0) {             log.info("用户未登录!");             response.setStatus(401);             return false;         }         return true;     } } /**  * 拦截器的配置信息  */ //@Configuration public class WebConfig implements WebMvcConfigurer {     @Autowired     LoginInterceptor loginInterceptor;      private List<String> excludePaths = Arrays.asList(             "/user/login",             "/**/*.js",             "/**/*.css",             "/**/*.png",             "/**/*.html"     );     @Override     public void addInterceptors(InterceptorRegistry registry) {         registry.addInterceptor(loginInterceptor)                 .addPathPatterns("/**")                 .excludePathPatterns(excludePaths);     } } 

1.2 使用拦截器的注意事项

  1. 拦截器的代码很短,主要把握好两个部分:

    1. 配置拦截器(招保安)
    2. 定义拦截器(招到以后安排哪部分的安保工作)

添加前置、后置、视图渲染的拦截工作

  1. 拦截器要加@component注解注册为Bean,方便Spring扫描得到

1.3 @DispatcherServlet源码分析

1. 继承关系

  1. DispatcherServlet继承 FrameworkServlet

  2. FrameworkServlet 继承 HttpServletBean

  3. HttpServletBean 继承 HttpServlet

  4. HttpServlet继承 GenericServlet

  5. enericServlet实现 Servlet(是一个接口)

  6. Servlet的声明周期有三部分:

    1. init()
    2. service()
    3. destroy()

2. DispatcherServlet

是从日志中发现了DispatcherServlet这个类。

img

3. initStrategies

	protected void initStrategies(ApplicationContext context) { 		initMultipartResolver(context); 		initLocaleResolver(context); 		initThemeResolver(context); 		initHandlerMappings(context); 		initHandlerAdapters(context); 		initHandlerExceptionResolvers(context); 		initRequestToViewNameTranslator(context); 		initViewResolvers(context); 		initFlashMapManager(context); 	} 
  1. Multipart是和文件相关的,Resolver是解析器
  2. 本地初始化解析器
  3. 主体初始化解析器
  4. 处理器初始化映射
  5. 处理器初始化适配器
  6. 处理器初始化

4. doDispatch

// 主要代码 // 获得处理器 mappedHandler = getHandler(processedRequest); // 获得处理器适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());  // 执行拦截器(预处理阶段),如果返回false就立即停止(被拦截) if (!mappedHandler.applyPreHandle(processedRequest, response)) {     return; } // 使用处理器适配器对于处理器进行调度,返回ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());  // 拦截器的后处理 mappedHandler.applyPostHandle(processedRequest, response, mv);  // 对于所有资源进行整理 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 
4.1. doDispatch-applyPreHandle
	/** 	 * Apply preHandle methods of registered interceptors. 	 * @return {@code true} if the execution chain should proceed with the 	 * next interceptor or the handler itself. Else, DispatcherServlet assumes 	 * that this interceptor has already dealt with the response itself. 	 */ 	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { 		for (int i = 0; i < this.interceptorList.size(); i++) { 			HandlerInterceptor interceptor = this.interceptorList.get(i); 			if (!interceptor.preHandle(request, response, this.handler)) { 				triggerAfterCompletion(request, response, null); 				return false; 			} 			this.interceptorIndex = i; 		} 		return true; 	} 

这是一个拦截器链,然后遍历每一个拦截器,对于各个拦截器进行拦截的前置处理。

4.2. doDispatch-applyPostHandle
	/** 	 * Apply postHandle methods of registered interceptors. 	 */ 	void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) 			throws Exception {  		for (int i = this.interceptorList.size() - 1; i >= 0; i--) { 			HandlerInterceptor interceptor = this.interceptorList.get(i); 			interceptor.postHandle(request, response, this.handler, mv); 		} 	} 

遍历拦截器链上的每一个拦截器,对于其进行后置拦截处理

二、统一数据返回格式

2.1 快速入门

对于每个接口,如果都是一样的返回格式,那么对于前后端交流是非常友好的。

实现代码:

@ControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice {      @Override     public boolean supports(MethodParameter returnType, Class converterType) {         return true;     }      @Override     public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {         return Result.success(body);     } } 
  1. supports方法是决定要不要使用统一返回格式:

    1. true——使用
    2. false——不使用
  2. beforeBodyWrite方法:在写入响应正文前需要做的事情。

    1. 此处就是对于所有的返回正文进行了一次封装,Result就是一个常见的返回类,有code,msg,data等信息

2.2 存在问题

当返回String的时候,会报500的错误。

img

img

原因?源代码整起

先根据错误堆栈信息打个断点试试

img

img

img

子类:

img

追根溯源,

就是因为write方法会传进去一个Result类型的参数给addDefaultHeaders,

而addDefaultHeaders方法是被子类重写过的,

所以会调用子类重写后的方法,(父类中接收这个的参数是一个泛型,对应到这个情况中正好是Result类型)

重写的方法中定死了这个参数是一个String类型,所以会发生类型转换异常。

所以需要给String做一个单独的处理!

处理方法就是使用SpringBoot内置的Jackson对于信息进行序列化,防止源码中的这种错误。

代码实现:

@ControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice {     @Autowired     private ObjectMapper objectMapper;     @Override     public boolean supports(MethodParameter returnType, Class converterType) {         return true;     }      @SneakyThrows     @Override     public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {         // 对于String类型的响应正文进行序列化         if (body instanceof String) {             return objectMapper.writeValueAsString(body);         }         return Result.success(body);     } } 

三、统一异常处理

统一异常也是一个面向切面编程思想的实现,其对于所有发生异常的接口进行捕获。

主要使用到的注解有三个:

  1. @ResponseBody

类注解,因为返回的仍然是数据,是响应正文。

  1. @ControllerAdvice

类注解,统一功能返回都是用这个注解。

  1. @ExceptionHandler

方法注解。

实现代码:

@ControllerAdvice @ResponseBody public class ExceptionAdvice {     @ExceptionHandler     public Object handler(Exception e) {         return Result.failed(e.getMessage());     }      @ExceptionHandler     public Object handler(ArithmeticException e) {         return Result.failed(e.getMessage());     }      @ExceptionHandler     public Object handler(NullPointerException e) {         return Result.failed(e.getMessage());     } } 

3.1 @ControlelrAdvice为什么能够对代码做到不侵入?源码整起

查看@ControlelrAdvice注解的源代码:

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface ControllerAdvice {     // ...... } 

@ControlelrAdvice派生于@Compoent,所以可以不加五大注解也能够被Spring找到。

再看DispatcherServlet的源码,能够找到异常处理器的初始化,工作过程:

	protected void initStrategies(ApplicationContext context) { 		initMultipartResolver(context); 		initLocaleResolver(context); 		initThemeResolver(context); 		initHandlerMappings(context); 		initHandlerAdapters(context); 		initHandlerExceptionResolvers(context); 		initRequestToViewNameTranslator(context); 		initViewResolvers(context); 		initFlashMapManager(context); 	}	 

其中重点关注initHandlerAdapters(context)initHandlerExceptionResolvers(context)这两个方法。

private void initHandlerAdapters(ApplicationContext context) { 		this.handlerAdapters = null;  		if (this.detectAllHandlerAdapters) { 			// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts. 			Map<String, HandlerAdapter> matchingBeans = 					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); 			if (!matchingBeans.isEmpty()) { 				this.handlerAdapters = new ArrayList<>(matchingBeans.values()); 				// We keep HandlerAdapters in sorted order. 				AnnotationAwareOrderComparator.sort(this.handlerAdapters); 			} 		} 		else { 			try { 				HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class); 				this.handlerAdapters = Collections.singletonList(ha); 			} 			catch (NoSuchBeanDefinitionException ex) { 				// Ignore, we'll add a default HandlerAdapter later. 			} 		}  		// Ensure we have at least some HandlerAdapters, by registering 		// default HandlerAdapters if no other adapters are found. 		if (this.handlerAdapters == null) { 			this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class); 			if (logger.isTraceEnabled()) { 				logger.trace("No HandlerAdapters declared for servlet '" + getServletName() + 						"': using default strategies from DispatcherServlet.properties"); 			} 		} 	} private void initHandlerExceptionResolvers(ApplicationContext context) { 		this.handlerExceptionResolvers = null;  		if (this.detectAllHandlerExceptionResolvers) { 			// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts. 			Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils 					.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false); 			if (!matchingBeans.isEmpty()) { 				this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values()); 				// We keep HandlerExceptionResolvers in sorted order. 				AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers); 			} 		} 		else { 			try { 				HandlerExceptionResolver her = 						context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class); 				this.handlerExceptionResolvers = Collections.singletonList(her); 			} 			catch (NoSuchBeanDefinitionException ex) { 				// Ignore, no HandlerExceptionResolver is fine too. 			} 		}  		// Ensure we have at least some HandlerExceptionResolvers, by registering 		// default HandlerExceptionResolvers if no other resolvers are found. 		if (this.handlerExceptionResolvers == null) { 			this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class); 			if (logger.isTraceEnabled()) { 				logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() + 						"': using default strategies from DispatcherServlet.properties"); 			} 		} 	} 

可以看到这两个方法是极其相似的,都是从Bean工厂中提取Bean,如果没有提取到,然后又使用其他手段去获得Bean。

从Bean工厂获得Bean的类型各不相同,分别是:

  1. HandlerAdapter.class
  2. HandlerExceptionResolver.class
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter 		implements BeanFactoryAware, InitializingBean {             @Override         	public void afterPropertiesSet() {         		// Do this first, it may add ResponseBody advice beans         		initControllerAdviceCache();         		initMessageConverters();                 // ...             }             private void initControllerAdviceCache() {                 // 如果这个Bean是空,直接返回         		if (getApplicationContext() == null) {         			return;         		}                          // 获得所有被@ControllerAdvice标注的Bean         		List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());                  		List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();                 //...         	}         } public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver 		implements ApplicationContextAware, InitializingBean {     @Override 	public void afterPropertiesSet() { 		// Do this first, it may add ResponseBodyAdvice beans 		initExceptionHandlerAdviceCache(); 		initMessageConverters();     }      // 如果这个Bean是空,直接返回 	private void initExceptionHandlerAdviceCache() { 		if (getApplicationContext() == null) { 			return; 		}          // 获得所有被@ControllerAdvice标注的Bean 		List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());         for (ControllerAdviceBean adviceBean : adviceBeans) { 			Class<?> beanType = adviceBean.getBeanType(); 			if (beanType == null) { 				throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean); 			}             // 获得异常处理方法的处理器 			ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType); 			if (resolver.hasExceptionMappings()) { 				this.exceptionHandlerAdviceCache.put(adviceBean, resolver); 			} 			if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) { 				this.responseBodyAdvice.add(adviceBean); 			} 		}         // ...     } } public class ExceptionHandlerMethodResolver {     @Nullable 	private Method getMappedMethod(Class<? extends Throwable> exceptionType) { 		List<Class<? extends Throwable>> matches = new ArrayList<>(); 		for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) { 			if (mappedException.isAssignableFrom(exceptionType)) { 				matches.add(mappedException); 			} 		} 		if (!matches.isEmpty()) { 			if (matches.size() > 1) {                 // 如果匹配到多个异常会进行排序,取第一个 				matches.sort(new ExceptionDepthComparator(exceptionType)); 			} 			return this.mappedMethods.get(matches.get(0)); 		} 		else { 			return NO_MATCHING_EXCEPTION_HANDLER_METHOD; 		} 	} } 

他们两个都实现了InitializingBean接口,所以都有afterPropertiesSet这个方法,能够执行一些额外的初始化。

public interface InitializingBean {     void afterPropertiesSet() throws Exception; } 

这个方法是在bean的依赖属性被注入之后(此时Spring Application Context已经有了一些Bean),执行一些额外的初始化

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!