SpringBoot2核心技术-核心功能【05、Web开发】

news2025/2/27 7:03:52

f9676610780b25a2bbe4477df668b119.png

目录

1、SpringMVC自动配置概览 

2、简单功能分析

2.1、静态资源访问

1、静态资源目录

2、静态资源访问前缀

2.2、欢迎页支持

2.3、自定义 Favicon

2.4、静态资源配置原理

3、请求参数处理

0、请求映射

1、rest使用与原理

2、请求映射原理

1、普通参数与基本注解

1.1、注解:

1.2、Servlet API:

1.3、复杂参数:

1.4、自定义对象参数:

2、POJO封装过程

3、参数处理原理

3.1、HandlerAdapter(处理器映射器)

3.2、执行目标方法

 3.3、参数解析器-HandlerMethodArgumentResolver

3.4、返回值处理器

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

3.5.1、循环所有参数解析器,看哪个支持解析这个参数

3.5.2、解析这个参数的值

3.5.3、解析自定义类型参数-封装POJO

3.6、目标方法执行完成

4、数据响应与内容协商

1、响应JSON

原理解析

1.2、SpringMVC到底支持哪些返回值

1.3、HTTPMessageConverter原理

2、内容协商


1、SpringMVC自动配置概览 

Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多场景我们都无需自定义配置)

The auto-configuration adds the following features on top of Spring’s defaults:

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
    • 内容协商视图解析器和BeanName视图解析器
  • Support for serving static resources, including support for WebJars (covered later in this document)).
    • 静态资源(包括webjars)
  • Automatic registration of Converter, GenericConverter, and Formatter beans.
    • 自动注册 Converter,GenericConverter,Formatter
  • Support for HttpMessageConverters (covered later in this document).
    • 支持 HttpMessageConverters (后来我们配合内容协商理解原理)
  • Automatic registration of MessageCodesResolver (covered later in this document).
    • 自动注册 MessageCodesResolver (国际化用)
  • Static index.html support.
    • 静态index.html 页支持
  • Custom Favicon support (covered later in this document).
    • 自定义 Favicon
  • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
    • 自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)

If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.

不用@EnableWebMvc注解。使用 @Configuration + WebMvcConfigurer 自定义规则

If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.

声明 WebMvcRegistrations 改变默认底层组件

If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.

使用 @EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC

2、简单功能分析

2.1、静态资源访问

1、静态资源目录

只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources

访问:当前项目根目录/ + 静态资源名

原理:静态映射/**

接收到请求,先到dispatcherServlet转发找对应的Controller,若找不到对应的Controller就交给静态资源处理器。若静态资源处理器还找不到,就返回404响应页面。

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

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

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

2、静态资源访问前缀

为了方便让拦截器直接放行静态资源的访问,可以给静态资源的访问配置一个统一的前缀。

默认是无前缀的。

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

当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找

示例:

原本请求路径:http://localhost:8080/res/xq.png

加了访问前缀后:http://localhost:8080/res/xq.png

2.2、欢迎页支持

可以有两种方式:静态资源、动态请求处理。

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

  resources:
    static-locations: [ classpath:/haha/ ]
  • 编写controller处理 /index 请求

2.3、自定义 Favicon

访问图标。favicon.ico 放在静态资源目录下即可。

2.4、静态资源配置原理

  • 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})
public class WebMvcAutoConfiguration {..
  • 查看自动配置类 给Spring容器中注册了哪些东西
@Configuration(proxyBeanMethods = false)
@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
  • 配置文件的相关属性和xxx进行了绑定。
    • WebMvcProperties==spring.mvc
    • ResourceProperties==spring.resources

1、配置类只有一个有参构造器

有参构造器的所有参数的值都会从容器中确定。

//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;
		}

2、资源处理的默认规则

public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
    } else {
        Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
        CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
        if (!registry.hasMappingForPattern("/webjars/**")) {
            this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
        }

        String staticPathPattern = this.mvcProperties.getStaticPathPattern();
        if (!registry.hasMappingForPattern(staticPathPattern)) {
            this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
        }

    }
}

可以发现使用配置可以禁用静态资源规则。

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

  resources:
    add-mappings: false   禁用所有静态资源规则
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {

	private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
			"classpath:/resources/", "classpath:/static/", "classpath:/public/" };

	/**
	 * Locations of static resources. Defaults to classpath:[/META-INF/resources/,
	 * /resources/, /static/, /public/].
	 */
	private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

