SpringBoot底层原理

SpringBoot

一、基本注解

1、@configration详解
/**
 * 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认为单实例
 * 2、配置类本身也是组件
 * 3、proxyBeanMethods:
 *    full(proxyBeanMethods = true):全局  会保存代理对象,外部多次调用配置类中组件的方法,获取的值都是之前在容器中注册的单实例
 *    lite(proxyBeanMethods = false):轻量级 不会保存代理对象,外部多次调用配置类中组件的方法,每次调用都会产生一个新的对象
 *    使用场景为:组件依赖
 *
 *    如何使用:1、如果只是给容器中注册组件,类组件中没有依赖关系,一般调成false,加快springboot的启动速度(lite模式)
 *            2、类组件中有依赖,调成true,保证依赖的组件就为容器中的组件(full模式)
 * 4、@Import({user.class, DBHelper.class})
 *     给容器中自动创建出这俩个类型的组件、默认组件的名字就是全类名
 */
---------------------------------------------------------------------
@Configuration(proxyBeanMethods = true) //被该注解声明的类为配置类 == 配置文件(beans.xml)
@Import({user.class, DBHelper.class})
public class MyConfig {
    @Bean //给容器中添加组件,以方法名作为组件ID,返回类型为组件类型,返回值就是容器中的对象(实例)
    public user user01(){
        user us = new user("小蔡", 20);
        us.setPet(userPet());
//        System.out.println("宠物是==>"+userPet());

        return us;
    }
   @Bean("My")
    public Pet userPet(){
        return new Pet("哈士奇","宠物狗");
    }
}
-------------------main--结果为第一张运行图----------------------
      user user01 = run.getBean("user01", user.class);
      Pet bean = run.getBean("My", Pet.class);
      System.out.println("用户的宠物:"+(user01.getPet() == bean));
-------------------main--结果为第二张运行图----------------------
	 //通过import组件导入的俩个组件进行获取组件
        String[] beanNamesForType = 				          run.getBeanNamesForType(user.class);
        for (String beng : beanNamesForType) {
            System.out.println("实体类的组件名称:"+beng);
        }
        DBHelper helper = run.getBean(DBHelper.class);
        System.out.println("组件名称:"+helper);
----------------------------------------------------------------

在这里插入图片描述

在这里插入图片描述

2、@Conditional详解
@ConditionalOnBean(name = "My") //该注解加载到类上,表明下列组件中没有“My”组件,则所有组件都不生效
 @Configuration(proxyBeanMethods = true) //被该注解声明的类为配置类 == 配置文件(beans.xml)
public class MyConfig { 
    // 加载到某一个具体的方法上时,表名如果没有该组件,则该方法不被加载
       @ConditionalOnBean(name = "My") 
//给容器中添加组件,以方法名作为组件ID,返回类型为组件类型,返回值就是容器中的对象(实例)
    @Bean 
    public user user01(){
        user us = new user("小蔡", 20);
        us.setPet(userPet());
//        System.out.println("宠物是==>"+userPet());

        return us;
    }
//   @Bean("My")
    public Pet userPet(){
        return new Pet("哈士奇","宠物狗");
    }
}
-------------------main--结果为第一张运行图----------------------
    boolean my = run.containsBean("My");
        System.out.println("容器中是否存在该组件:"+my);

        boolean containsBean = run.containsBean("user01");
        System.out.println("容器中是否存在该组件:"+containsBean);     

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B4UPJjNF-1650551284334)(C:\Users\Dell7591\AppData\Roaming\Typora\typora-user-images\image-20220320205613562.png)]

3、@ImportResource详解

使用场景:在配置文件中导入过多的组件,想迁移成注解的方式。就可以用该注解,重新解析

-------------------xml文件中配置属性值---------------------------
<bean id="pet" class="parent.pojo.Pet">
        <property name="name" value="谢逊"></property>
        <property name="petType" value="金毛狮王"></property>
</bean>
-------------通过@ImportResource注解注入到容器中-----------------
 @ImportResource("classpath:bean.xml")
-------------------main--结果为第一张运行图----------------------
		boolean pet = run.containsBean("pet");
        System.out.println("容器中是否有该组件:"+pet);

在这里插入图片描述

4、配置绑定

