WEB开发
简单功能分析
静态资源访问
-
静态资源目录
类路径下/static, /public, /resources, /META-INF/resources,这些目录里的资源都可以直接通过url访问:当前项目根路径/ + 静态资源名
原理:静态映射/**
请求进来,先去controller找,看有没有对应的controller可以处理,如果没有就交给静态资源处理器,如果静态资源也找不到就404 -
静态资源访问前缀
默认无前缀
spring:
mvc:
static-path-pattern: /res/**
如果要改变前缀,就用如上的配置,此时访问静态资源的路径为:当前项目根路径/res+ 静态资源名
spring:
mvc:
static-path-pattern: /res/**
web:
resources:
static-locations: classpath:/haha/
static-locations是改变保存静态文件夹的位置,默认是/static, /public, /resources, /META-INF/resources这些文件夹下,按着上面的更改,静态文件夹变成了haha文件夹
欢迎页支持
-
静态资源路径下的index.html
在静态资源文件夹里新建一个index.html,那么在项目启动后会自动弹出页面如果更改了静态资源访问路径( static-path-pattern: /res/**),那么欢迎页会失效
-
controller里有对/index路径的映射
自定义Favicon
在静态资源文件夹下创建favicon.ico,文件名不能变,此时访问网页时在访问栏会出现自定义的图标。
跟上面一样,如果更改了静态资源访问路径也会导致失效。
静态资源配置原理
主要依赖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 {
该类WebMvcAutoConfiguration 下的核心类WebMvcAutoConfigurationAdapter 如下
与配置文件绑定:
WebMvcProperties <> “spring.mvc”
ResourceProperties <> “spring.resources”
WebProperties <> “spring.web”
@Configuration(
proxyBeanMethods = false
)
@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
这个类有一个构造器,其中的参数都是从容器中获取
ResourceProperties :获取配置文件里spring.resources的配置
WebProperties :获取配置文件里spring.web的配置
WebMvcProperties :获取配置文件里spring.mvc的配置
ListableBeanFactory:IOC容器
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.resourceProperties = (Resources)(resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources());
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
this.servletRegistrations = servletRegistrations;
this.mvcProperties.checkConfiguration();
}
重点讲这个方法
该方法是加载静态资源的核心方法
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
registration.addResourceLocations(new Resource[]{resource});
}
});
}
}
- 先获取配置文件里add-mappings的值,默认是true,如果改成false,那么静态资源加载的默认规则就不生效了
- 获取webjars下的静态资源,解释了当引入jquery后直接在根路径下输入webjars就会显示jquery的静态资源
- 获取配置文件里的static-path-pattern和static-locations的值
spring:
mvc:
static-path-pattern: /res/**
web:
resources:
static-locations: classpath:/haha/
add-mappings: true
staticLocations 的默认值是{“classpath:/META-INF/resources/”, “classpath:/resources/”, “classpath:/static/”, “classpath:/public/”}
public static class Resources {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
private String[] staticLocations;
private boolean addMappings;
private boolean customized;
private final WebProperties.Resources.Chain chain;
private final WebProperties.Resources.Cache cache;
public Resources() {
this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
this.addMappings = true;
this.customized = false;
this.chain = new WebProperties.Resources.Chain();
this.cache = new WebProperties.Resources.Cache();
}
欢迎页原理
也是在WebMvcAutoConfiguration 这个配置类下有一个EnableWebMvcConfiguration 配置类
@Configuration(
proxyBeanMethods = false
)
@EnableConfigurationProperties({WebProperties.class})
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
private final Resources resourceProperties;
private final WebMvcProperties mvcProperties;
private final WebProperties webProperties;
private final ListableBeanFactory beanFactory;
private final WebMvcRegistrations mvcRegistrations;
private ResourceLoader resourceLoader;
public EnableWebMvcConfiguration(ResourceProperties resourceProperties, WebMvcProperties mvcProperties, WebProperties webProperties, ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ListableBeanFactory beanFactory) {
this.resourceProperties = (Resources)(resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources());
this.mvcProperties = mvcProperties;
this.webProperties = webProperties;
this.mvcRegistrations = (WebMvcRegistrations)mvcRegistrationsProvider.getIfUnique();
this.beanFactory = beanFactory;
}
这个配置类下有一个welcomePageHandlerMapping方法就是配置欢迎页的核心方法
@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的构造器中写死了如果staticPathPattern不是/** ,就从controller中找,所以这也解释了当自己配置了静态资源访问路径之后,也就是staticPathPattern不为 /* *了,所以只能进入else里
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
if (welcomePage != null && "/**".equals(staticPathPattern)) {
logger.info("Adding welcome page: " + welcomePage);
this.setRootViewName("forward:index.html");
} else if (this.welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
logger.info("Adding welcome page template: index");
this.setRootViewName("index");
}
}
请求参数处理
请求映射
Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
- 以前: /getUser:获取用户 /deleteUser:删除用户 /editUser:修改用户 /saveUser:保存用户
- 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
- 核心Filter:HiddenHttpMethodFilter
要想使filter生效,需要现在配置文件中将spring.mvc.hiddenmethod.filter变成可用的
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
prefix = "spring.mvc.hiddenmethod.filter",
name = {"enabled"}
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
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);
}
因为表单提交只能提交get和post请求,如果要提交delete和put请求,需要指定表单是post请求,并且要有name为_method的参数,value是delete或put,大小写都可以
<form method="post">
<input name="_method" type="hidden" value="delete"/>
</form>
原理:
需要指定表单是post请求:
if ("POST".equals(request.getMethod())
不用区分大小写,代码里会统一转为大写
String method = paramValue.toUpperCase(Locale.ENGLISH);
value必须是delete或put时才会被识别
static {
ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
}
以上只适用于表单提交
如果是用客户端提交,比如post,直接发送delete或put请求,此时就不会转成HttpMethodRequestWrapper
也可以自定义name,默认是_method,创建完以下配置之后,name就是_m了
package com.study.springboot2.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;
@Configuration(proxyBeanMethods = false)
public class WebConfig {
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}
}
请求映射原理
所有的请求都会使用DispatcherServlet,直接在idea里使用Ctrl+n搜索该类。
该类下的核心方法
会有五种handlerMapping
- RequestMappingHandlerMapping:自定义controller的请求映射都在这个HandlerMapping里
- BeanNameUrlHandlerMapping
- RouterFunctionMapping
- SimpleUrlHandlerMapping
- WelcomePageHandlerMapping: 欢迎页的请求处理
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
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;
}