3、欢迎页的处理规则

    HandlerMapping:处理器映射器。保存了每一个Handler能处理哪些请求。    

@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, 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");
    }
}

4、favicon

浏览器会发送 /favicon 来获取图标,整个session期间不再获取。

这就解释了,在配置了静态资源前缀下,浏览器获取不到的ico的原因。

3、请求参数处理

0、请求映射

1、rest使用与原理

  • @xxxMapping;
  • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作
    • 以前:/getUser   获取用户  /deleteUser 删除用户   /editUser  修改用户   /saveUser 保存用户
    • 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
  • 核心Filter;HiddenHttpMethodFilter
    • 表单method=post,隐藏域 _method=put

@RestController
public class HelloController {
    @RequestMapping("/bug.jpg")
    public String hello() {
        return "hello";
    }
    @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({FormContentFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.mvc.formcontent.filter",
        name = {"enabled"},
        matchIfMissing = true
    )
    public OrderedFormContentFilter formContentFilter() {
        return new OrderedFormContentFilter();
    }
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        HttpServletRequest requestToUse = request;
        if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
            String paramValue = request.getParameter(this.methodParam);
            if (StringUtils.hasLength(paramValue)) {
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                if (ALLOWED_METHODS.contains(method)) {
                    requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
                }
            }
        }

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

Rest原理(表单提交要使用Rest的时候)

  • 表单提交带上 _method=PUT
  • 请求过来被HiddenHttpMethodFilter 拦截
    • 请求是否正常,并且是POST请求
      • 获取到_method 的值,并转成大写英文。
      • 兼容以下请求:PUT、DELETE、PATCH
      • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
      • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true

Rest使用客户端工具

  • 如PostMan直接发送Put、delete等方式请求,无需Filter。

2、请求映射原理

SpringMVC功能分析都从 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的映射规则。

所有的请求映射都在HandlerMapping(处理器映射器)中。

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

	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

1、普通参数与基本注解

1.1、注解:

  • @PathVariable(路径变量)
  • @RequestHeader(获取请求头)
  • @ModelAttribute
  • @RequestParam(获取请求参数)
  • @MatrixVariable(矩阵变量)
  • @CookieValue(获取cookie值)
  • @RequestBody(获取请求体)

1.2、Servlet API:

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

 ServletRequestMethodArgumentResolver (方法参数解析器)来解析以上部的

@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);
    }

1.3、复杂参数:

  • MapModel(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.getMode() 返回一个 BindingAwareModelMap,而它是Model,也是Map。

 

1.4、自定义对象参数:

可以自动类型转换与格式化,可以级联封装。

/**
 *     姓名: <input name="userName"/> <br/>
 *     年龄: <input name="age"/> <br/>
 *     生日: <input name="birth"/> <br/>
 *     宠物姓名:<input name="pet.name"/><br/>
 *     宠物年龄:<input name="pet.age"/>
 */
@Data
public class Person {
    
    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;
    
}
==================================================
@Data
public class Pet {

    private String name;
    private String age;

}
@PostMapping("/saveuser")
public Person saveUser(Person person) {
    return person;
}

2、POJO封装过程

  • 自定义类型的参数解析器为:
    • ServletModelAttributeMethodProcessor

3、参数处理原理

  • HandlerMapping(处理器映射器)中找到能处理请求的Handler(Controller.method()),并返回处理器执行链。
  • 为当前Handler找到一个HandlerAdapter(处理器适配器);一般被@RequestMapping 标记的方法找到的都是 RequestMappingHandlerAdapter
  • 适配器执行目标方法,并确定方法参数的每一个值。

3.1、HandlerAdapter(处理器映射器)

0 - 支持方法上标注@RequestMapping

1 - 支持函数式编程的

xxxxxx

3.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.3、参数解析器-HandlerMethodArgumentResolver

确定将要执行的目标方法的每一个参数的值是什么;

SpringMVC目标方法能写多少种参数类型。取决于参数解析器。

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

3.4、返回值处理器

3.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;
	}