使用场景:将实体类中的属性值通过配置文件(.properties/.yaml)进行赋值,通过@ConfigurationProperties(prefix = “mypet”)绑定前缀,并且通过添加组件的注解,把其加载到容器中去

方式一:@Component+@ConfigurationProperties(prefix = "mypet")
@Component
@ConfigurationProperties(prefix = "mypet")//绑定配置文件中的属性值
@Data
public class Pet {
    private String name;
    private String PetType;
}
--------------.properties/.yaml)配置文件----------------------
mypet.name=ynu
mypet.PetType=mi
-------------------main-----------------------------------------
@RestController
public class FirstController {
    @Autowired
    Pet pet;
        @RequestMapping("/go")
    public Pet pet(){
        return pet;
    }
}
----------------------------方式二-------------------------------
在配置类(Config)中开启自动绑定,使用该注解就无须在实体类上添加注明添加组件的注解。
    主要用到:实体类可能是第三方包,并没有在类上添加组件的注解
@EnableConfigurationProperties(Pet.class)
    //1、开启Pet配置绑定功能
    //2、把Pet组件自动注入到容器中去
//同时需要关联实体类中的属性
@ConfigurationProperties(prefix = "mypet")

二、自动配置原理

1、引导加载自动配置类
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {}
  1. @SpringBootConfiguration

    • @Configuration:代表当前是一个配置类

    • @ComponentScan:指定扫描包

    • @EnableAutoConfiguration —>这三个注解就相当于**@SpringBootConfiguration** 一个注解

      • @AutoConfigurationPackage
        @Import({AutoConfigurationImportSelector.class})
        public @interface EnableAutoConfiguration {}
        
      • @AutoConfigurationPackage:自动配置包

      • @Import({Registrar.class}) //给容器中导入组件
        public @interface AutoConfigurationPackage {}
        //利用Registrar给容器中导入一系列组件
        //将指定包下的所有的组件导入容器中。这就解释了为什么默认的包路径是Main程序所在的包
        

        @Import({AutoConfigurationImportSelector.class})(核心)

        1. 该方法给容器中导入批量的组件

           AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
          
        2. 调用该方法获取到所有需要导入到容器中的组件(134个)

          List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
          

        在这里插入图片描述

        1. 利用加载工厂得到所有的组件
        Map<String, List<String>> loadSpringFactories(ClassLoader classLoader)
        
        1. 从META-INF/spring.factories位置来加载文件
                      Enumeration urls = classLoader.getResources("META-INF/spring.factories");
        

        默认扫描我们当前系统中所有META-INF/spring.factories位置的文件,其核心文件就是spring-boot-autoconfigure中的spring.factories

        在这里插入图片描述

        1. 按需开启自动配置项
        • 134个场景的自动配置组件,在启动的时候默认全部加载
        • 但最终按照条件装配注解,@ConditionalOnClass该注解就是来确定包下是否有某一个需要加载的类,@ConditionalOnBean确定组件
        1. 修改默认配置
        @Bean
        //容器中有这个类型的组件
        @ConditionalOnBean({MultipartResolver.class})
        @ConditionalOnMissingBean(
        //容器中没有这个名字multipartResolver的组件时
        name = {"multipartResolver"})
        //调用multipartResolver方法,传入参数,并且将参数返回出去,并且将返回值放在容器中
              public MultipartResolver multipartResolver(MultipartResolver resolver) {
        //如果@Bean注解下的方法中有对象参数,这个参数值名称就会从容器中去找,即使用户配置的文件上传解析器名称不是multipartResolver,也会从容器中去找,然后返回出去
                  return resolver;
              }
        
    2、自动配置流程总结
    1. SpringBoot会通过@SpringBootApplication注解下的@EnableAutoConfiguration中的@Import({AutoConfigurationImportSelector.class})加载所有的自动配置类
    2. 每个自动配置类按照条件注解@ConditionalOnClass或者@ConditionalOnMissingClass来决定是否生效,并且默认绑定xxxProperties.class类中指定的值和配置文件(springboot工程下的application.properties)进行绑定
    3. 生效的配置类就会默认给容器中装配组件:比如导入springmvc依赖就会有DispathcherServlet的配置类
    4. 定制化配置
      1. 用户通过在自定义的@Configuration类中,通过@Bean注解添加相应的组件,就会代替底层自动装配的组件
      2. 用户通过获取该配置文件的配置前缀,去修改对应的配置,比如修改缓存:prefix = “spring.cache”—>拿到前缀即可修改
    5. 查看自动配置装配了哪些
      1. 可以在配置文件中配置debug=true,开启自动配置报告。Negative matches(表示没生效的自动配置类)、Positive matches(表示已经生效的自动配置类)

