SpringBoot2-核心技术:Web开发



一、静态资源配置原理

SpringBoot启动默认加载 xxxAutoConfiguration 类

SpringMVC功能的自动配置类 WebMvcAutoConfiguration

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

1、给容器中配置了WebMvcAutoConfigurationAdapter

配置文件的相关属性和WebMvcProperties.class==spring.mvc, ResourceProperties.class==spring.resources绑定

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

拓展:配置类只有一个有参构造器,有参构造器所有参数的值都从容器中取

//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) {
	// 1、如果(addMappings = false) - 禁用默认资源处理
	if (!this.resourceProperties.isAddMappings()) {
		logger.debug("Default resource handling disabled");
		return;
	}
	Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
	CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
	if (!registry.hasMappingForPattern("/webjars/**")) {
		customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
				.addResourceLocations("classpath:/META-INF/resources/webjars/")
				.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
	}
	String staticPathPattern = this.mvcProperties.getStaticPathPattern();
	if (!registry.hasMappingForPattern(staticPathPattern)) {
		customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
				.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
				.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
	}
}

1、访问/webjars/jquery就能获得maven的jquery资源

如果ResourceHandlerRegistry没有模式映射hasMappingForPattern就按默认自定义资源处理器注册
/webjars/请求映射到classpath:/META-INF/resources/webjars/

if (!registry.hasMappingForPattern("/webjars/**")) {
	//自定义资源处理程序注册器注册资源处理程序
	customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
			//添加资源位置
			.addResourceLocations("classpath:/META-INF/resources/webjars/")
			//设置缓存时间
			.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}

2、只要将静态资源放在类目录:/static /public /resources /META-INF/resources就能找到静态资源

如果ResourceHandlerRegistry没有模式映射hasMappingForPattern就按默认自定义资源处理器注册
/**映射到/static /public /resources /META-INF/resources

private String staticPathPattern = "/**";
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
	//将/**的请求映射到下图位置
	customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
			//默认位置为下图
			.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
			.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}

在这里插入图片描述

3、欢迎页的处理规则

@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) {
	//如果欢迎页存在且路径为/**就转发到index.html
	if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
		logger.info("Adding welcome page: " + welcomePage.get());
		setRootViewName("forward:index.html");
	}
	//否则调用controller /index
	else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
		logger.info("Adding welcome page template: index");
		setRootViewName("index");
	}
}


二、REST映射及源码解析

Rest风格支持

  • 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /deleteUser 删除用户
  • 现在:/user GET-获取 DELETE-删除 PUT-修改 POST保存

核心Filter:hiddenHttpMethodFilter
用法:表单method=post,隐藏域_method=put/delete

<input name="_method" type="hidden" value="DELETE/PUT">

springboot需要手动开启

@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)

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

  • 表单提交会带上_method=PUT
  • 请求过来会被hiddenHttpMethodFilter拦截
    1、检测请求是否正常并且是POST
    2、获取到_method的值
    3、兼容以下请求:PUT DELETE PATCH
    4、元素request(post),包装模式requestWrapper重写getMethod()方法
    5、过滤器放行的时候用wrapper,以后的方法调用getMethod()方法是调用requestWrapper的。

可以直接使用PostMan等工具发送请求就无需配置

源码:

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

继承hiddenHttpMethodFilter

public class OrderedHiddenHttpMethodFilter extends HiddenHttpMethodFilter implements OrderedFilter {}
public class HiddenHttpMethodFilter extends OncePerRequestFilter {

	private static final List<String> ALLOWED_METHODS =
			Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
					HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));

	/** Default method parameter: {@code _method}. */
	public static final String DEFAULT_METHOD_PARAM = "_method";

	private String methodParam = DEFAULT_METHOD_PARAM;


	/**
	 * Set the parameter name to look for HTTP methods.
	 * @see #DEFAULT_METHOD_PARAM
	 */
	public void setMethodParam(String methodParam) {
		Assert.hasText(methodParam, "'methodParam' must not be empty");
		this.methodParam = methodParam;
	}

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		HttpServletRequest requestToUse = request;

		if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
			//获取请求参数_method的值
			String paramValue = request.getParameter(this.methodParam);
			if (StringUtils.hasLength(paramValue)) {
				String method = paramValue.toUpperCase(Locale.ENGLISH);
				if (ALLOWED_METHODS.contains(method)) {
					//包装类处理method
					requestToUse = new HttpMethodRequestWrapper(request, method);
				}
			}
		}
		
		//放行包装类
		filterChain.doFilter(requestToUse, response);
	}


	/**
	 * Simple {@link HttpServletRequest} wrapper that returns the supplied method for
	 * {@link HttpServletRequest#getMethod()}.
	 */
	private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {

		private final String method;

		public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
			super(request);
			this.method = method;
		}
		
		//重写了httpServletRequest的getMethod()方法将传入的method返回
		@Override
		public String getMethod() {
			return this.method;
		}
	}

}