3.5.1、循环所有参数解析器,看哪个支持解析这个参数

	@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;
	}

3.5.2、解析这个参数的值

调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可。

3.5.3、解析自定义类型参数-封装POJO

1、找到参数解析器。

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));
	}

 2、WebDataBinder (Web数据绑定器)利用它里面的Converters(转换器)将请求数据转换成指定的数据类型。

核心源码: 

@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;
	}

GenericConversionService:在设置每一个值的时候,遍历它里面所有的所有converter(124个),直到找到可以将当前参数的数据类型(request带来参数的字符串)转换到指定的类型(JavaBean -- Integer)。

3、WebDataBinder 将转换后的数据再次封装到指定的JavaBean中。

3.6、目标方法执行完成

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

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);
			}
		});
	}


4、数据响应与内容协商

数据响应分为响应页面和响应数据。

  • 响应页面:如何发送一个请求,跳转到指定页面。一般用于一个单体请求。后续在视图解析器详细说明。
  • 响应数据:通常用来开发前后端分离的项目。前端发送请求给后端,后端响应数据、图片、xls、自定义协议数据等。

1、响应JSON

1.1、jackson.jar+@ResponseBody

引入web场景
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

而web场景自动引入了json场景
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-json</artifactId>
      <version>2.3.4.RELEASE</version>
      <scope>compile</scope>
    </dependency>

 主要引入的依赖为:

测试代码与结果

@Controller
public class ResponseTestController {

    @ResponseBody
    @GetMapping("/test/person")
    public Person getPerson() {
        Person person = new Person();
        person.setAge(28);
        person.setBirth(new Date());
        person.setUserName("张三");
        return person;

    }
}

原理解析

1、返回值解析器

核心代码: this.returnValueHandlers.handleReturnValue

try {
    this.returnValueHandlers.handleReturnValue(
    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

		HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
		if (handler == null) {
			throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
		}
		handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}
RequestResponseBodyMethodProcessor  	
@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		mavContainer.setRequestHandled(true);
		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

		// Try even with null return value. ResponseBodyAdvice could get involved.
        // 使用消息转换器进行写出操作
		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
	}

2、返回值解析器原理 

  • 1、返回值处理器判断是否支持这种类型返回值 supportsReturnType;
  • 2、若支持,该返回值处理器调用 handleReturnValue 进行处理。
  • 若标记了@ResponseBody注解,则调用处理器为RequestResponseBodyMethodProcessor
    • 利用 MessageConverters 进行处理 将数据写为json,具体过程:

      • 1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)

      • 2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据;

      • 3、决定内容类型后,SpringMVC会遍历所有容器底层的 HttpMessageConverter(消息转换器)看谁能处理

        • 1、得到MappingJackson2HttpMessageConverter可以将对象写为json

        • 2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。

1.2、SpringMVC到底支持哪些返回值

ModelAndView
Model
View
ResponseEntity 
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;

1.3、HTTPMessageConverter原理

1、MessageConverter 接口规范

HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。

例子:Person对象转为JSON。或者 JSON转为Person

2、默认的MessageConverter

0 - 只支持Byte类型的

1 - 只支持String

2 - 只支持String

3 - 只支持Resource

4 - 只支持ResourceRegion

5 - 只支持DOMSource.class \ SAXSource.class) \ StAXSource.class \StreamSource.class \Source.class

6 - 只支持MultiValueMap

7 - 直接返回true

8 - 直接返回true

9 - 支持注解方式xml处理的。

最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)

2、内容协商

根据客户端接收能力不同,返回不同媒体类型的数据。

假设现在有三种客户端,接收的数据类型不同:

  • 浏览器:能接收所有类型内容
  • 安卓客户端:只能接收json
  • 另一种客户端:只能接收xml

1、引入xml依赖

 <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

2、postman分别测试返回json和xml

只需要改变请求头中Accept字段。

Http协议中规定的,告诉服务器本客户端可以接收的数据类型。

发现服务代码没改动情况下,具备同时返回xml跟json两种格式的能力。

