SpringBoot的Web功能

SpringBoot的Web功能

1.1 静态资源访问

  • 静态资源目录(类路径
/static` (or `/public` or `/resources` or `/META-INF/resources

访问:当前项目路径/+静态资源名

spring.mvc.static-path-pattern=/resources/**
#默认是为此配置,根路径

当静态资源和访问的API有冲突时,请求到服务器,会先去找Controller能不能处理,不能处理的请求又都交给静态资源处理器处理,如果静态资源也不能找到,则返回404

1.2 静态资源访问前缀

为了更好的区分动态资源和静态资源,可以对静态资源加*问前缀

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

1.3 欢迎页支持

  • 静态资源路径下 index.html
    • 可以配置静态资源路径
    • 但是不能配置静态资源的访问前缀,否则导致index.html不能被默认访问
  • controller能处理/index

2.1静态资源配置原理

  • SpringBoot启动默认加载xxxAutoConfiguration(自动配置类)
  • 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})
  • 给容器中配置什么功能

    • OrderedHiddenHttpMethodFilter
      
    • OrderedFormContentFilter
      
    • //WebMvcAutoConfigurationAdapter静态内部类
      @Configuration(
          proxyBeanMethods = false
      )
      @Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
      @EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
      @Order(0)
      public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware 
      
  • 配置文件的相关属性和XXX进行了绑定,WebMvcPropertiesspring.mvc,WebPropertiesspring.web进行了绑定

  • 一个配置类只有一个有参构造器

有参构造器所有参数的值都会从容器中确定
//WebProperties webProperties获取所有和Spring.web绑定的所有的值
// WebMvcProperties mvcProperties获取所有和Spring.Mvc所绑定的所有的值
// ListableBeanFactory beanFactory 获取容器工厂
//HttpMessageConverters 获取所有的HttpMessageConverters
// resourceHandlerRegistrationCustomizerProvider 找到资源处理器的自定义器 ======
// dispatcherServletPath 能处理的路径
// ServletRegistrationBean 给应用注册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();
        }

2.2 资源处理的默认规则

//AddMappings 属性控制是否能访问静态资源
//getStaticLocations可以找到相应得静态资源路径
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});
                    }

                });
            }
        }

2.3 欢迎页的处理规则

// HandlerMapping 处理器映射,保存了每一个Handlet能处理哪些请求。
//同样,参数都会从容器中拿
@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
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)) {
            //调用Controller /index
            logger.info("Adding welcome page template: index");
            this.setRootViewName("index");
        }

    }

3.1 请求映射 Rest风格

  • @xxxMapping;

  • Rest风格支持(使用HTPP请求方式动词来表示对资源的操作)

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

    • 核心Filter: HiddenHttpMethodFilter

      • 用法,表达method=post,隐藏域——method=put

      • 必须在SpringBoot中手动开启,

         //REST风格
         @RequestMapping(value = "/user",method = RequestMethod.GET)
         public String getUser(){
             return "GET-USER";
         }
        
         @RequestMapping(value = "/user",method = RequestMethod.PUT)
         public String putUser(){
             return "PUT-USER";
         }
        
         @RequestMapping(value = "/user",method = RequestMethod.POST)
         public String postUser(){
             return "POST-USER";
         }
         
         @RequestMapping(value = "/user",method = RequestMethod.DELETE)
         public String deleteUser(){
             return "DELETE-USER";
         }
        
        
        

      //手动开启的原因,默认enabled是false
      @Bean
      @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
      @ConditionalOnProperty(
      prefix = “spring.mvc.hiddenmethod.filter”,
      name = {“enabled”}
      )
      public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
      return new OrderedHiddenHttpMethodFilter();
      }

      
      Rest原理(**表单**提交时要使用REST风格)
      
      - 表单提交会带上_method=PUT
      - **请求过来被hiddenHttpMethodFilter拦截**
      - 兼容以下请求:PUT,DELETE,PATCH
      - 原生request(post),包装模式requestWrapper重写了HttpServletRequest接口,getMethod,返回的是传入的值,也就是_method的值
      - **过滤器放行的时候时wrapper,以后的方法调用getMethod时调用的requestWrapper的method**
      
      - ```java
      protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
         HttpServletRequest requestToUse = request;
         //判断请求是否正常并且时POST请求
         if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
             //拿到隐藏域中_method的值
             String paramValue = request.getParameter(this.methodParam);
             //判断值不是为空
             if (StringUtils.hasLength(paramValue)) {
                 //将小写转化为大写
                 String method = paramValue.toUpperCase(Locale.ENGLISH);
                 //描写允许请求的方式 包不包括DELETE
                 if (ALLOWED_METHODS.contains(method)) {
                     requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
                 }
             }
         }
      
         filterChain.doFilter((ServletRequest)requestToUse, response);
      }
      

      Rest使用客户端工具

      • 与上述的filter方式无关,如PostMan直接发送Put,delete等方式请求
    • 简化注解

    //    @RequestMapping(value = "/user",method = RequestMethod.GET
        @GetMapping("/user")
        public String getUser(){
            return "GET-USER";
        }
    
    //    @RequestMapping(value = "/user",method = RequestMethod.PUT)
        @PutMapping("/user")
        public String putUser(){
            return "PUT-USER";
        }
    
    
    //    @RequestMapping(value = "/user",method = RequestMethod.POST)
        @PostMapping("/user")
        public String postUser(){
            return "POST-USER";
        }
    
    //    @RequestMapping(value = "/user",method = RequestMethod.DELETE)
        @DeleteMapping("/user")
        public String deleteUser(){
            return "DELETE-USER";
        }
    
    • 设置服务器接受的隐藏域参数
    @Configuration(proxyBeanMethods = false)
    public class WebConfig {
    
        @Bean
        public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
            HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
            methodFilter.setMethodParam("_r");
            return methodFilter;
        }
    }
    

3.2 请求映射原理

​ DispatcherServlet是处理所有请求的开始,类的继承关系,如下

SpringBoot的Web功能

SpringMVC功能分析都从org.springframework.web.servlet.DispatcherServlet->doDispatch()

doDispatch中核心是getHandler()

  • doDispatch()
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //获取原生的request(注意已经是经过3.1过滤过的请求)
        HttpServletRequest processedRequest = request;
        //handler执行链
        HandlerExecutionChain mappedHandler = null;
        //是不是文件上传请求,默认为false
        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来处理请求
                    
                    //会出现com.atguigu.boot.Controller.HelloController#putUser()****
                    
                    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);
            }

        }
    }
  • getHandler()
@Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        //handlerMappings:处理器映射 /xxx->>xxx 表示请求交予谁来处理
        if (this.handlerMappings != null) {
            Iterator var2 = this.handlerMappings.iterator();

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

        return null;
    }

HandlerMapping默认有5个

SpringBoot的Web功能

我们所写的API接口会在RequestMappingHandlerMapping中的mappingRegister(映射注册中心)中找到能处理请求handler

所有的请求映射都在HandlerMapping中

  • SpringBoot自动配置欢迎页的HandlerMapping,访问/能访问道index.html
  • 我们所写的API接口会在RequestMappingHandlerMapping中的mappingRegister(映射注册中心)中找到能处理请求handler
  • 请求今来,挨个尝试所有的HandlerMapping看是否有请求信息
    • 如果有,就找到这个请求所对应的handler
    • 如果没有的话,就换下一个HandlerMapping中
  • 我们需要一些自定义的映射处理,我么也可以自己给容器中方HanndlerMapping,自定义HandlerMapping

3.3 常用web开发注解

  • 前端Rest的get请求
<!--Rest风格的get请求路径-->
<a href="/car/1/owner/lisi?age=18&inters=game&inters=basketball">car/{id}/owner/{username}</a>
  • 常用注解
@RestController
public class ParameterTestController {
	//Rest风格求请
    @GetMapping("/car/{id}/owner/{username}")
    public Map<String,Object> getCar( //路径变量的获取
        							 @PathVariable("id") Integer id,
                                     @PathVariable("username") String username,
                                     @PathVariable Map<String,String> pv,
                                     //请求头参数的获取
                                     @RequestHeader("User-Agent") String userAgent,
                                     @RequestHeader Map<String,String> head,
                                     //请求参数的获取
                                     @RequestParam("age") Integer age,
                                     @RequestParam("inters") List<String> inters,
                                     @RequestParam Map<String,String> params,
                                     //Cookie的获取
                                     @CookieValue("_ga") String _ga,
                                     @CookieValue("_ga") Cookie cookie) {
        Map<String,Object> map = new HashMap<>();
        map.put("id",id);
        map.put("username",username);
        map.put("pv",pv);

        map.put("User-Agent",userAgent);
        map.put("head",head);

        map.put("age",age);
        map.put("inters",inters);
        map.put("params",params);

        map.put("_ga",_ga);
        map.put("_ga_cookie",cookie);


        return map;
    }
    
	//Post请求的获取
    @PostMapping("/save")
    public Map postMethod(@RequestBody String content){
        Map<String,Object> map = new HashMap<>();
        map.put("contnet",content);
        return map;
    }
}

  • 请求转发
//请求转发
@Controller
public class RequestController {

    @GetMapping("/goto")
    public  String goToPage(HttpServletRequest request){
        request.setAttribute("msg","成功了.........");
        request.setAttribute("code","200");
        return "forward:/success";
    }
    //请求转发到success中
    @ResponseBody
    @GetMapping("/success")
    public Map success(@RequestAttribute("msg") String msg,
                          @RequestAttribute("code") String code,
                          HttpServletRequest request){
        Map<String,Object> map = new HashMap<>();
        map.put("msg",msg);
        map.put("code",code);
        map.put("msg_request_Attribute",request.getAttribute("msg"));
        return  map;
    }
}
  • 矩阵变量注解

    • 前端代码
    <!--未使用矩阵变量-->
    /cars/{path}?xxx=xxx&aaa=ccc    查询字符串时,使用@RequestParam
    <!--使用矩阵变量-->
    /cars/{path;low=34;brand=byd,bm,yd}
    
    • 使用场景
    页面开发,cookie禁止了,session内容使用
    
    session.set(a,b)--->jsessionid--->cookie--->每次发请求携带
    
    解决方法:url重写:/abc;jsessionid===xxx 
    
    • 后端代码
    //Spring boot 默认是禁用了矩阵变量的功能
        // 需要手动开启,侧面反应应用较少
        @GetMapping("/cars/sell")
        public Map carsSell(@MatrixVariable("low") Integer low,
                            @MatrixVariable("brand") List<String> brand){
            Map<String,Object> map = new HashMap<>();
            map.put("low",low);
            return  map;
        }
    

    原理:对于路径的处理都是经过UrlPathHelper进行处理的

sion内容使用

session.set(a,b)—>jsessionid—>cookie—>每次发请求携带

解决方法:url重写:/abc;jsessionid===xxx


- 后端代码

```java
//Spring boot 默认是禁用了矩阵变量的功能
    // 需要手动开启,侧面反应应用较少
    @GetMapping("/cars/sell")
    public Map carsSell(@MatrixVariable("low") Integer low,
                        @MatrixVariable("brand") List<String> brand){
        Map<String,Object> map = new HashMap<>();
        map.put("low",low);
        return  map;
    }

原理:对于路径的处理都是经过UrlPathHelper进行处理的

上一篇:iOS--消息转发机制


下一篇:Method类和ClassLoader类