SpringBoot——Web开发之简单功能源码剖析

五、SpringBoot——Web开发

点击查看5.1 简单功能介绍(上篇)——Web开发之简单功能介绍

5.1 简单功能介绍(下篇)

5.1.4 源码剖析——静态资源原理

SpringBoot中,我们大部分的web的自动配置都放在WebMvcAutoConfiguration类中。我们可以去里面看看。(可以按两个Shift,输入类名,也可以自己去依赖里面找)

我们看看他都配了啥:
这个应该挺熟悉的,这个是用来配置让我们表单可以提交put和delete请求方式的。之前SpringMVC讲RESTFul风格的时候讲过。
在这里插入图片描述
回归正题哈,看这个:
他同时绑定了这三个类WebMvcProperties.class, ResourceProperties.class, WebProperties.class。
在这里插入图片描述
其中WebMvcProperties和spring.mvc进行绑定
在这里插入图片描述
ResourceProperties和spring.resources进行绑定
在这里插入图片描述
WebProperties和spring.web进行了绑定
在这里插入图片描述
我们往下再看他的构造器,可以看到,他只有一个有参构造器,这种写法相当于有参构造器的所有参数的值会从容器中确定。
其中

参数描述
ResourceProperties resourceProperties获取和spring.resources绑定的所有的值的对象
WebProperties webProperties获取和spring.web绑定的所有的值的对象
WebMvcProperties mvcProperties获取和spring.mvc绑定的所有的值的对象
ListableBeanFactory
beanFactory
获取Spring的beanFactory
ObjectProvider<HttpMessageConverters>
messageConvertersProvider
获取所有的HttpMessageConverters
ObjectProvider<WebMvcAutoConfiguration.
ResourceHandlerRegistrationCustomizer>
resourceHandlerRegistrationCustomizerProvider
找到资源处理器的定义器
ObjectProvider dispatcherServletPath前端控制器路径
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations给应用注册Servlet、Filter …
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
    this.resourceProperties = (Resources)(resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources());
    this.mvcProperties = mvcProperties;
    this.beanFactory = beanFactory;
    this.messageConvertersProvider = messageConvertersProvider;
    this.resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
    this.dispatcherServletPath = dispatcherServletPath;
    this.servletRegistrations = servletRegistrations;
    this.mvcProperties.checkConfiguration();
}

继续往下拉:
这个代码写的就是我们的资源的默认处理规则啦!!!

public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
    } else {
        this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
        this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
            registration.addResourceLocations(this.resourceProperties.getStaticLocations());
            if (this.servletContext != null) {
                ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
                registration.addResourceLocations(new Resource[]{resource});
            }
        });
    }
}

从上面的代码,我们可以看出来哈,如果我们要禁用 用户对我们静态资源的访问很简单,只需要让第一个if条件!this.resourceProperties.isAddMappings()=true,也就是this.resourceProperties.isAddMappings()=false就可以了。(可以自己往内部看,最后addMapping的默认值是true。)上面那个CLASSPATH_RESOURCE_LOCATIONS就是我们默认的静态资源的路径。

以下代码WebProperties.class类的部分代码截取。

@ConfigurationProperties("spring.web")
public class WebProperties {
    private Locale locale;
    private WebProperties.LocaleResolver localeResolver;
    private final WebProperties.Resources resources;

    public WebProperties() {
        this.localeResolver = WebProperties.LocaleResolver.ACCEPT_HEADER;
        this.resources = new WebProperties.Resources();
    }
        public static class Resources {
        private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
        private String[] staticLocations;
        private boolean addMappings;
        private boolean customized;
        private final WebProperties.Resources.Chain chain;
        private final WebProperties.Resources.Cache cache;

        public Resources() {
            this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
            this.addMappings = true;
            this.customized = false;
            this.chain = new WebProperties.Resources.Chain();
            this.cache = new WebProperties.Resources.Cache();
        }
    }
}

从上面代码,我们可以看到Resources是WebProperties的内部类,而且WebProperties类中还包含一个Resources的对象,他的无参构造方法更是直接new了Resources的无参构造方法,这就让addMappings的默认值为true。
而根据yaml的语法,我们要修改这个addMappings的值,就需要通过他的属性resources来控制addMappings。
在这里插入图片描述
所以我们使用以下语句,就可以修改我们addMappings的默认值啦。(为什么这里是-mapping而不是Mapping,这个是因为配了一个从杠-到驼峰的东东。)
回归正题,修改这个值,我们就可以让我们的用户无法访问我们的静态资源哦。