三、Web开发

1、静态资源访问
  1. 静态资源目录

    1. 只要静态资源放在类路径下:当前类路径下的这四个包中–> /static—/public—/resources—/META-INF.resources 。可以直接通过项目根路径 /+静态资源名—>即可访问
    2. 原理:底层规定静态资源映射:/**—>请求进来,先去找Controller能否处理当前请求,能处理则处理,不能处理就将不能处理的所有请求交给静态资源处理器处理,如果静态资源处理器能够处理则处理,不能处理则返回404错误
  2. 静态资源访问前缀

    1. 默认是没有前缀,通过项目根路径 /+静态资源名—>即可访问

      spring.mvc.static-path-pattern= /res/**
      

      当前项目 + static-path-pattern定义的前缀 +静态资源名 = 静态资源文件

  3. 欢迎页支持

    1. 静态资源路径下的index.html

      • 在配置静态资源路径时,配置访问前缀时,访问index.html页面也同时需要加上访问前缀,否则报错

        spring:
          mvc:
            static-path-pattern: /gos/**  #静态资源访问路径前缀
          web:
            resources:
              static-locations: [classpath:/Welcome/] #修改默认静态资源访问包
        
      • 自定义Favicon小图标

        spring:
          mvc:
            static-path-pattern: /gos/**  #静态资源访问路径前缀
            #同时会影响小图标的展示
        
2、静态资源原理

1、流程

  • SpringBoot启动默认加载134个自动配置类
  • SpringMVC功能的自动配置类WebMvcAutoConfiguration,配置类是否生效
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})//是否有这几个类,加载SpringMVC依赖就有
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})//容器中没有该组件就不生效
  • 给容器中配置了什么:配置文件的相关属性和xxx进行绑定
WebMvcProperties=====spring.mvc//(前缀)--->在yaml或者properties配置文件中均可配置相关属性
ResourceProperties=====spring.resources(前缀)//(前缀)--->在yaml或者properties配置文件中均可配置相关属性
//新增WebProperties
WebProperties====spring.web//(前缀)--->在yaml或者properties配置文件中均可配置相关属性

扩展:一个类中只有一个有参构造器—>有参构造器所有参数的值都会从容器中确定

//ResourceProperties resourceProperties:获取所有和spring.resources绑定的所有值的对象
//WebMvcProperties mvcProperties:获取所有和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory:找到IOC,找到spring的beanFactory
//HttpMessageConverters:找到系统里的所有HttpMessageConverters
//resourceHandlerRegistrationCustomizer:找到资源处理器的自定义器
//DispatcherServletPath:找到DispatcherServlet可以处理的路径
//ServletRegistrationBean:给应用注册原生的Servlet、Filter、lisener....
//WebProperties:找到
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();
        }

2、静态资源处理的默认规则

public void addResourceHandlers(ResourceHandlerRegistry registry) {
            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});
                    }

                });
-----------------静态资源欢迎页的处理----------------------------
 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");
        }

    }
-----------------静态资源默认能够存储的四个位置-------------------
            private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};

spring:
  web:
    resources:
      add-mappings: true 
      cache:
        period: 11120 #做缓存处理,时间以秒为单位,不用每次请求都去访问服务器,降低服务器的开销
3、Rest风格处理请求及其原理
  1. 请求映射

    Rest原理(表单提交需带上name = “_method”)

    • 用户发起请求会被HiddenHttpMethodFilter拦截

      • 请求是否正常,并且是否为相应值,如:PUT、DEIETE
        1. 获取"_method"值
        2. 判断是否为Rest风格
        3. 原生request的请求方式只有两种(get、post),而包装模式HttpServletRequestWrapper继承了原生的HttpServletRequest接口,requestWrapper重写了getMethod方法,返回的是传入的值。过滤器放行的时候使用Wrapper。之后的方法调用的是requestWrapper中重写后的getMethod方法
    • Rest风格支持

    //必须要在表单中添加'_method'标记
    public static final String DEFAULT_METHOD_PARAM = "_method";
    
    <input name="_method" type="hidden" value="Put"/>
    
    • 必须在SpringBoot配置文件中手动开启
    spring.mvc.hiddenmethod.filter.enabled=开true/on|关off/false
    
    • 因为SpringBoot底层在自动配置的时候,默认是不加载的,需要手动开启
    @Bean
    @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
    @ConditionalOnProperty(
            prefix = "spring.mvc.hiddenmethod.filter",
            name = {"enabled"}
        )
     public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
            return new OrderedHiddenHttpMethodFilter();
        }
    
    • Controller层实现
    @PutMapping("/user")
    public String TestPut(){return "Put";}
    
    • 底层如何知道请求是Delete请求,因为在请求方法之前需经过Filter方法,而Filter中重写了getMethod,当请求经过filter时,Controller获取的则是重写getMethod方法,并且以delete方式输出
    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);
                    }
                }
            }
    ------------------重写的getMethod方法-----------------------
        public String getMethod() {
                return this.method;
            }
    
  2. 自定义HiddenHttpMethodFilter中的Rest参数

    • 在容器中添加自定义的HiddenHttpMethodFilter方法,并且调用其setMethodParam()就可以更改其属性值
@Configuration(proxyBeanMethods = false)
public class WebConfig {
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_Tan");
        return methodFilter;
    }
}
4、请求映射原理

1、DispatcherServlet

  1. 定义:处理所有请求的开始,继承FrameworkServlet
    在这里插入图片描述

  2. 分析:

    • SpringMVC功能都从DispatcherServlet中的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;
mappedHandler = this.getHandler(processedRequest);
                if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
                        return;
                    }
//找到当前请求使用哪个Handler(Controller方法处理) 
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
  • 首先会得到五个处理器映射
mappedHandler = this.getHandler(processedRequest);
//该方法中加载处理器映射
if (this.handlerMappings != null) {}
  • RequestMappingHandlerMapping:保存了所有@RequestMapping注解和Handler的映射机制
    在这里插入图片描述

  • 该方法中加载所有的处理器映射

在这里插入图片描述

  • 在这里插入图片描述

  • 在这里插入图片描述

  • SpringBoot自动配置欢迎页的WelcomePageHandlerMapping。直接访问’/'请求即可访问到index.html

  • SpringBoot自动配置了默认RequestMappingHandlerMapping,所有请求经过RequestMappingHandlerMapping找到相应的HandlerMapping,如果没有则报错。

//五个处理器
1@RequestMappingHandlerMapping	2@WelcomePageHandlerMapping
3@BeanNameUrlHandlerMapping	4@RouterFunctionMapping
5@SimpleUrlHandlerMapping
5、普通参数与基本注解

1、注解

  • @PathVariable
    @RequestHeader
    @ModelAttribute
    @RequestParam
    @MatrixVariable
    @CookieValue
    @RequestBody
    

2、测试提交

// 定义Url传参
    @GetMapping("/user/{id}/owner/{username}")
    public Map<String, Object> testOwner(
            // 路径变量
           @PathVariable("id") Integer id,
           @PathVariable("username") String name,
            // 将传入的路径变量的值封装到map中
           @PathVariable Map<String,String> pm,
           @RequestHeader("Accept") String accept,
            //拿到所有的请求头信息,封装到map中
            @RequestHeader Map<String,String> Header,
            @RequestParam("age") Integer age,
            @RequestParam("items") String items,
            @RequestParam Map<String,String> Param
            ){
        Map<String, Object> map = new HashMap<>();
        map.put("id",id);
        map.put("name",name);
        map.put("pm",pm);
        map.put("Accept",accept);
        map.put("age",age);
        map.put("items",items);
        map.put("param",Param);
        //map.put("Headers",Header);
        // 将map中的数据返回给页面
        return map;
    }
--------------------拿到表单提交的内容---------------------------
    @PostMapping("/save")
    public Map testBody(
            @RequestBody String content
    ){
        Map<String, Object> map = new HashMap<>();
        map.put("content",content);
        return map;
    }
------------------------测试@RequestAttribute注解----------------
     /*
        测试 @RequestAttribute
     */
    @GetMapping("/goTo")
    public String TestRequestAttribute(
            HttpServletRequest request
    ){
        request.setAttribute("msg","成功了.....");
        request.setAttribute("code","200");
        // 请求转发到当前项目的路径上
        return "forward:/success";
    }
    @ResponseBody
    @GetMapping("/success") // 转发到该路径上
    public Map GoToPage(@RequestAttribute("msg") String msg,
                        @RequestAttribute("code") Integer code,
                        HttpServletRequest request){
        // 原生request中取出值
        Object msg1 = request.getAttribute("msg");
        Object code1 = request.getAttribute("code");
        Map<String, Object> map = new HashMap<>();
        // 拿到原生request中的值,存放到map中,并且展示出来
        map.put("ReqMeth_msg",msg1);
        map.put("ReqMeth_code",code1);
        // 通过注解的方式拿到request中的值,存放到map中,并且展示出来
        map.put("Annotation",msg);
        map.put("Annotation",code);
        return map;
    }