拓展:自定义_method为其他名称

@Configuration
public class WebConfig {
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_m");
        return methodFilter;
    }
}


三、请求映射原理

在这里插入图片描述

  1. 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet捕获;

  2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;

  3. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法)

  4. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
    HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息数据转换:对请求消息进行数据转换。如String转换成Integer、Double等数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中.

  5. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;

  6. 根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet ;

  7. ViewResolver 结合Model和View,来渲染视图

  8. 将渲染结果返回给客户端。

DispatcherServlet—>doDispatch()开始

源码验证:

  • 请求进来都会先经过DispatcherServlet,但其没有doGet() doPost()方法
    在这里插入图片描述
  • 在父类FrameworkServlet找到doGetdoPost方法(Ctrl+F12展示该类的所有方法)
    在这里插入图片描述
  • 都是调用本类的processRequest()
    在这里插入图片描述
  • 主要就是调用doService()
    在这里插入图片描述
  • 发现该类是抽象类,只能到子类DispatcherServlet找实现方法
    在这里插入图片描述
  • doService()又来调用doDispatch()
    在这里插入图片描述
    最终研究的方法doDispatch()每个请求进来都要调用该方法
    在这里插入图片描述

分析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
			mappedHandler = getHandler(processedRequest);
			if (mappedHandler == null) {
				noHandlerFound(processedRequest, response);
				return;
			}
  • 分析getHandler()
    在这里插入图片描述
    在这里插入图片描述
    有5个handlerMappings
    在这里插入图片描述
    其中一个handlerMappings处理欢迎页:访问 "/" 转发到index.html
    在这里插入图片描述
    下标为0的RequestMappingHandlerMappeing保存了所有自定义请求信息
    在这里插入图片描述

总结

  • SpringBoot默认配置了欢迎页的handlerMapping,访问"/"能访问到index.html
  • SpringBoot自动配置了默认的RequestMappingHandlerMapping
  • 所有请求进来挨个尝试所有的handlerMapping是否有请求信息
  • 找到处理该请求的handler再进行后续处理


四、参数处理原理

继续分析doDsipatch()
在这里插入图片描述

1、找到HandlerAdapter

  • HandlerMapping中找到能出来请求的handler及拦截器
    在这里插入图片描述
  • 为当前Handler找一个处理器适配器HandlerAdapter找到了RequestMappingHandlerAdapter
    在这里插入图片描述
    0-支持方法标注@RequestMapping
    1-支持函数式编程
    在这里插入图片描述
  • 进行一系列判断
    在这里插入图片描述
    用户自定义的请求处理器一般都是RequestMappingHandlerMapping处理

2、执行目标方法

  • 真正调用处理程序
    在这里插入图片描述
  • 调用handleInternal()返回ModelAndView
    在这里插入图片描述
  • 调用invokeHandlerMethod()执行目标方法
    在这里插入图片描述

1.参数解析器

确定将要执行的目标方法的每一个参数的值是什么
SpringMvc目标方法能写多少参数类型取决于参数解析器
在这里插入图片描述
在这里插入图片描述
参数解析器工作流程
判断当前解析器是否支持解析哪种参数
支持就调用resolveArgument()
在这里插入图片描述

2. 返回值处理器

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

3. 将这两个解析器放到invocableMethod

在这里插入图片描述

在这里插入图片描述
完成上述配置后真正执行目标方法
在这里插入图片描述

4. 执行完invokeForRequest()后真正执行目标方法

在这里插入图片描述
执行真正的方法
在这里插入图片描述
进入invokeForRequest()查看执行方法流程
在这里插入图片描述
方法参数
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Laptoy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值