3、开启浏览器参数方式内容协商功能

4、内容协商原理

  • 1、判断当前响应头中是否已经有确定的媒体类型。MediaType
  • 2、获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)【application/xml】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/361452.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PrivateLoader PPI服务发现RisePro恶意软件窃取分发信息

称为PrivateLoader的按安装付费&#xff08;PPI&#xff09;软件下载器服务正用于恶意软件RisePro的信息窃取。Flashpoint 于 2022 年 12月13日发现了新的窃取者&#xff0c;此前发现了在名为Russian Market的非法网络犯罪市场上使用该恶意软件泄露的“几组日志”。RisePro是一…

因“AI”而“深” 第四届OpenI/O 启智开发者大会高校开源专场25日开启!

中国算力网资源不断开发&#xff0c;开源社区治理及AI开源生态引来众多有才之士参与建设&#xff0c;国家级开放创新应用平台、NLP大模型等高新技术内容逐渐走向科研舞台上聚光灯的中心&#xff0c;新时代的大门缓缓打开。在启智社区&#xff0c;有一群人&#xff0c;他们年纪轻…

BEV感知:DETR3D

3D检测&#xff1a;DETR3D前言MethodImage Feature Extracting2D-to-3D Feature TransformationLoss实验结果前言 在这篇paper&#xff0c;作者提出了一个更优雅的2D与3D之间转换的算法在自动驾驶领域&#xff0c;它不依赖于深度信息的预测&#xff0c;这个框架被称之为DETR3D…

性能测试学习和性能瓶颈分析路线

很多企业招聘都只写性能测试&#xff0c;会使用LR&#xff0c;jmeter工具。其实会使用jmeter和LR进行性能测试还只是性能测试的第一步&#xff0c;离真正的性能测试工程师还很远&#xff0c;笔者也还在路上 .。 性能测试&#xff0c;都是要求测试系统性能&#xff0c;系统自然…

面试中经常被问到的【宏定义】,改变你对【C\C++】中宏定义的认识。

最近遇到挺多宏定义的代码&#xff0c;其实挺烦的&#xff0c;每次看复杂的宏定义看到一半就懵了&#xff0c;今天盘一盘它。本篇设计宏定义的原理、使用方法、使用技巧。 目录 一、宏定义原理 二、宏定义定义复杂功能函数 2.1 定义注册函数 三、宏定义实现条件编译 四、宏…

【OpenCV学习笔记01】- 初步使用OpenCV实现人脸识别

想要使用opencv实现人脸识别&#xff0c;我们需要做这样几步&#xff1a; 1.opencv-python的安装 这里我们使用的python的opencv-python库&#xff0c;在安装opencv-python库之前&#xff0c;我们需要安装numpy, matplotlib。 # 安装指令 # 安装 numpy pip install numpy # …

Chirp-Z变换(线性调频Z变换)原理

Chirp-Z变换&#xff08;Chirp-Z Transform&#xff0c;CZT&#xff09; 采用FFT算法可以很快地计算出全部DFT值&#xff0c;即Z变换在单位圆上的全部等间隔采样值。 在实际情况中&#xff0c;并不需要对整个单位圆的频谱进行分析&#xff0c;例如&#xff0c;对于窄带信号&am…

运动型蓝牙耳机推荐哪款、最新运动蓝牙耳机推荐

提起运动耳机&#xff0c;如今很多运动爱好者和职业教练员们&#xff0c;都会向萌新推荐骨传导运动耳机。骨传导耳机解决了入耳式蓝牙耳机掉落的问题&#xff0c;佩戴相当舒服。骨传导耳机在佩戴过程中解放了双耳&#xff0c;不会因为耳机堵住耳朵&#xff0c;听不到环境音&…

【Spring6】| Spring启示录、Spring概述

目录 一&#xff1a;Spring启示录 1. OCP开闭原则 2. 依赖倒置原则DIP 3. 控制反转IoC 二&#xff1a;Spring概述 1. Spring简介 2. Spring8大模块 3. Spring特点 一&#xff1a;Spring启示录 引言&#xff1a;前面我们已经学习了三层架构&#xff1a;表示层、业务层、…

【工作笔记】syslog,kern.log大量写入invalid cookie错误信息问题

