SpringBoot之web功能

1. 静态资源访问

只有把静态资源在类路径下:called /static (or /public or /resources or /META-INF/resources。如下图
在这里插入图片描述
访问资源:当前项目根路径/+ 静态资源名 ,如下图
在这里插入图片描述
原理:静态映射所有请求/**,当请求进来,先去找Controller看能不能处理,不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面

1.1 改变默认的静态资源路径

只需要在application.yml配置属性和在类路径下创建同名的文件夹即可。

spring:
  web:
    resources:
      static-locations: [classpath:/res/]

1.2 静态资源访问前缀

访问静态静态默认是无前缀的,但是为了过滤请求,一般都会自定义访问前缀。在application.yml配置属性

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

1.3 源码分析

前面我们说过SpringBoot的自动配置原理,SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类),帮助我们自动配置很多组件。在spring-boot-autoconfigure-2.6.6.jarorg.springframework.boot.autoconfigure中配置了web组件。如图
在这里插入图片描述
SpringMVC功能的自动配置类 WebMvcAutoConfiguration,已经生效

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {

WebMvcAutoConfiguration配置类中为我们配置了很多组件,并帮助我们和配置文件做了绑定

    @Configuration( proxyBeanMethods = false)
    @Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
    @EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {

配置文件的相关属性和xxx进行了绑定。

  • WebMvcProperties和spring.mvc做了属性绑定
@ConfigurationProperties(
    prefix = "spring.mvc"
)
public class WebMvcProperties {
  • WebProperties和spring.web做了属性绑定。
@ConfigurationProperties("spring.web")
public class WebProperties {
  • 以后配置web功能的属性都可以参考这个类
  • 我们发现WebMvcAutoConfigurationAdapter配置类只有一个有参构造器,这代表有参构造器所有参数的值都会从容器中确定。
  /**
  *(WebProperties webProperties:获取和spring.web绑定的所有的值的对象
  *WebMvcProperties mvcProperties:获取和spring.mvc绑定的所有的值的对象
  *ListableBeanFactory beanFactory:Spring的beanFactory
  *ObjectProvider<HttpMessageConverters> messageConvertersProvider:获取所有的HttpMessageConverters
  *ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider:找到资源处理器的自定义器。
  *ObjectProvider<DispatcherServletPath> dispatcherServletPath, 
  *ObjectProvider<ServletRegistrationBean<?>> servletRegistrations:给应用注册Servlet、Filter....
  */
  public WebMvcAutoConfigurationAdapter(WebProperties webProperties, 
										WebMvcProperties mvcProperties, 
										ListableBeanFactory beanFactory,
										ObjectProvider<HttpMessageConverters> messageConvertersProvider,
										ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, 
										ObjectProvider<DispatcherServletPath> dispatcherServletPath, 
										ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
            this.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) {
			//判断是否禁用静态处理器,spring:web:resourcesadd-mappings: false
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
            } else {
            	//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});
                    }

                });
            }
        }
spring:
  web:
    resources:
      add-mappings: true   #禁用所有静态资源规则

2. 欢迎页支持

静态资源路径下 index.html

  • 可以配置静态资源路径
  • 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
spring:
#  mvc:
#    static-path-pattern: /aa/**   这个会导致welcome page功能失效

  resources:
    static-locations: [classpath:/res/]

也可以通过controller来处理/index。

2.1 源码分析

HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求。 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;
        }

//处理欢迎页的处理器映射
 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");
            // 调用Controller  /index
            this.setRootViewName("index");
        }

    }

3. 自定义 Favicon

favicon.ico 放在静态资源目录下即可

spring:
#  mvc:
#    static-path-pattern: /res/**   这个会导致 Favicon 功能失效

4. 请求参数处理

4.1 请求映射

4.1.1 REST使用与原理

Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
以前使用url请求资源:

  • /getUser 获取用户
  • /deleteUser 删除用户
  • /editUser 修改用户
  • /saveUser 保存用户

