spring高级篇(七)

1、异常处理

在DispatcherServlet中,doDispatch(HttpServletRequest request, HttpServletResponse response) 方法用于进行任务处理:

在捕获到异常后没有立刻进行处理,而是先用一个局部变量dispatchException进行记录,然后统一由processDispatchResult() 方法进行处理:

processDispatchResult() 方法中,首先判断异常是否为空,如果为空就不进行处理,然后判断是否是ModelAndViewDefiningException类型异常,如果不是就进入processHandlerException()

processHandlerException() 中,会循环遍历handlerExceptionResolvers集合去匹配并处理异常:

复制代码
	@Nullable
	private List<HandlerExceptionResolver> handlerExceptionResolvers;

HandlerExceptionResolver是一个接口,我们使用ExceptionHandlerExceptionResolver的实现去模拟异常处理的过程:

ExceptionHandlerExceptionResolver专门用于解析 @ExceptionHandler注解,把标注了 @ExceptionHandler注解的方法作为处理异常的方法

复制代码
//专门用于解析 @ExceptionHandler注解,把标注了 @ExceptionHandler注解的方法作为处理异常的方法
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
//设置消息转换json
resolver.setMessageConverters(Collections.singletonList(new MappingJackson2HttpMessageConverter()));
//初始化 加入常用的参数和返回值解析器
resolver.afterPropertiesSet();

testJSON(resolver);

在afterPropertiesSet() 初始化方法中,已经预先定义好了一些参数解析器和返回值处理器:

定义一个控制器:

复制代码
public class Controller1 {

    public void foo(){

    }

    /**
     * 处理异常的方法,并且将返回值转成JSON
     * @param e
     * @return
     */
    @ExceptionHandler
    @ResponseBody
    public Map<String,Object> handle(ArithmeticException e){
        return Collections.singletonMap("error",e.getMessage());
    }
}

resolver.resolveException()方法会检查Controller1中是否有@ExceptionHandler注解标注的方法,如果有,并且参数的异常和实际发生的异常能对应上,就执行其中的逻辑:

复制代码
  private static void testJSON(ExceptionHandlerExceptionResolver resolver) throws NoSuchMethodException {
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        //将控制器的foo方法封装成HandlerMethod对象
        HandlerMethod handlerMethod = new HandlerMethod(new Controller1(),Controller1.class.getMethod("foo"));
        //检查Controller1中是否有@ExceptionHandler注解标注的方法,如果有,并且参数的异常和实际发生的异常能对应上,就执行其中的逻辑
        resolver.resolveException(request,response,handlerMethod,new ArithmeticException("数学异常"));
        System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
    }

此外处理异常的方法还支持ModelAndView类型的返回,与上述解析异常的过程相似。


我们还可以转发自定义的错误处理页面:

复制代码
 /**
     * 转发到自定义的错误页面
     * @return
     */
    @Bean
    public ErrorPageRegistrar errorPageRegistrar(){
        return new ErrorPageRegistrar() {
            @Override
            public void registerErrorPages(ErrorPageRegistry registry) {
                registry.addErrorPages(new ErrorPage("/error"));
            }
        };
    }

    /**
     * 注册后处理器
     * @return
     */
    @Bean
    public ErrorPageRegistrarBeanPostProcessor errorPageRegistrarBeanPostProcessor(){
        return new ErrorPageRegistrarBeanPostProcessor();
    }


    /**
     * Spring Boot中配置自定义的BasicErrorController,用于处理基本的错误页面和错误信息。
     * @return
     */
    @Bean
    public BasicErrorController basicErrorController(){
        ErrorProperties errorProperties = new ErrorProperties();
        errorProperties.setIncludeException(true);
        return new BasicErrorController(new DefaultErrorAttributes(),errorProperties);
    }

2、BeanNameUrlHandlerMapping&SimpleControllerHandlerAdapter

BeanNameUrlHandlerMapping和SimpleControllerHandlerAdapter分别是HandlerMapping和HandlerAdapter的实现类:

在BeanNameUrlHandlerMapping中,以/开头的 bean 的名字会被当作映射路径。这些 bean 本身当作 handler,要求实现 Controller 接口。

准备一个Config类,将BeanNameUrlHandlerMapping和SimpleControllerHandlerAdapter注册成bean:

复制代码
@Configuration
@ComponentScan
@PropertySource("classpath:application.properties")
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})//将配置文件中的属性绑定到对象中
public class Config {

    /**
     *  注册内嵌web容器工厂 tomcat容器
     */
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory(){
        return new TomcatServletWebServerFactory();
    }

    /**
     * 创建DispatcherServlet
     * 首次使用时,由tomcat容器初始化
     * @return
     */
    @Bean
    public DispatcherServlet dispatcherServlet(){
        return new DispatcherServlet();
    }

    /**
     * 注册DispatcherServlet springmvc入口
     * @param dispatcherServlet
     * @return
     */
    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean
    (DispatcherServlet dispatcherServlet,WebMvcProperties webMvcProperties){
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        //设置tomcat容器启动时即进行DispatcherServlet初始化
        registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
        return registrationBean;
    }

    @Bean
    public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping(){
        return new BeanNameUrlHandlerMapping();
    }

    @Bean
    public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter(){
        return new SimpleControllerHandlerAdapter();
    }

    @Bean("/c3")
    public Controller controller3(){
        return (request, response) -> {
            response.getWriter().print("this is c3");
            return null;
        };
    }
    
}

