- 学无止境 Java工程师的进阶之旅
目录
一、静态资源配置原理
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;
}
}
三、请求映射原理
-
用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet捕获;
-
DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;
-
DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法)
-
提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息数据转换:对请求消息进行数据转换。如String转换成Integer、Double等数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中. -
Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;
-
根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet ;
-
ViewResolver 结合Model和View,来渲染视图
-
将渲染结果返回给客户端。
从DispatcherServlet
—>doDispatch()
开始
源码验证:
- 请求进来都会先经过
DispatcherServlet
,但其没有doGet() doPost()
方法
- 在父类
FrameworkServlet
找到doGet
和doPost
方法(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()
查看执行方法流程
方法参数