使用Rest风格url请求资源:

  • /user GET-获取用户
  • DELETE-删除用户
  • PUT-修改用户
  • POST-保存用户

接下来我们使用SpringBoot实现Rest风格的Http请求。

  1. 第一步:我们在static路径下创建一个测试页面
<body>
<h1>Hello, SpringBoot</h1>

<form action="/user" method="get">
    <input type="submit" value="get提交">
</form>
<br>
<form action="/user" method="post">
    <input type="submit" value="post提交">
</form>
<br>
<form action="/user" method="PUT">
    <input type="submit" value="PUT提交">
</form>
<br>
<form action="/user" method="DELETE">
    <input type="submit" value="DELETE提交">
</form>
<br>

</body>
  1. 第二步:编写UserController控制器
@RestController
public class UserController {

    @GetMapping("/user")
    //@RequestMapping(value = "/user",method = RequestMethod.GET)
    public String getUser(){
        return "GET-张三";
    }

    @PostMapping("/user")
    //@RequestMapping(value = "/user",method = RequestMethod.POST)
    public String saveUser(){
        return "POST-张三";
    }


    @PutMapping("/user")
    // @RequestMapping(value = "/user",method = RequestMethod.PUT)
    public String putUser(){
        return "PUT-张三";
    }

    @DeleteMapping("/user")
    //@RequestMapping(value = "/user",method = RequestMethod.DELETE)
    public String deleteUser(){
        return "DELETE-张三";
    }
}
  1. 第三步:测试
    在这里插入图片描述

get提交的测试图:
在这里插入图片描述

post提交的测试图:
在这里插入图片描述

PUT提交的测试图和DELETE提交的测试图:
在这里插入图片描述

  • 我们发现PUT和DELETE请求的最后没有生效,还是使用默认的get请求。这是因为form表单只有get和post两种请求方式,Rest使用客户端工具,如PostMan直接发送Put、delete等方式请求,无需Filter。但是如果使用表单来发送其他请求,还必须借助HiddenHttpMethodFilter过滤器。
  • HiddenHttpMethodFilter可以帮我们通过表单method=post隐藏域 _method=put或者delete的方式来发送请求。SpringBoot已经帮我们配置好了。但是默认是禁用的,必须手动开启
spring:
 mvc:
   hiddenmethod:
     filter:
       enabled: true   #开启页面表单的Rest功能
  @Bean
    @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.mvc.hiddenmethod.filter",
        name = {"enabled"}
    )
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }

  • 启用配置好就能正常访问了,不过还需要修改页面
<body>
<h1>Hello, SpringBoot</h1>

<form action="/user" method="get">
    <input type="submit" value="get提交">
</form>
<br>
<form action="/user" method="post">
    <input type="submit" value="post提交">
</form>
<br>
<form action="/user" method="post">
    <input type="hidden" name="_method" value="PUT">
    <input type="submit" value="PUT提交">
</form>
<br>
<form action="/user" method="post">
    <input type="hidden" name="_method" value="DELETE">
    <input type="submit" value="DELETE提交">
</form>
<br>

</body>

4.1.2 原理分析

上面我们说过表单PUT和DELERTE请求是通过HiddenHttpMethodFilter过滤器来帮助我们重写包装请求。源码分析

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
		//获取request
        HttpServletRequest requestToUse = request;
        //如果是POST请求且没有发生异常
        if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
        	//获取this.methodParam也就是_method的属性的值
            String paramValue = request.getParameter(this.methodParam);
            //判断是否存在
            if (StringUtils.hasLength(paramValue)) {
           		 //帮我们转换成大写
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                //是否是允许的请求范围之内
                if (ALLOWED_METHODS.contains(method)) {
                	// 原生request(post),使用包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
                    requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
                }
            }
        }
		//过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
        filterChain.doFilter((ServletRequest)requestToUse, response);
    }

当表单提交要使用REST风格的时候

  • 表单提交会带上_method=PUT。
  • 请求过来被HiddenHttpMethodFilter拦截。
  • 如果请求是POST并且是正常的,先获取到_method的值,如果是小写也会自动帮我转换成大写,然后判断是否兼容以下请求;PUTDELETEPATCH
  • 如果是兼容的请求,把原生request(post),使用包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
  • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。