测试首页的基本注解:
<ul>
    <a href="/user/1/owner/王永江?age=18&items=cl&items=dk">传参</a>
    <li>@PathVariable(路径变量)</li>
    <li>@RequestHeader(获取请求头)</li>
    <li>@RequestParam(获取请求参数)</li>
    <li>@CookieValue(获取Cookie值)</li>
    <li>@RequestBody(获取请求体)</li>
    <li>@MatrixVariable(获取矩阵变量)</li>
    <li>@RequestAttribute(获取Request域属性)</li>
</ul>
------------------------表单提交的内容---------------------------
<form action="/save" method="post">
    用户名:<input name="userName"/></br>&nbsp;&nbsp;&nbsp;码:<input name="passWord"/></br>
    <input type="submit" value="登录"/>
</form>
-------------------------矩阵变量--------------------------------
1、问题

​ 在页面开发中,如果Cookie被禁用了,session里面的内容该怎么使用?

​ 答:①:在没禁用之前–>每次发起请求,cookie都会带上JsessionId,服务器按照JsessionId找到具体的session对象,然后调用session的get方法就能得到session中的内容

​ ②:被禁用之后–>因为JsessionId存放在cookie中,就无法通过cookie拿到JsessionId,拿不到JsessionId那么服务器就找不到对应的session对象,从而获取不到session对象中的属性值

​ ③:被禁用之后如何使用session中的内容—>可以通过矩阵变量的方式带上JsessionId(也称为 “路径重写” ),也就是把cookie的值通过矩阵变量的方式进行传递

6、矩阵变量与路径帮助器
  1. 主要用于:cookie被禁用的情况下,通过get方式进行传参

  2. 原理:

    • SpringBoot对于路径的处理,通过UrlPathHelper进行解析,其中removeSemicolonContent(移除分号内容)属性支持矩阵变量,默认为true,默认是禁用矩阵变量的功能,必须手动开启,改为false
  3. 使用**@Bean注解往容器中添加组件WebMvcConfigurer或者实现WebMvcConfigurer接口,并且重写configurePathMatch**方法

  4. ------------------------实现接口的方法-------------------------
    @Override
        // configurePathMatch配置路径映射规则
        public void configurePathMatch(PathMatchConfigurer configurer) {
            UrlPathHelper pathHelper = new UrlPathHelper();
        	// 将true改为false:不移除':'分号后的内容,矩阵变量生效
            pathHelper.setRemoveSemicolonContent(false);
            // UrlPathHelper路径帮助器,默认是禁用掉矩阵变量的使用
            configurer.setUrlPathHelper(pathHelper);
        }
    -------------------------添加组件的方法------------------------
        @Bean
        public WebMvcConfigurer webMvcConfigurer(){
            return new WebMvcConfigurer() {
                @Override
                public void configurePathMatch(PathMatchConfigurer configurer) {
                    UrlPathHelper help = new UrlPathHelper();
               // 设置成false:不移除';'分号后的内容,矩阵变量生效
                    help.setRemoveSemicolonContent(false);
                    configurer.setUrlPathHelper(help);
                }
            };
        }
    
  5. 重点:在使用矩阵变量的时候,一定要绑定在路径变量上@GetMapping(“/cars/{path}”)

// /cars/sell;low=34;brand=byd,audi,yd
    @GetMapping("/cars/{path}")
    public Map carSell(@MatrixVariable("low") Integer low,
                       @MatrixVariable("brand")List<String> brand,
                       @PathVariable("path") String path){
        Map<String, Object> map = new HashMap<>();
        map.put("low",low);
        map.put("brand",brand);
        map.put("path",path);
        return map;
    }
    //url路径变量
    // /boss/1;age=20/2;age=10
    @GetMapping("/boss/{bossId}/{empId}")
    public Map boss(@MatrixVariable(value = "age",pathVar ="bossId" ) Integer bossAge,
                    @MatrixVariable(value = "age",pathVar ="empId" )Integer empAge){
        Map<String, Object> map = new HashMap<>();
        map.put("age",bossAge);
        map.put("age",empAge);
        return map;
    }
<a href="/cars/sell;low=34;brand=byd,audi,yd">one</a>
<a href="/cars/sell;low=34;brand=byd;brand=audi;brand=yd">two(矩阵变量)</a>
<a href="/boss/1;age=20/2;age=10">@MatrixVariable(矩阵变量)/boss/{bossId}/{empId}</a>
7、参数处理原理
1、如何处理
  1. HandlerMapping中找到能处理请求的Handler,也就是Controller方法

  2. 为当前Handler找到一个适配器HandlerAdapter,而最终决定由哪个HandlerAdapter进行处理,是该方法作为判断,如果可以处理直接返回给DispathcerServlet下面的HandlerAdapter

    //DispatcherServlet -- doDispatch 首先进入该方法
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    
    // 执行目标方法 -->在该类中InvocableHandlerMethod        
    Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
    
    
    // 获取当前请求方法的参数值     
    Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
    ------//该方法进行判断当前请求需要哪个适配器进行处理------------
    public final boolean supports(Object handler) {
            return handler instanceof HandlerMethod && this.supportsInternal((HandlerMethod)handler);
        }
    ------------//--------返回给该方法进行处理---------------------
         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;
                    }
                }
            }
            throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
        }
    

    四种适配器

    在这里插入图片描述

    ​ 第0个:支持方法上标注@RequestMapping注解的(常用)

    ​ 第1个:支持函数式编程的(常用)

    2、参数解析器

    ​ 1)SpringMVC的目标方法能写多少种参数类型的参数,取决于底层的参数解析器接口中规定了多少种参数

    // 该方法中规定的27种参数解析器
    if (this.argumentResolvers != null) {   invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
                }
    

在这里插入图片描述

2)参数解析器接口中的方法判断

public interface HandlerMethodArgumentResolver {
    // 判断当前是否支持需要处理的参数
    boolean supportsParameter(MethodParameter var1);
	// 支持则调用该方法进行解析
    @Nullable
    Object resolveArgument(MethodParameter var1, @Nullable ModelAndViewContainer var2, NativeWebRequest var3, @Nullable WebDataBinderFactory var4) throws Exception;
}

3)参数返回值处理器

if (this.returnValueHandlers != null) {
// 规定了15种处理器
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            }

在这里插入图片描述

3、如何确定目标方法每一个参数的值

1)底层原理

//==========代码出处:--->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数组对参数进行存储,长度随着参数个数变化
            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) {
     // 将参数赋值给迭代器,然后依次遍历27个参数解析器,寻找是否有解析器能够解析传入的参数
            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;
    }

4、Servlet API

WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

ServletRequestMethodArgumentResolver 以上的部分参数

能够处理的API

@Override
	public boolean supportsParameter(MethodParameter parameter) {
		Class<?> paramType = parameter.getParameterType();
		return (WebRequest.class.isAssignableFrom(paramType) ||
				ServletRequest.class.isAssignableFrom(paramType) ||
				MultipartRequest.class.isAssignableFrom(paramType) ||
				HttpSession.class.isAssignableFrom(paramType) ||
				(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
				Principal.class.isAssignableFrom(paramType) ||
				InputStream.class.isAssignableFrom(paramType) ||
				Reader.class.isAssignableFrom(paramType) ||
				HttpMethod.class == paramType ||
				Locale.class == paramType ||
				TimeZone.class == paramType ||
				ZoneId.class == paramType);
	}
5、复杂参数

Map、**Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、**Errors/BindingResult、RedirectAttributes( 重定向携带数据)ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

Map<String,Object> map,  Model model, HttpServletRequest request 以上三个参数都是可以给request域中放数据,
    在通过取出
request.getAttribute();

Map、Model类型的参数,会返回 mavContainer.getModel();—> BindingAwareModelMap 是Model 也是Map

mavContainer.getModel(); 获取到值的

8、自定义类型(封装POJO)参数处理原理

ServletModelAttributeMethodProcessor:

---------------------------判断----------------------------------
 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;
}
------------------------进入方法--------------------------------
    public boolean supportsParameter(MethodParameter parameter) {
    // 首先判断改参数是否有@ModelAttribute注解
return parameter.hasParameterAnnotation(ModelAttribute.class) ||
    // 判断这个注解是不是必须的"require = true/false"
this.annotationNotRequired &&     
    // 判断是不是简单属性,“!:表示非简单属性” 返回true表示不是简单类型
    !BeanUtils.isSimpleProperty(parameter.getParameterType());
    }
------------------------进入方法:简单类型判断--------------------
    public static boolean isSimpleValueType(Class<?> type) {
        return Void.class != type && Void.TYPE != type && 		(ClassUtils.isPrimitiveOrWrapper(type) || Enum.class.isAssignableFrom(type) || CharSequence.class.isAssignableFrom(type) || Number.class.isAssignableFrom(type) || Date.class.isAssignableFrom(type) || Temporal.class.isAssignableFrom(type) || URI.class == type || URL.class == type || Locale.class == type || Class.class == type);
    }
--------------------------------核心----------------------------
      @Nullable
    public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
        Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
        String name = ModelFactory.getNameForParameter(parameter);
        ModelAttribute ann = (ModelAttribute)parameter.getParameterAnnotation(ModelAttribute.class);
        if (ann != null) {
            mavContainer.setBinding(name, ann.binding());
        }

        Object attribute = null;
        BindingResult bindingResult = null;
        if (mavContainer.containsAttribute(name)) {
            attribute = mavContainer.getModel().get(name);
        } else {
            try {
                // 先创建一个空的实例对象,然后通过 WebDataBinder绑定器进行绑定
                attribute = this.createAttribute(name, parameter, binderFactory, webRequest);
            } catch (BindException var10) {
                if (this.isBindExceptionRequired(parameter)) {
                    throw var10;
                }

                if (parameter.getParameterType() == Optional.class) {
                    attribute = Optional.empty();
                } else {
                    attribute = var10.getTarget();
                }

                bindingResult = var10.getBindingResult();
            }
        }

        if (bindingResult == null) {
 // web数据绑定器,将请求参数的值绑定到指定的JavaBean中(也就是attribute中,而attribute在绑定数据之前就已经创建了一个空的attribute实例)。封装到binder里,而binder还有conversionService,里面有converters = 125个转换器。
            // 为什么会有125个转换器呢?
            // 因为请求是HTTP(超文本传输协议),认为万物皆文本,通过文本传输数字时,必须转换成java形式的Interget,这个时候就用到其中的转换器
            WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
            if (binder.getTarget() != null) {
                if (!mavContainer.isBindingDisabled(name)) {
                    // 该方法如何进行绑定在下面
                    this.bindRequestParameters(binder, webRequest);
                }

                this.validateIfApplicable(binder, parameter);
                if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {
                    throw new BindException(binder.getBindingResult());
                }
            }

            if (!parameter.getParameterType().isInstance(attribute)) {
                attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
            }

            bindingResult = binder.getBindingResult();
        }

        Map<String, Object> bindingResultModel = bindingResult.getModel();
        mavContainer.removeAttributes(bindingResultModel);
        mavContainer.addAllAttributes(bindingResultModel);
        return attribute;
    }
------------------------如何绑定的核心方法------ -----------------
     protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
    // 1、先获取原生的request请求
        ServletRequest servletRequest = (ServletRequest)request.getNativeRequest(ServletRequest.class);
    // 2、判断请求不为空
        Assert.state(servletRequest != null, "No ServletRequest");
    // 3、拿到(WebDataBinder)web数据绑定器,转换成ServletRequestDataBinder,拿到request中的数据
        ServletRequestDataBinder servletBinder = (ServletRequestDataBinder)binder;
    // 4、再调用bind方法,将原生的request传进来进行绑定
        servletBinder.bind(servletRequest);
    }

核心原理:WebDataBinder(web数据绑定器)会最终把请求中的所有数据利用这125个转换器进行转换,再通过反射绑定到空的JavaBean上

在这里插入图片描述

  • 28
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值