任务描述 错误出现出现过四五次&#xff0c;应该是诊断单元tf卡读写出问题导致下面这条告警一直高频写入到/var/log/下的syslog、kern.log、messages中 Nov 23 06:25:12 embest kernel: omap_hsmmc 48060000.mmc: [omap_hsmmc_pre_dma_transfer] invalid cookie: data->hos…

将maven项目打包成可执行的jar(加入外部依赖)

在有些场景下我们需要将编写的Java工程编译打包成为一个完整的jar包&#xff0c;如果你的项目是使用maven构建的话可以通过以下方法来完成这个打包的过程。添加maven打包插件。在项目的pom.xml配置文件的build标签中添加以下代码&#xff0c;其中 mainClass 属性需要替换成你项…

多种调度模式下的光储电站经济性最优储能容量配置分析(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

安装MQTT Server遇到报错“cannot verify mosquitto.org‘s certificate”,该如何解决?

MQTT是基于发布/订阅的轻量级即时通讯协议&#xff0c;很适合用于低带宽、不稳定的网络中进行远程传感器和控制设备通讯等操作中。在我们的软件研发中&#xff0c;也经常使用MQTT协议进行消息通信等。今天来和大家分享一些关于在安装MQTT Server中遇到的疑难问题及解决思路。当…

为什么阳康后,感觉自己变傻了?

不少人在阳康后出现脑力下降的情况&#xff0c;好像脑子里被雾笼罩。脑雾并不是新名词&#xff0c;已经存在了十几年。以前慢性疲劳综合征患者和脑震荡患者会用它来形容自己的症状。脑雾其实是认知障碍&#xff0c;它可由多种原因引起。比如过度劳累、长期酗酒、缺乏睡眠、久坐…

Semi-supervised(半监督)布料缺陷检测实战

数据及源码链接见文末 1.任务目标和流程概述 对于常规的缺陷检测,常常需要我们准备好数据,使用分割或者检测的方法选择模型,进行训练。但是有一个问题。在日常生产中,我们接触到的往往都是正常的,缺陷数据往往很难收集,更何况我们还要打标签。我们能不能通过训练正常数据…

独立搭建 handle server

本节主要介绍,如何搭建一个与 GHR隔离的 handle sever,不与外界有任何连通。 下载文件 访问地址下载最新版:http://www.handle.net/download_hnr.html 这里以 9.3.0 版本作为讲解 解压服务端,解压客户端 # 解压 tar -xzvf handle-9.3.0-distribution.tar.gz# 到目录下 …

NestJS学习:图片上传、下载

参考 大神的这两篇文章讲的很详细&#xff0c;这里自己也来试一下 小满nestjs&#xff08;第十三章 nestjs 上传图片-静态目录&#xff09; 小满nestjs&#xff08;第十四章 nestjs 下载图片&#xff09; 上传图片 安装包 需要&#xff1a;multer 和 nestjs/platform-expre…

深入理解Java的Lambda原理

1、前言 Lambda函数的概念其实有很久远的历史了&#xff0c;在Lisa&#xff0c;C#中早有实现。且近年来&#xff0c;开发者对语言的表现力有了更高的要求&#xff0c;Java也在JDK 1.8 中引入了Lambda函数这一概念。虽然截止到写下这段文字的一刻已经过去七年之久&#xff0c;但…

Mybatis框架详解(全)

目录 MyBatis简介 MyBatis整体架构及运行流程 1.数据源配置文件 2.Sql映射文件 3.会话工厂与会话 4.运行流程 mybatis实现增删改查 Mybatis的获取参数的方式 mapper中自定义映射 mybatis注解开发 mybatis缓存 mybatis分页插件 MyBatis简介 MyBatis 是一款优秀的持久…

程序员如何向架构师转型?看完就明白该怎么做了

软件行业技术开发从业人员众多&#xff0c;但具备若干年开发经验的普通的开发人员往往面临个人发展的瓶颈&#xff0c;即如何从普通开发人员转型成高层次的系统架构师和技术管理人员。想成为一名架构师&#xff0c;应当具备全面的知识体系&#xff0c;需要进行系统的学习和实践…