1. 静态资源访问
- 静态资源目录
上图为部分源码
只要静态资源放在类路径下:/static,/public,/resources,/META-INF/resources
访问:当前项目根路径/ + 静态资源名
http://127.0.0.1:8080/photo.png
原理:静态映射 /**
请求进来,先去找Controller映射,如若不能处理则交给静态处理器处理,静态资源也找不到则报404。
- 静态资源访问前缀
配置之后此后访问静态资源需要加一个前缀,默认无前缀。
使用上述配置后,访问静态资源路径为
http://127.0.0.1:8080/res/photo.png
- 添加默认访问路径
2. 静态资源配置原理
- 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 {
}
- 给容器中配了什么
@Configuration(
proxyBeanMethods = false
)
@Import({
WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({
WebMvcProperties.class, ResourceProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
}
WebMvcProperties.class == 配置文件中前缀为spring.mvc的属性
ResourceProperties.class == 配置文件中前缀为spring.resources的属性
3. 请求参数处理
- @xxxMapping
- Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
- 以前: /getUser 获取用户 /deleteUser 删除用户
- 现在:/user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
- 核心Filter:HiddenHttpMethodFilter
- 用法:表单method=post,隐藏于_method=put
- SpringBoot中手动开启,在 *.yaml 配置文件中开启
<form action="/user" method="post">
<input name="_method" type="hidden" value="PUT"/>
<input type="submit" value="REST-PUT 提交"/>
</form>
//源码
@Bean
@ConditionalOnMissingBean({
HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
prefix = "spring.mvc.hiddenmethod.filter",
name = {
"enabled"},
matchIfMissing = false
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
- 表单提交的REST风格原理
- 表单提交会带上_method=PUT
- 请求过来会被HiddenHttpMethodFilter拦截
a. 判断请求是否正常,并且是POST请求
b. 取到_method的值,并判断是否是要求的请求方式
c. 若符合b的条件,则使用_method的值新建一个请求(HttpMethodRequestWrapper)
d. 过滤器链放行的时候用wrapper,以后调用的getMethod()方法是wrapper的方法
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
private static final List<String> ALLOWED_METHODS;
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = "_method";
//过滤方法
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestToUse = request;
//请求正常并且为POST
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);
}
//可兼容的请求方式集合:PUT、DELETE、PATCH
static {
ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
}
//原生request,使用包装模式
//HttpServletRequestWrapper 实际实现的是HttpServletRequest接口
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
//重写getMethod()方法
public String getMethod() {
return this.method;
}
}
}
- 客户端工具发送Rest风格请求直接发即可,如PostMan
4. 请求映射原理
代码分析
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
//判断是否为上传文件的请求
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
//找到当前请求使用哪个Controller处理,通过HandlerMapping(处理器映射)寻找
mappedHandler = this.getHandler(processedRequest);
//......
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//HandlerMapping(处理器映射)什么请求对应哪个处理方法
if (this.handlerMappings != null) {
Iterator var2 = this.handlerMappings.iterator();
while(var2.hasNext()) {
HandlerMapping mapping = (HandlerMapping)var2.next();
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
- RequestMappingHandlerMapping:保存了所有@RequestMapping和handler的映射规则
- 所有请求映射都在handlerMapping中
- 请求进来挨个尝试所有的HandlerMapping
- SpringBoot默认配置了RequestMappingHandlerMapping、WelcomePageHandlerMapping
- 当我们需要自定义一些映射出历史,也可以自己向容器中放HandlerMapping