再准备两个实现了Controller接口的控制器类:

复制代码
@Component("/c1")
public class Controller1 implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        response.getWriter().print("this is c1");
        return null;
    }
}

@Component("c2")
public class Controller2 implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        response.getWriter().print("this is c2");
        return null;
    }
}

启动主类:

复制代码
public class A31 {
    public static void main(String[] args) {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(Config.class);

    }
}

我们可以模拟实现这一组映射器和适配器:

定义一个类实现BeanNameUrlHandlerMapping的顶级接口HandlerMapping:

它的作用是在初始化时收集容器中所有以/开头的路径和类成map集合,并且在调用时会判断当前requestURI能否与map集合中的任意元素相匹配:

(复习一下,容器初始化时会收集所有 @RequestMapping 映射信息,封装为 Map)

复制代码
/**
 * 模拟处理器映射器
 * 收集请求中以/开头的bean
 */
@Component
public class MyHandlerMapping implements HandlerMapping {

    /**
     * 处理器映射器,getHandlerMethods中 和当前requestURI 匹配的路径信息
     * @param request
     * @return
     * @throws Exception
     */
    @Override
    public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        String requestURI = request.getRequestURI();
        Controller controller = map.get(requestURI);
        //匹配不上 404
        if (controller == null){
            return null;
        }
        return new HandlerExecutionChain(controller);
    }




    @Autowired
    private ApplicationContext applicationContext;

    private Map<String, Controller> map;

    /**
     * 初始化时收集所有容器中/开头的bean信息
     */
    @PostConstruct
    public void init() {
        Map<String, Controller> beansOfType = applicationContext.getBeansOfType(Controller.class);
        map = beansOfType.entrySet().stream().filter(e -> e.getKey().startsWith("/")).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        System.out.println(map);
    }
}

定义一个类实现HandlerAdapter作为适配器,负责将请求分派给实现了controller接口类中的方法, RequestMappingHandlerAdapter相比,不需要自定义参数和返回值处理器。

复制代码
/**
 * 模拟处理器适配器
 */
@Component
public class MyHandlerAdapter implements HandlerAdapter {

    /**
     * 判断传递的handler是否是当前MyHandlerAdapt支持的
     * @param handler
     * @return
     */
    @Override
    public boolean supports(Object handler) {
        return handler instanceof Controller;
    }

    /**
     * 调用实现了Controller接口的方法
     * 无需参数解析器,返回值处理器
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof Controller){
            ((Controller) handler).handleRequest(request, response);
        }
        return null;
    }

    @Override
    public long getLastModified(HttpServletRequest request, Object handler) {
        return -1;
    }
}

结论:Controller1和Controller3能匹配上,Controller2匹配不上(路径中没有/)

3、RouterFunctionMapping&HandlerFunctionAdapter

RouterFunctionMapping和HandlerFunctionAdapter也是HandlerMapping和HandlerAdapter的实现类:

  • RouterFunctionMapping会收集所有的RouterFunction,请求到达时,根据条件找到HandlerFunction

  • HandlerFunctionAdapter会调用符合条件的HandlerFunction。

    /**
    * 会收集所有的RouterFunction
    * 请求到达时,根据条件找到HandlerFunction
    * @return
    */
    @Bean
    public RouterFunctionMapping routerFunctionMapping(){
    return new RouterFunctionMapping();
    }

    复制代码
      /**
       * 调用符合条件的HandlerFunction
       * @return
       */
      @Bean
      public HandlerFunctionAdapter handlerFunctionAdapter(){
          return new HandlerFunctionAdapter();
      }

RouterFunction分为两部分:匹配规则和具体的执行逻辑(请求是GET类型,并且路径是/r1,就执行new HandlerFunction<ServerResponse>()中的逻辑)

复制代码
   @Bean
    public RouterFunction<ServerResponse> r1(){
        //参数一 匹配规则 参数二 具体的执行逻辑
        return RouterFunctions.route(RequestPredicates.GET("/r1"), new HandlerFunction<ServerResponse>() {
            @Override
            public ServerResponse handle(ServerRequest request) throws Exception {
                return ServerResponse.ok().body("this is r1");
            }
        });
    }

下一篇对Spring MVC 的执行流程做一个总结。

相关推荐
东阳马生架构几秒前
Sentinel源码—7.参数限流和注解的实现一
java·sentinel
李白的粉8 分钟前
基于springboot的在线教育系统
java·spring boot·毕业设计·课程设计·在线教育系统·源代码
码农10087号21 分钟前
Hot100方法及易错点总结2
java
唯独失去了从容35 分钟前
WebRTC服务器Coturn服务器中的通信协议
运维·服务器·webrtc
小马爱打代码1 小时前
SpringBoot原生实现分布式MapReduce计算
spring boot·分布式·mapreduce
iuyou️1 小时前
Spring Boot知识点详解
java·spring boot·后端
北辰浮光1 小时前
[Mybatis-plus]
java·开发语言·mybatis
一弓虽1 小时前
SpringBoot 学习
java·spring boot·后端·学习
南客先生1 小时前
互联网大厂Java面试:RocketMQ、RabbitMQ与Kafka的深度解析
java·面试·kafka·rabbitmq·rocketmq·消息中间件
ai大佬1 小时前
Java 开发玩转 MCP:从 Claude 自动化到 Spring AI Alibaba 生态整合
java·spring·自动化·api中转·apikey