静态资源配置原理
静态资源处理规则
sssSpringBoot启动自动加载 xxxAutoConfiguration 自动配置类
sssSpringMVC功能的自动配置类大都集中在 ==》 WebMvcAutoConfiguration ,生效
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class}) // 没有 WebMvcConfigurationSupport 类生效💦
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {
... ... .... .... ... ...
}
sssWebMvcAutoConfiguration 生效给容器中配置了什么?
// WebMvcAutoConfiguration 内部类
@Configuration( proxyBeanMethods = false )
@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class}) //配置文件的相关属性和xxx进行了绑定
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
... ... ...
}
sss配置文件的相关属性和xxx进行了绑定:
ssdssWebMvcProperties ==》 spring.mvc ResourceProperties ==》 spring.resources
ssdssWebProperties ==》 spring.web
ssdssWebMvcAutoConfigurationAdapter 只有一个有参构造器,有参构造器所有参数值都会从容器中确定。如下:
/**
@ ResourceProperties :获取和spring.web绑定的所有的值的对象
@ WebMvcProperties :获取和spring.mvc绑定的所有的值的对象
@ ListableBeanFactory :Spring的beanFactory
@ HttpMessageConverters :找到所有的HttpMessageConverters
@ ResourceHandlerRegistrationCustomizer :找到资源处理器的自定义器
@ DispatcherServletPath
@ ServletRegistrationBean 给应用注册Servlet、Filter....
*/
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
this.servletRegistrations = servletRegistrations;
}
sss资源处理的默认规则:
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// addMappings默认为true,即允许静态资源💦
//并且因为调用resourceProperties属性,点进去发现是属于ResourceProperties类,相对应于容器的组件,看注1💦
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
//设置缓存的时间
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
// 设置 webjars 中静态资源访问规则💦
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));
}
// 设置静态资源的路径 this.resourceProperties.getStaticLocations() 获得,默认为 "/**",可以修改💦
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));
}
}
}
sss【注1】:
public class ResourceProperties {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/"};
private String[] staticLocations;
public ResourceProperties() {
this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
this.addMappings = true;
this.chain = new ResourceProperties.Chain();
this.cache = new ResourceProperties.Cache();
}
public String[] getStaticLocations() { return this.staticLocations;}
spring:
resources:
# 禁用所有静态资源
add-mappings: false
# 设置缓存
cache: period: 11000
# 修改静态资源路径
static-locations: classpath:/stat_resources/
sss欢迎页处理规则:
//HandlerMapping:处理器映射。保存了每一个Handler能处理那些请求。
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
// 跳转欢迎页,看注【注2】💦
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;
}
sss【注2】:
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
// 要使用欢迎页必须是 “ /** ”💦
if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
logger.info("Adding welcome page: " + welcomePage.get());
this.setRootViewName("forward:index.html");
} else if (this.welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
// 静态资源路径不是默认配置,就调用 Controller 处理 /index 请求💦
logger.info("Adding welcome page template: index");
this.setRootViewName("index");
}
}
sss【注3】:•favicon :浏览器默认会发请求访问 /favicon.ico当静态资源请求规则是 /** 时,可以访问。如果自己指定了静态资源请求路径则不能访问。
请求参数处理
请求映射
sssrest使用:
ssdssRest风格支持(使用HTTP请求方式动词表示对资源的操作)
ssdss以前: ○ 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
sdssdsdsd s ○ 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
sdssdsdsd s ○ 核心Filter :HiddenHttpMethodFilter
ssdsdsddsdsdss ■ 用法: 表单method=post,隐藏域 _method=put
ssdsdsddsdsdss ■ SpringBoot中手动开启
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启页面表单的Rest功能
@RestController
public class HelloController {
@RequestMapping("/bug.jpg")
public String hello(){
return "aaaa";
}
// @RequestMapping(value = "/user",method = RequestMethod.GET)
@GetMapping("/user")
public String getUser(){
return "GET-张三";
}
// @RequestMapping(value = "/user",method = RequestMethod.POST)
@PostMapping("/user")
public String saveUser(){
return "POST-张三";
}
// @RequestMapping(value = "/user",method = RequestMethod.PUT)
@PutMapping("/user")
public String putUser(){
return "PUT-张三";
}
@DeleteMapping("/user")
// @RequestMapping(value = "/user",method = RequestMethod.DELETE)
public String deleteUser(){
return "DELETE-张三";
}
}
sss rest 原理 (表单提交要使用REST的时候):
ssdss前提 :表单提交会带上_method=PUT (这里应该是_method=希望使用的提交方法)
ssdss1、请求过来被HiddenHttpMethodFilter拦截 (源码) :
ssdssHiddenHttpMehodFilter的核心方法:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//原生的servlet请求:HttpServletRequest request💦
HttpServletRequest requestToUse = request;
// 2、请求是否是POST请求并且正常💦
if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
// 4、获取到_method的值,默认methodParam = "_method" 💦
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
//或者methodParam的前缀,并将其转换成大写,比如 delete_method,所以会获取到 DELETE
String method = paramValue.toUpperCase(Locale.ENGLISH);
//ALLOWED_METHODS == PUT、DELETE、PATCH💦
if (ALLOWED_METHODS.contains(method)) {
//就是一种包装,通过调用HiddenHttpMethodFilter的内部类HttpMethodRequestWrapper的💦
//getMethod()方法返回输入的method而不是post💦
requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}
sdssd s 2、 请求是否正常,并且是POST
ssdsdsddsdsdss 3、 用法: 表单method=post,隐藏域 _method=put
ssdsdsddsdsdss 4、 获取到_method的值(无论value是大写还是小写,底层都会统一转换为大写)
ssdsdsddsdsdss 5、 兼容以下请求 :PUT.DELETE. PATCH
ssdsdsddsdsdss 6、原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值
ssdsdsddsdsdss 7、过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
sss【注 5】:通过源码可知,methodParam = “_method”; 但是可以通过setMethodParam(String methodParam)方法改变methodParam值,因此这时候 我们可以通过把_method 换成我们自己自定义得值,比如_m:
@Configuration(proxyBeanMethods = false)
public class config {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
hiddenHttpMethodFilter.setMethodParam("_m");
return hiddenHttpMethodFilter;
}
}
sss【注 6】:ALLOWED_METHODS值的设置方式,通过静态块赋值:
sss【注 7】:requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
源码:
sss【注 8】:Rest使用客户端工具,如PostMan直接发送Put、delete等方式请求,无需Filter。因为:
sss【注 9】:
@ DeleteMapping实质就是 @RequestMapping ( method = { RequestMethod.DELETE} )
@ PutMapping实质就是 @RequestMapping ( method = { RequestMethod.PUT} )
@ PostMapping实质就是 @RequestMapping ( method = { RequestMethod.POST} )
@ GetMapping实质就是 @RequestMapping ( method = { RequestMethod.GET} )
请求映射原理
dasdasdas
sssDispatcherServlet调用FrameworkServlet中的doGet和doPost
sssdoGet和doPost ==> processRequest(request, response) ==> doService(request, response)
sssdoService是一个抽象方法,并未做具体实现
sss FrameworkServlet的子类DispatcherServlet实现了doService
sssDispatcherServlet的doService ==> doDispatch(request, response)
sss请求映射流程:
sdsdsss请求进来 ==> HttpServlet的doGet和doPost
sdsdsdsdsddsdsdsss==> FrameworkServlet中的doGet和doPost
sdsdsddsdsdsddsdsdsss==> DispatcherServlet的doService
sdsdsdsddsdsdsdsdsdsdsss==> doService中调用doDispatch(request, response)
sss[注10]: SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet-》doDispatch()
sss找到当前请求使用哪个Handler : mappeHandler = this.gethHandler(processedRequest); 😒
dasdas
ssshandlerMappings:处理器映射,有五个值
sss[注11]: 第一各处理器映射:RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则
所有的请求映射都在HandlerMapping中。
sss1. SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
sss2. SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
sss3. 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息(在request)。(同样的请求信息只能有一个)
sdsss• 如果有就找到这个请求对应的handler
ssdss• 如果没有就是下一个 HandlerMapping
sss4. 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping (映射每一个请求到对应的Handler)
sss[注12]: getHandler就是我们寻找相应处理器方法的方法:
sss[注13]: 点击DispatcherServlet的getHandler方法,进入到AbstractHandlerMapping的getHandler方法,
sss[注14]: 再进入getHandlerInternal(request):
sss[注15]: 再进入getHandlerInternal(request)方法:就是核心部分:
sss[注16]: 通过lookupHandlerMethod(lookupPath, request)获取相应的处理方法: