源码理解SpringBoot参数处理(总体第二篇)

上一篇文章中已经学习到了我们的SpringBoot相比较Spring和SpringMVC给我带来的便利之处,和观察源代码的一些总结,接下来学习SpringBoot的一些系统知识。

一、看一些不重要部分

1、Lombok

  • 首先看依赖
 <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>
  • 这个最常用的就是用在下面的例子上:数据的person表和entities下面的person类,我们在写的时候
    在这里插入图片描述

2、Devtools

在这里插入图片描述
这个是热更新,就是当你的项目有修改,那你的项目就会重启。

3、Spring Initailizr

其实就是SpringBoot自动初始化方式
在这里插入图片描述
在这里插入图片描述
如果你的idea是社区版,那你就不用找了,是没有这个的。老老实实的像我之前那种创建maven项目吧。

  • 首先它会创建项目结构
  • 会给你创建好启动类
  • 会给你创建好application.properties文件
  • 自动引入启动的基础依赖。
  • 但是、但是、但是,如果你是社区版也不要慌,你创建项目都是一样的效果,建maven项目,导入依赖,创建application.properties或者application.yml文件,最后写启动类

4、SpringBoot项目之yml文件

application.properties和application.yml文件是一样的,都是配置文件。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
XXXProperties.class里面有的属性,就是我们可以在配置文件里面写的,另外我们可以自己写。
在这里插入图片描述
这发现helloworld没有用‘helloworld’或者“helloworld”标记,其他我们不写和‘’都是会输出你的值,而用了“”之后,会把你这个按着字符串处理,就是你“hello /n world”都是会输出hello /n world的。

比如还有什么List啊,Array啊,Map什么的,怎么写,具体的可以了解一下这篇文章

5、配置在配置文件里面编写的时候有提示

  • 上面我们在写的时候没有任何提示【下面这种有提示很好,那我们可以引入依赖】
    在这里插入图片描述
  • 导入依赖
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
  • 另外SpringBoot建议在打包的时候写上如下,因为我们这个仅仅是开发的时候有用,发布没什么用。
    在这里插入图片描述

二、Web部分

(一)SpringMVC自动配置

1、静态资源规则和定制化

(1)静态资源放哪里

  • 只要静态资源放在类路径下: classpath /static (or /public or /resources or /META-INF/resources
  • 访问 : 当前项目根路径/ + 静态资源名
    在这里插入图片描述

(2)查看查找原理

它为什么请求来的不是一个请求,而且是一个静态资源,原因就是如下

  • 原理: 静态映射/**。
    请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面

(3)配置访问前缀和路径

#前缀,加上之后,如果要想访问静态资源必须加前缀
spring:
  mvc:
    static-path-pattern: /res/**
#修改路径
  resources:
    static-locations: [classpath:/haha/]

(4)werjars

只要想访问某一个官方提供的静态资源,那我们可以写/webjars/xxxx/文件名

(5)自定义index页面

- 只要把你的欢迎页写为index.html即可

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

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

(6)Favicon

在这里插入图片描述

(7)静态资源配置原理

  • SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
  • SpringMVC功能的自动配置类 WebMvcAutoConfiguration,生效

在这里插入图片描述

  • 给容器中配了什么。
    在这里插入图片描述
    昨天看的对XXXProperties文件进行绑定吧,这就是我们能配置的元婴了。

  • 只有一个有参构造器,所有参数的值都要从容器中确定。
    在这里插入图片描述

//有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
//DispatcherServletPath  
//ServletRegistrationBean   给应用注册Servlet、Filter....
	public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
				ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
				ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
				ObjectProvider<DispatcherServletPath> dispatcherServletPath,
				ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
			this.resourceProperties = resourceProperties;
			this.mvcProperties = mvcProperties;
			this.beanFactory = beanFactory;
			this.messageConvertersProvider = messageConvertersProvider;
			this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
			this.dispatcherServletPath = dispatcherServletPath;
			this.servletRegistrations = servletRegistrations;
		}
  • 资源处理的默认规则
    在这里插入图片描述
    在这里插入图片描述
spring:
#  mvc:
#    static-path-pattern: /res/**

  resources:
    add-mappings: false   禁用所有静态资源规则
  • 欢迎页的处理
    在这里插入图片描述

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

	WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
			ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
		if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
            //要用欢迎页功能,必须是/**
			logger.info("Adding welcome page: " + welcomePage.get());
			setRootViewName("forward:index.html");
		}
		else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
            // 调用Controller  /index
			logger.info("Adding welcome page template: index");
			setRootViewName("index");
		}
	}

(二)请求参数处理

1、请求处理

(1)rest使用与原理

  • @xxxMapping;
  • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
    • 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
    • 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
    • 核心Filter;HiddenHttpMethodFilter
      • 用法: 表单method=post,隐藏域 _method=put
      • SpringBoot中手动开启
    • 扩展:如何把_method 这个名字换成我们自己喜欢的。
@RequestMapping(value = "/user",method = RequestMethod.GET)
    public String getUser(){
        return "GET-张三";
    }

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


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

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


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


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

在这里插入图片描述

(2)看一下源代码如何开启

  • 首先底层已经像如下的处理了
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    经过这样写,可以把前端写成如下进行测试
    在这里插入图片描述
    测试结果发现还是不行

  • 原因就是

	@Bean
	@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
	//它这里默认是不开启状态,所以要手动开启
	@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled")
	public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
		return new OrderedHiddenHttpMethodFilter();
	}

在这里插入图片描述

(3)基于表单提交使用rest的原理

表单提价带上_method=xxx,过来后会被filter拦截,原理就是给容器里面放了一个HiddenHttpMethodFilter,同样也会去请求的方法是不是post方法,
在这里插入图片描述
在这里插入图片描述

  • 如果Rest使用客户端工具,
    如PostMan直接发送Put、delete等方式请求,无需Filter。

  • 我们还可以自己添加前端能发的请求。
    在这里插入图片描述

2、请求映射原理

在这里插入图片描述
在这里插入图片描述

  • 分析org.springframework.web.servlet.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 {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 找到当前请求使用哪个Handler(Controller的方法)处理
				mappedHandler = getHandler(processedRequest);
                
                //HandlerMapping:处理器映射。/xxx->>xxxx

在这里面打断点可以跳转到如下这一步
在这里插入图片描述
在这里插入图片描述
RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。
在这里插入图片描述
在这里插入图片描述
会去调用这个方法,找所对应的某一个实例。
在这里插入图片描述
在这里插入图片描述

3、底层处理请求可以接受的传参类型

(1)注解类型

@PathVariable、@RequestHeader、@RequestParam、@CookieValue、@RequestBody

  • PathVariable:路径变量,它可以获取路径变量
  • RequestHeader:获取请求头的一些信息
  • RequestParam:获取请求的参数
  • CookieValue:拿到cookie对象,拿到对象后就可以做其他的了
  • RequestBody:获取请求体【这个比较重要的,在你前端的是个post对象的时候就用处很多】
@RestController
public class ParameterTestController {


    //  car/2/owner/zhangsan
    @GetMapping("/car/{id}/owner/{username}")
    public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                     @PathVariable("username") String name,
                                     @PathVariable Map<String,String> pv,
                                     @RequestHeader("User-Agent") String userAgent,
                                     @RequestHeader Map<String,String> header,
                                     @RequestParam("age") Integer age,
                                     @RequestParam("inters") List<String> inters,
                                     @RequestParam Map<String,String> params,
                                     @CookieValue("_ga") String _ga,
                                     @CookieValue("_ga") Cookie cookie){


        Map<String,Object> map = new HashMap<>();

//        map.put("id",id);
//        map.put("name",name);
//        map.put("pv",pv);
//        map.put("userAgent",userAgent);
//        map.put("headers",header);
        map.put("age",age);
        map.put("inters",inters);
        map.put("params",params);
        map.put("_ga",_ga);
        System.out.println(cookie.getName()+"===>"+cookie.getValue());
        return map;
    }


    @PostMapping("/save")
    public Map postMethod(@RequestBody String content){
        Map<String,Object> map = new HashMap<>();
        map.put("content",content);
        return map;
    }



}

@ModelAttribute、@MatrixVariable

  • @MatrixVariable:假设你的cookie被禁用了
    //1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
    //2、SpringBoot默认是禁用了矩阵变量的功能
    // 手动开启:原理。对于路径的处理。UrlPathHelper进行解析。
    // removeSemicolonContent(移除分号内容)支持矩阵变量的
    //3、矩阵变量必须有url路径变量才能被解析
    在这里插入图片描述
    可以如下两种方式设置支持矩阵变量进行处理。
    在这里插入图片描述
    在这里插入图片描述

    //1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
    //2、SpringBoot默认是禁用了矩阵变量的功能
    //      手动开启:原理。对于路径的处理。UrlPathHelper进行解析。
    //              removeSemicolonContent(移除分号内容)支持矩阵变量的
    //3、矩阵变量必须有url路径变量才能被解析
    @GetMapping("/cars/{path}")
    public Map carsSell(@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;
    }

    // /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("bossAge",bossAge);
        map.put("empAge",empAge);
        return map;

    }

4、请求的支持的参数处理的原理

1)首先来看点原理

(1)找到具体的响应方法

在这里插入图片描述

(2)找到具体解析器

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2)支持的注解参数原理

(1)首先看大致的介绍

  • HandlerMapping中找到能处理请求的Handler(Controller.method())
  • 为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter
  • 适配器执行目标方法并确定方法参数的每一个值
    在这里插入图片描述

(2)HandlerAdapter

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
未来也可以手动配置Handler

  • 具体是怎么着到某一个Adapter的呢
    在这里插入图片描述
    在这里插入图片描述
    0 - 支持方法上标注@RequestMapping ;1 - 支持函数式编程的。也就是你可以自定义controller,这个controller可以添加为RequestMapping注解和函数式的方法,现在就找到第0个。

(3)执行目标方法

经过上面找到某一个Handler之后,就会执行真正的目标方法
在这里插入图片描述

// Actually invoke the handler.指定目标方法
//DispatcherServlet -- doDispatch
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  • 进入ha.handle方法【要打断点进入噢】
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

(4)这里就可以看到了支持的参数解析器,有26个

在这里插入图片描述
这你就是你controller下能写的参数,就取决于这个参数解析器。
在这里插入图片描述
看它参数解析器,先要判断当前解析器是否支持当前参数,支持就调用解析方法,不支持就报错。【它玩不起】

(5)这里也看到了返回值处理器

在这里插入图片描述

  • 真正的处理应该是这里
    在这里插入图片描述在这里插入图片描述

(7)获取到所有的参数的点

在这里插入图片描述
在这里插入图片描述

(8)到底是如何获取参数值的呢

在这里插入图片描述

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		//获取到所有的参数类型【就是那个26个】
		MethodParameter[] parameters = getMethodParameters();
		//是否有参数
		if (ObjectUtils.isEmpty(parameters)) {
			return EMPTY_ARGS;
		}

		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) {
				continue;
			}
			//判断当前解析器是否支持这种参数类型,它会去挨个判断是否有哪个支持
			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 ex) {
				// Leave stack trace for later, exception may actually be resolved and handled...
				if (logger.isDebugEnabled()) {
					String exMsg = ex.getMessage();
					if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
						logger.debug(formatArgumentError(parameter, exMsg));
					}
				}
				throw ex;
			}
		}
		return args;
	}
  • 挨个的去判断
    在这里插入图片描述
    这里就会匹配到到具体的是哪个参数可以解析当前参数,如果都匹配不到,那就报错。找到了就要解析这个值了
  • 解析这个参数值
    在这里插入图片描述
    最后返回的就是确定了方法的参数:Object[]的数组。

3)支持的Servlet原生API

(1)Servlet API

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

他们都是由ServletRequestMethodeArgumentResolver进行解析处理的

4)复杂参数

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

(1)Model和Map原理

在这里插入图片描述

Map<String,Object> map,  Model model, HttpServletRequest request 都是可以给request域中放数据,request.getAttribute();

Map、Model类型的参数,会返回 mavContainer.getModel();—> BindingAwareModelMap 是Model 也是Map mavContainer.getModel(); 获取到值的
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(2)自己的一些理解

像这种打断点是不好理解的,只能自己打断点了试一试,跳出重点理解一下,中途具体走了哪些我也不知道,所以希望自己跟着打。
在这里插入图片描述
在这里插入图片描述
经过我们的跳过看到了这个方法进入这个方法的执行过程
在这里插入图片描述
在这里插入图片描述
经过一步步的跳转来到了这里
在这里插入图片描述
再继续点下去来到了这一步【增强for循环,这里会对比你controller的参数和已有的参数类型进行比较,找到那个参数解析器就交给哪个参数解析器处理】
在这里插入图片描述
遍历找了之后回到了这个方法【发现匹配到了具体的参数类型】
在这里插入图片描述
在这里插入图片描述
继续点就来到了这里
在这里插入图片描述
在这里插入图片描述
最后这里返回一个modelandview对象
在这里插入图片描述
最后的最后就来到了这里
在这里插入图片描述

后面的处理,就要到了后续的学习再总结。

5)自定义参数绑定原理

(1)编写请求

在这里插入图片描述

(2)打断点

  • 首先对这个两个断点放行
    在这里插入图片描述
    在这里插入图片描述

  • 同样的进入这个方法
    在这里插入图片描述

  • 进入就到了这个方法我们去看看是干嘛的
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    【原来就是选择它的是什么方法的:if下面的,如果不是支持的方法就要报错】

  • 继续点就执行这个方法了
    在这里插入图片描述
    在这里插入图片描述

  • 进入这一步看看
    在这里插入图片描述
    在这里插入图片描述

  • 执行到最后反正是肯定要返回到这【具体的业务逻辑肯定是在里面,以后再慢慢的理解】
    在这里插入图片描述

(3)具体原理

具体原理各位可以看看,我现在还没有搞懂,之后慢慢的理解。
ServletModelAttributeMethodProcessor 这个参数处理器支持,是否为简单类型。

public static boolean isSimpleValueType(Class<?> type) {
		return (Void.class != type && void.class != 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));
	}
@Override
	@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 = 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 {
			// Create attribute instance
			try {
				attribute = createAttribute(name, parameter, binderFactory, webRequest);
			}
			catch (BindException ex) {
				if (isBindExceptionRequired(parameter)) {
					// No BindingResult parameter -> fail with BindException
					throw ex;
				}
				// Otherwise, expose null/empty value and associated BindingResult
				if (parameter.getParameterType() == Optional.class) {
					attribute = Optional.empty();
				}
				bindingResult = ex.getBindingResult();
			}
		}

		if (bindingResult == null) {
			// Bean property binding and validation;
			// skipped in case of binding failure on construction.
			WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
			if (binder.getTarget() != null) {
				if (!mavContainer.isBindingDisabled(name)) {
					bindRequestParameters(binder, webRequest);
				}
				validateIfApplicable(binder, parameter);
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new BindException(binder.getBindingResult());
				}
			}
			// Value type adaptation, also covering java.util.Optional
			if (!parameter.getParameterType().isInstance(attribute)) {
				attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
			}
			bindingResult = binder.getBindingResult();
		}

		// Add resolved attribute and BindingResult at the end of the model
		Map<String, Object> bindingResultModel = bindingResult.getModel();
		mavContainer.removeAttributes(bindingResultModel);
		mavContainer.addAllAttributes(bindingResultModel);

		return attribute;
	}

WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中

GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean – Integer)
byte – > file

@FunctionalInterfacepublic interface Converter<S, T>
在这里插入图片描述
在这里插入图片描述

未来我们可以给WebDataBinder里面放自己的Converter;
private static final class StringToNumber implements Converter<String, T>

自己的Converter

//1、WebMvcConfigurer定制化SpringMVC的功能
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // 不移除;后面的内容。矩阵变量功能就可以生效
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }

            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {

                    @Override
                    public Pet convert(String source) {
                        // 啊猫,3
                        if(!StringUtils.isEmpty(source)){
                            Pet pet = new Pet();
                            String[] split = source.split(",");
                            pet.setName(split[0]);
                            pet.setAge(Integer.parseInt(split[1]));
                            return pet;
                        }
                        return null;
                    }
                });
            }
        };
    }

5、参数处理的原理

● HandlerMapping中找到能处理请求的Handler(Controller.method())
● 为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter
● 适配器执行目标方法并确定方法参数的每一个值

(1)HandlerAdapter

在这里插入图片描述
0 - 支持方法上标注@RequestMapping
1 - 支持函数式编程的

(2)执行目标方法

// Actually invoke the handler.
//DispatcherServlet -- doDispatch
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法


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

(3)参数解析器

确定将要执行的目标方法的每一个参数的值是什么;
SpringMVC目标方法能写多少种参数类型。取决于参数解析器。
在这里插入图片描述
在这里插入图片描述
● 当前解析器是否支持解析这种参数
● 支持就调用 resolveArgument

(4)返回值处理器

在这里插入图片描述

(5)如果确定目标方法每一个参数的值

============InvocableHandlerMethod==========================
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		MethodParameter[] parameters = getMethodParameters();
		if (ObjectUtils.isEmpty(parameters)) {
			return EMPTY_ARGS;
		}

		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) {
				continue;
			}
			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 ex) {
				// Leave stack trace for later, exception may actually be resolved and handled...
				if (logger.isDebugEnabled()) {
					String exMsg = ex.getMessage();
					if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
						logger.debug(formatArgumentError(parameter, exMsg));
					}
				}
				throw ex;
			}
		}
		return args;
	}
  • 挨个判断所有参数解析器那个支持解析这个参数
@Nullable
	private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
			for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
				if (resolver.supportsParameter(parameter)) {
					result = resolver;
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}
  • 解析这个参数的值:调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可
  • 自定义类型参数:ServletModelAttributeMethodProcessor 这个参数处理器支持是否为简单类型。
public static boolean isSimpleValueType(Class<?> type) {
		return (Void.class != type && void.class != 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));
	}
@Override
	@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 = 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 {
			// Create attribute instance
			try {
				attribute = createAttribute(name, parameter, binderFactory, webRequest);
			}
			catch (BindException ex) {
				if (isBindExceptionRequired(parameter)) {
					// No BindingResult parameter -> fail with BindException
					throw ex;
				}
				// Otherwise, expose null/empty value and associated BindingResult
				if (parameter.getParameterType() == Optional.class) {
					attribute = Optional.empty();
				}
				bindingResult = ex.getBindingResult();
			}
		}

		if (bindingResult == null) {
			// Bean property binding and validation;
			// skipped in case of binding failure on construction.
			WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
			if (binder.getTarget() != null) {
				if (!mavContainer.isBindingDisabled(name)) {
					bindRequestParameters(binder, webRequest);
				}
				validateIfApplicable(binder, parameter);
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new BindException(binder.getBindingResult());
				}
			}
			// Value type adaptation, also covering java.util.Optional
			if (!parameter.getParameterType().isInstance(attribute)) {
				attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
			}
			bindingResult = binder.getBindingResult();
		}

		// Add resolved attribute and BindingResult at the end of the model
		Map<String, Object> bindingResultModel = bindingResult.getModel();
		mavContainer.removeAttributes(bindingResultModel);
		mavContainer.addAllAttributes(bindingResultModel);

		return attribute;
	}

WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中

GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean – Integer)
byte – > file

@FunctionalInterfacepublic interface Converter<S, T>
在这里插入图片描述
在这里插入图片描述
未来我们可以给WebDataBinder里面放自己的Converter;
private static final class StringToNumber implements Converter<String, T>

自定义 Converter

//1、WebMvcConfigurer定制化SpringMVC的功能
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // 不移除;后面的内容。矩阵变量功能就可以生效
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }

            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {

                    @Override
                    public Pet convert(String source) {
                        // 啊猫,3
                        if(!StringUtils.isEmpty(source)){
                            Pet pet = new Pet();
                            String[] split = source.split(",");
                            pet.setName(split[0]);
                            pet.setAge(Integer.parseInt(split[1]));
                            return pet;
                        }
                        return null;
                    }
                });
            }
        };
    }

(6)目标方法执行完成

将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View。还包含Model数据。

在这里插入图片描述

(7)处理派发结果

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);

InternalResourceView:
@Override
	protected void renderMergedOutputModel(
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

		// Expose the model object as request attributes.
		exposeModelAsRequestAttributes(model, request);

		// Expose helpers as request attributes, if any.
		exposeHelpers(request);

		// Determine the path for the request dispatcher.
		String dispatcherPath = prepareForRendering(request, response);

		// Obtain a RequestDispatcher for the target resource (typically a JSP).
		RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
		if (rd == null) {
			throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
					"]: Check that the corresponding file exists within your web application archive!");
		}

		// If already included or response already committed, perform include, else forward.
		if (useInclude(request, response)) {
			response.setContentType(getContentType());
			if (logger.isDebugEnabled()) {
				logger.debug("Including [" + getUrl() + "]");
			}
			rd.include(request, response);
		}

		else {
			// Note: The forwarded resource is supposed to determine the content type itself.
			if (logger.isDebugEnabled()) {
				logger.debug("Forwarding to [" + getUrl() + "]");
			}
			rd.forward(request, response);
		}
	}
暴露模型作为请求域属性
// Expose the model object as request attributes.
		exposeModelAsRequestAttributes(model, request);
protected void exposeModelAsRequestAttributes(Map<String, Object> model,
			HttpServletRequest request) throws Exception {

    //model中的所有数据遍历挨个放在请求域中
		model.forEach((name, value) -> {
			if (value != null) {
				request.setAttribute(name, value);
			}
			else {
				request.removeAttribute(name);
			}
		});
	}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值