4.1.3 如何把_method 换成我们自己喜欢的

只需要自定义一个filter即可

	//自定义filter
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_self");
        return methodFilter;
    }

4.2 请求映射原理

4.2.1 SpringMVC工作原理

在这里插入图片描述

  1. 用户将请求发送到前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用HandlerMapping处理器映射器。
  3. 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
  4. DispatcherServlet调用HandlerAdapter处理器适配器。
  5. HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
  6. Controller执行完成返回ModelAndView。
  7. HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。
  8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
  9. ViewReslover解析后返回具体View。
  10. DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
  11. DispatcherServlet响应用户。

组件说明:
以下组件通常使用框架提供实现:

  • DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性。
  • HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
  • HandlAdapter:通过扩展处理器适配器,支持更多类型的处理器。
  • ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel等。

组件:

  1. 前端控制器DispatcherServlet(不需要工程师开发),由框架提供
    作用:接收请求,响应结果,相当于转发器,中央处理器。有了dispatcherServlet减少了其它组件之间的耦合度。
    用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。
  2. 处理器映射器HandlerMapping(不需要工程师开发),由框架提供
    作用:根据请求的url查找Handler
    HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
  3. 处理器适配器HandlerAdapter
    作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler
    通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
  4. 处理器Handler(需要工程师开发)
    注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler
    Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。
    由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。
  5. 视图解析器View resolver(不需要工程师开发),由框架提供
    作用:进行视图解析,根据逻辑视图名解析成真正的视图(view)
    View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。
    一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由工程师根据业务需求开发具体的页面。
  6. 视图View(需要工程师开发jsp…)
    View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf…)

4.2.2 SpringBoot中springmvc源码分析

当用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
之前ssm框架中还需要我们手动在web.xml中配置DispatcherServlet,但是在SpringBoot中已经帮我们自动配置了,可以直接使用。

   @Bean(
            name = {"dispatcherServlet"}
        )
        public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
            DispatcherServlet dispatcherServlet = new DispatcherServlet();
            dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
            dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
            dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
            dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
            dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
            return dispatcherServlet;
        }

SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet->doService()->doDispatch()。也就是说具体的逻辑都在doDispatch()方法中。

 protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
 //省略其他代码
        try {
            this.doDispatch(request, response);
        } finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
                this.restoreAttributesAfterInclude(request, attributesSnapshot);
            }

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

    }

doDispatch()方法源码

 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;
                    // 找到当前请求使用哪个Handler(Controller的方法)处理
                    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);
            }

        }
    }

4.2.2.1 如何通过url获得HandlerMapping