spring:
  web:
    resources:
      add-mappings: false

如果这个值我们不改的话,我们往下看:

else {
  this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
  this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
      registration.addResourceLocations(this.resourceProperties.getStaticLocations());
      if (this.servletContext != null) {
          ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
          registration.addResourceLocations(new Resource[]{resource});
      }
  });

一句一句解析把:
这一句是处理我们的webjars的。(这是啥?其实就是一个不知道算组织还是什么,我给忘了,他帮我们把一个比如jquery啊啥的封装成jar包了。然后默认放的路径把,就是他下面的/META-INF/resources/webjars/)。

this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");

然后是这一段:

this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
    registration.addResourceLocations(this.resourceProperties.getStaticLocations());
    if (this.servletContext != null) {
        ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
        registration.addResourceLocations(new Resource[]{resource});
    }

this.mvcProperties.getStaticPathPattern()这个,我们可以进去看一下,最后发现他的默认值是/**。
(registration)这个嘛,其实就是他默认写好的{“classpath:/META-INF/resources/”, “classpath:/resources/”, “classpath:/static/”, “classpath:/public/”}啦

所以呢,如果我们要修改this.mvcProperties.getStaticPathPattern()的默认值,我们就修改这个spring.mvc.staticPathPattern即可。第二张图可以看到,它的默认值是/**。
在这里插入图片描述
在这里插入图片描述
配置文件写作:

spring:
  mvc:
    static-path-pattern: /**

如果要修改(registration)这个值,我们可以看到他调用了addResourceLocations(this.resourceProperties.getStaticLocations());也就是说,我们只需要修改this.resourceProperties.getStaticLocations()这个,就可以了。
我们到this.resourceProperties里面,再定位到他的类,可以看到他是别人的一个内部类,那他上级是谁嘞,往上拉。
在这里插入图片描述
好家伙,是这个棱,他的前缀是spring.web
在这里插入图片描述
所以我们要修改那个值就是spring.web.resources.static-locations=[]

spring:
  web:
    resources:
      static-locations: 

我们再看看欢迎页的底层代码:
看这个方法,然后到WelcomePageHandlerMapping这个类下看一下他的构造器方法吧。

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
    WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
    welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));
    welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations());
    return welcomePageHandlerMapping;
}

看下面的if条件if (welcomePage != null && "/**".equals(staticPathPattern)),他写死了staticPathPattern必须是/**,也就是静态资源的访问路径前缀必须是这个,否则就没法setRootViewName(“forward:index.html”);
取而代之的是,他会去Controller看哪个控制器方法能调用我们的index请求。

    WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
        if (welcomePage != null && "/**".equals(staticPathPattern)) {
            logger.info("Adding welcome page: " + welcomePage);
            this.setRootViewName("forward:index.html");
        } else if (this.welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
            logger.info("Adding welcome page template: index");
            this.setRootViewName("index");
        }
    }

5.1.5 源码剖析——Rest映射

之前在将SpringMVC的时候学过Restful,然后现在呢,我们如果要用SpringBoot写Rest风格的代码,怎么搞嘞?看源码:

要让SpringBoot开启他帮我们配好的HiddenHttpMethodFilter,首先,我们还不能自己去注册这个HiddenHttpMethodFilter组件,然后。我们还在在配置文件中配置spring.mvc.hiddenmethod.filter下的enabled。

    @Bean
    @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.mvc.hiddenmethod.filter",
        name = {"enabled"}
    )
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }

OK,那我们就乖乖地去配。

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true

然后写个控制器方法:

package com.example.boot.controller;

import org.springframework.web.bind.annotation.*;

@RestController
public class UserController {

    @GetMapping("/user")
    public String getUser(){
        return "GET-User";
    }

    @PostMapping("/user")
    public String postUser(){
        return "POST-User";
    }

    @PutMapping("/user")
    public String putUser(){
        return "Put-User";
    }

    @DeleteMapping("/user")
    public String deleteUser(){
        return "Delete-User";
    }

}

再在index中加四个form表单,来测试我们的REST:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
    <h1>欢迎光临!!!</h1>
    <h2>测试REST风格:</h2><br>
    测试Get请求:
    <form action="/user" method="get">
        <input value="提交" type="submit">
    </form>
    <br>
    测试Post请求:
    <form action="/user" method="post">
        <input value="提交" type="submit">
    </form>
    <br>
    测试Put请求:
    <form action="/user" method="post">
        <input type="hidden" name="_method" value="put">
        <input value="提交" type="submit">
    </form>
    <br>
    测试Delete请求:
    <form action="/user" method="post">
        <input type="hidden" name="_method" value="delete">
        <input value="提交" type="submit">
    </form>
    <br>
</body>
</html>

看到下图这两个应该就足够证明配置成功了吧。
在这里插入图片描述
在这里插入图片描述
至于这个HiddenHttpMethodFilter为什么可以让我们发送put和delete请求,请看:
HiddenHttpMethodFilter这个类的下面这个方法:(来自源码)
为了能读懂这段源码,下面还给出了这个类的其他几个方法。

public class HiddenHttpMethodFilter extends OncePerRequestFilter {
    private static final List<String> ALLOWED_METHODS;
    private String methodParam = "_method";
  
    static {
        ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
    }
    
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        HttpServletRequest requestToUse = request;
        if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
            String paramValue = request.getParameter(this.methodParam);
            if (StringUtils.hasLength(paramValue)) {
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                if (ALLOWED_METHODS.contains(method)) {
                    requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
                }
            }
        }

        filterChain.doFilter((ServletRequest)requestToUse, response);
    }
}    

简单解读一下吧,就是首先ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));告诉我们ALLOWED_METHODS包含PUT、DELETE、PATCH。然后,匹配我们的请求的方式是不是POST,而且访问的这个Servlet是否也没有异常,如果这两个条件满足的话,获取请求的_method的参数,然后把这个参数转化为纯大写字母去跟ALLOWED_METHODS中的字符做对比。如果匹配成功,那么对其的提交的方式进行封装,将post封装成对应的put、delete或patch方式转发到对应的服务器的控制器方法。

扩展:我们如果不想用_method,也可以自己定义。如下:
自己写一个配置类,然后配Bean。通过HiddenHttpMethodFilter的setMethodParam方法来设置我们要的name。

package com.example.boot.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;

@Configuration(proxyBeanMethods = false)
public class MyConfig {

    @Bean
    HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("function");
        return methodFilter;
    }
}

完后,记得把index里面的值改了,如下:
在这里插入图片描述
编译一下,然后我们可以测试一下,依旧成功访问。

5.1.6 源码剖析——请求映射原理

以前学JavaWeb的时候,我们有学习过Servlet,然后后来对每个请求,我们都要写一个直自己的Servlet方法继承HttpServlet。而这个HttpServlet呢,又通过doGet和doPost两个方法来处理我们接收到的get和post请求。

而现在,我们知道,我们导入web场景的时候,他会自动帮我们配置我们的前端控制器。而且所有的请求都会被他所接收。来,我们看一下他们之间有啥关系。下图可以看到哈,这个HttpServlet是DispatcherServlet的祖先。
在这里插入图片描述
而这个DispatherServlet以及HttpServletBean都没有直接的重写HttpServlet的doGet和doPost方法。所以我们来看一下有重写doGet和doPost的FrameworkServlet。

他重写的doGet和doPost如下,都只是简单的执行一句 this.processRequest(request, response);。

    protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.processRequest(request, response);
    }

    protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.processRequest(request, response);
    }

行吧,既然如此,那就再看看这个processRequest写的是啥。

    protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        LocaleContext localeContext = this.buildLocaleContext(request);
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor());
        this.initContextHolders(request, localeContext, requestAttributes);

        try {
            this.doService(request, response);
        } catch (IOException | ServletException var16) {
            failureCause = var16;
            throw var16;
        } catch (Throwable var17) {
            failureCause = var17;
            throw new NestedServletException("Request processing failed", var17);
        } finally {
            this.resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }

            this.logResult(request, response, (Throwable)failureCause, asyncManager);
            this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);
        }
    }

前面那一块初始化参数的我们就不看了。直接看他最核心的部分:

 try {
            this.doService(request, response);
        } catch (IOException | ServletException var16) {
            failureCause = var16;
            throw var16;
        } catch (Throwable var17) {
            failureCause = var17;
            throw new NestedServletException("Request processing failed", var17);
        } finally {
            this.resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }
            this.logResult(request, response, (Throwable)failureCause, asyncManager);
            this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);
        }
    }

首先,我们先执行 this.doService(request, response); 所以,我们再看看他的doService都做了啥。

    protected abstract void doService(HttpServletRequest var1, HttpServletResponse var2) throws Exception;

好家伙,他直接作为抽象方法,交给别人写去了。行吧,委屈一下,我们再去看他的子类怎么写doService的。(他的子类是DispatcherServlet)

    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        this.logRequest(request);
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap();
            Enumeration attrNames = request.getAttributeNames();

            label116:
            while(true) {
                String attrName;
                do {
                    if (!attrNames.hasMoreElements()) {
                        break label116;
                    }

                    attrName = (String)attrNames.nextElement();
                } while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));

                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }

        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
        if (this.flashMapManager != null) {
            FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
            if (inputFlashMap != null) {
                request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
            }

            request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
            request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
        }

        RequestPath previousRequestPath = null;
        if (this.parseRequestPath) {
            previousRequestPath = (RequestPath)request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
            ServletRequestPathUtils.parseAndCache(request);
        }

        try {
            this.doDispatch(request, response);
        } finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
                this.restoreAttributesAfterInclude(request, attributesSnapshot);
            }

            if (this.parseRequestPath) {
                ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
            }
        }
    }

这里吧,一样的前面都是在配置这个那个东西,我们不管,直接看他最关键的try里面的内容this.doDispatch(request, response);

**这个就是最关键的了!!!!**我们的每个请求,最终其实都需要经过这里。虽然她历经波折,

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            try {
                ModelAndView mv = null;
                Object dispatchException = null;

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }

                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = HttpMethod.GET.matches(method);
                    if (isGet || HttpMethod.HEAD.matches(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }

                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }

                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }

                    this.applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }

                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }

        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }

        }
    }

他这里具体做了啥嘞?我们把主要的代码说一下:
首先processedRequest = this.checkMultipart(request);这一段是用来检查该请求是否是上传文件的请求。咱们先不管,本次要讲的是请求映射,所以主要是这个方法mappedHandler = this.getHandler(processedRequest);获取我们请求的处理方法(要用哪个Controller进行响应)。

我们给个断点,调试来看一下:
首先,肯定是进入我们的getHandler内部
在这里插入图片描述
然后看看这个handlerMappings是啥,里面都有啥:
在这里插入图片描述
我们可以看到前两个,一个是WelcomePageHandlerMapping,一个是RequestMappingHandlerMapping。其中这个WelcomePageHandlerMapping就是用来处理帮我们自动跳转到index页面的。打开来看看:
可以看到他写了一个view,用来跳转到index.html页面
在这里插入图片描述
还有这个pathMatcher下的pathSeparator,用来拦截 / 这个请求。
在这里插入图片描述
然后再看看这个RequestMappingHandlerMapping,RequestMapping看着应该很眼熟吧。就是我们在控制器方法中常用的用来处理请求的注解。这个RequestMappingHandlerMapping类,其实他保存了所有的@RequestMapping和所有的映射规则。来,我们来看一下,我们让他继续往后执行,到这一句HandlerMapping mapping = (HandlerMapping)var2.next();此时,RequestmappingHandlerMapping里面的mappingRegistry里面有数据了。(而且里面的数据,正是我们所有的请求映射)
在这里插入图片描述
如果找不到怎么办呢?找不到他的循环语句就遍历下一个HandlerMapping,也就是我们的WelcomePageHandlerMapping,还没有就继续再下一个HandlerMapping反复下去,直到真的一个也找不到了。那就只能报错404了:

 while(var2.hasNext()) {
     HandlerMapping mapping = (HandlerMapping)var2.next();
     HandlerExecutionChain handler = mapping.getHandler(request);
     if (handler != null) {
         return handler;
     }
 }

总结一下:

  • SpringBoot自动配置欢迎页的WelcomePageHandlerMapping,访问 / 就能访问到我们的index.html
  • SpringBoot自动配置了默认的RequestMappingHandlerMapping
  • 请求进来,逐个尝试所有的HandlerMapping,看是否有对应的请求映射
          1. 如果有就找到这个请求对应的handler
          2. 如果没有就去找下一个HandlerMapping
          3. 实在没有了,报404错误
  • 我们也可以通过给自己的容器放HandlerMapping,以此来定义我们的HandlerMapping
    在这里插入图片描述
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值