HandlerMapping 将会把请求映射为HandlerExecutionChain 对象(包含一个Handler 处理器(页面控制器)对象、多个HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新的映射策略;

比如我们通过访问一个http://localhost:9080/hello来分析源码中如何得到Handler 。

 // 找到当前请求使用哪个Handler(Controller的方法)处理
                    mappedHandler = this.getHandler(processedRequest);

    @Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    	//获取对应的HandlerMapping
        if (this.handlerMappings != null) {
            Iterator var2 = this.handlerMappings.iterator();

			//遍历HandlerMapping寻找到对应的Handler
            while(var2.hasNext()) {
                HandlerMapping mapping = (HandlerMapping)var2.next();
                HandlerExecutionChain handler = mapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }

        return null;
    }

通过debug分析,发现handlerMappings 存在五个HandlerMapping。

  • RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。也就是我们自己编写的Controller。
  • welcomePageHandlerMapping:欢迎页的处理。
    在这里插入图片描述
    执行到HandlerMapping mapping = (HandlerMapping)var2.next();发现我们自己编写的Controller。

HandlerMapping mapping = (HandlerMapping)var2.next();
最后返回HandlerExecutionChain对象,(包含一个Handler 处理器(页面控制器)对象、多个HandlerInterceptor 拦截器)对象
在这里插入图片描述

  • 所有的请求映射都在HandlerMapping中。
  • SpringBoot自动配置了默认 的 RequestMappingHandlerMapping。
  • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html。
  • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。如果有就找到这个请求对应的handler,然后返回对应的handler和拦截器。如果没有就是下一个HandlerMapping。
  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping
4.2.2.2 HandlerAdapter处理器处理请求参数

DispatcherServlet——>HandlerAdapter,HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView 对象(包含模型数据、逻辑视图名);

接下来我们写一个UserController 类来测试

@RestController
public class UserController {

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

在org.springframework.web.servlet.DispatcherServlet类的doDispatch()方法中。

  • 为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter
  • 适配器执行目标方法并确定方法参数的每一个值
 HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        if (this.handlerAdapters != null) {
            Iterator var2 = this.handlerAdapters.iterator();

            while(var2.hasNext()) {
                HandlerAdapter adapter = (HandlerAdapter)var2.next();
                if (adapter.supports(handler)) {
                    return adapter;
                }
            }
        }
    }

在这里插入图片描述

  • 0 : 支持方法上标注@RequestMapping
  • 1 :支持函数式编程的

执行目标方法

//DispatcherServlet -- doDispatch
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

在org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter类执行目标方法

mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法

参数解析器-HandlerMethodArgumentResolver
确定将要执行的目标方法的每一个参数的值是什么;
SpringMVC目标方法能写多少种参数类型。取决于参数解析器。
在这里插入图片描述
在这里插入图片描述

  • 当前解析器是否支持解析这种参数
  • 支持就调用 resolveArgument

返回值处理器

在这里插入图片描述
真正执行目标方法

//ServletInvocableHandlerMethod
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//获取方法的参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

如何确定目标方法每一个参数的值
在InvocableHandlerMethod类中

 protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        MethodParameter[] parameters = this.getMethodParameters();
        if (ObjectUtils.isEmpty(parameters)) {
            return EMPTY_ARGS;
        } else {
            Object[] args = new Object[parameters.length];

            for(int i = 0; i < parameters.length; ++i) {
                MethodParameter parameter = parameters[i];
                parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
                args[i] = findProvidedArgument(parameter, providedArgs);
                if (args[i] == null) {
                    if (!this.resolvers.supportsParameter(parameter)) {
                        throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
                    }

                    try {
                        args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
                    } catch (Exception var10) {
                        if (logger.isDebugEnabled()) {
                            String exMsg = var10.getMessage();
                            if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                                logger.debug(formatArgumentError(parameter, exMsg));
                            }
                        }

                        throw var10;
                    }
                }
            }

            return args;
        }
    }

挨个判断所有参数解析器那个支持解析这个参数

 @Nullable
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
        if (result == null) {
            Iterator var3 = this.argumentResolvers.iterator();

            while(var3.hasNext()) {
                HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
                if (resolver.supportsParameter(parameter)) {
                    result = resolver;
                    this.argumentResolverCache.put(parameter, resolver);
                    break;
                }
            }
        }

        return result;
    }

解析这个参数的值

调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可
4.2.2.3Map、Model原理缺少

37集到42集没看,后续补上。

4.2.2.3

5、ModelAndView的逻辑视图名——> ViewResolver, ViewResolver 将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;
6、View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;
7、返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。

5. 视图解析与模板引擎

视图解析:SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染。

5.1 视图解析

5.1 thymeleaf使用

5.1.1 引入Starter

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

5.1.1 自动配置好了thymeleaf

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration { }

自动配好的策略

  • 所有thymeleaf的配置值都在 ThymeleafProperties。
  • 配置好了 SpringTemplateEngine 。
  • 配好了 ThymeleafViewResolver 。
  • 我们只需要直接开发页面
	public static final String DEFAULT_PREFIX = "classpath:/templates/";

	public static final String DEFAULT_SUFFIX = ".html";  //xxx.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值