SpringBoot的Web功能
1.1 静态资源访问
- 静态资源目录(类路径)
/static` (or `/public` or `/resources` or `/META-INF/resources
访问:当前项目路径/+静态资源名
spring.mvc.static-path-pattern=/resources/**
#默认是为此配置,根路径
当静态资源和访问的API有冲突时,请求到服务器,会先去找Controller能不能处理,不能处理的请求又都交给静态资源处理器处理,如果静态资源也不能找到,则返回404
1.2 静态资源访问前缀
为了更好的区分动态资源和静态资源,可以对静态资源加上访问前缀
spring:
mvc:
static-path-pattern: /res/**
1.3 欢迎页支持
- 静态资源路径下 index.html
- 可以配置静态资源路径
- 但是不能配置静态资源的访问前缀,否则导致index.html不能被默认访问
- controller能处理/index
2.1静态资源配置原理
- 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})
-
给容器中配置什么功能
-
OrderedHiddenHttpMethodFilter
-
OrderedFormContentFilter
-
//WebMvcAutoConfigurationAdapter静态内部类 @Configuration( proxyBeanMethods = false ) @Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class}) @EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class}) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware
-
-
配置文件的相关属性和XXX进行了绑定,WebMvcPropertiesspring.mvc,WebPropertiesspring.web进行了绑定
-
一个配置类只有一个有参构造器
有参构造器所有参数的值都会从容器中确定
//WebProperties webProperties获取所有和Spring.web绑定的所有的值
// WebMvcProperties mvcProperties获取所有和Spring.Mvc所绑定的所有的值
// ListableBeanFactory beanFactory 获取容器工厂
//HttpMessageConverters 获取所有的HttpMessageConverters
// resourceHandlerRegistrationCustomizerProvider 找到资源处理器的自定义器 ======
// dispatcherServletPath 能处理的路径
// ServletRegistrationBean 给应用注册Servlet,Filter
public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.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();
}
2.2 资源处理的默认规则
//AddMappings 属性控制是否能访问静态资源
//getStaticLocations可以找到相应得静态资源路径
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});
}
});
}
}
2.3 欢迎页的处理规则
// HandlerMapping 处理器映射,保存了每一个Handlet能处理哪些请求。
//同样,参数都会从容器中拿
@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
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)) {
//调用Controller /index
logger.info("Adding welcome page template: index");
this.setRootViewName("index");
}
}
3.1 请求映射 Rest风格
-
@xxxMapping;
-
Rest风格支持(使用HTPP请求方式动词来表示对资源的操作)
-
/user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
-
核心Filter: HiddenHttpMethodFilter
-
用法,表达method=post,隐藏域——method=put
-
必须在SpringBoot中手动开启,
//REST风格 @RequestMapping(value = "/user",method = RequestMethod.GET) public String getUser(){ return "GET-USER"; } @RequestMapping(value = "/user",method = RequestMethod.PUT) public String putUser(){ return "PUT-USER"; } @RequestMapping(value = "/user",method = RequestMethod.POST) public String postUser(){ return "POST-USER"; } @RequestMapping(value = "/user",method = RequestMethod.DELETE) public String deleteUser(){ return "DELETE-USER"; }
//手动开启的原因,默认enabled是false
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
prefix = “spring.mvc.hiddenmethod.filter”,
name = {“enabled”}
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}Rest原理(**表单**提交时要使用REST风格) - 表单提交会带上_method=PUT - **请求过来被hiddenHttpMethodFilter拦截** - 兼容以下请求:PUT,DELETE,PATCH - 原生request(post),包装模式requestWrapper重写了HttpServletRequest接口,getMethod,返回的是传入的值,也就是_method的值 - **过滤器放行的时候时wrapper,以后的方法调用getMethod时调用的requestWrapper的method** - ```java 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) { //拿到隐藏域中_method的值 String paramValue = request.getParameter(this.methodParam); //判断值不是为空 if (StringUtils.hasLength(paramValue)) { //将小写转化为大写 String method = paramValue.toUpperCase(Locale.ENGLISH); //描写允许请求的方式 包不包括DELETE if (ALLOWED_METHODS.contains(method)) { requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method); } } } filterChain.doFilter((ServletRequest)requestToUse, response); }
Rest使用客户端工具
- 与上述的filter方式无关,如PostMan直接发送Put,delete等方式请求
-
-
简化注解
// @RequestMapping(value = "/user",method = RequestMethod.GET @GetMapping("/user") public String getUser(){ return "GET-USER"; } // @RequestMapping(value = "/user",method = RequestMethod.PUT) @PutMapping("/user") public String putUser(){ return "PUT-USER"; } // @RequestMapping(value = "/user",method = RequestMethod.POST) @PostMapping("/user") public String postUser(){ return "POST-USER"; } // @RequestMapping(value = "/user",method = RequestMethod.DELETE) @DeleteMapping("/user") public String deleteUser(){ return "DELETE-USER"; }
- 设置服务器接受的隐藏域参数
@Configuration(proxyBeanMethods = false) public class WebConfig { @Bean public HiddenHttpMethodFilter hiddenHttpMethodFilter(){ HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter(); methodFilter.setMethodParam("_r"); return methodFilter; } }
-
3.2 请求映射原理
DispatcherServlet是处理所有请求的开始,类的继承关系,如下
SpringMVC功能分析都从org.springframework.web.servlet.DispatcherServlet->doDispatch()
doDispatch中核心是getHandler()
- doDispatch()
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//获取原生的request(注意已经是经过3.1过滤过的请求)
HttpServletRequest processedRequest = request;
//handler执行链
HandlerExecutionChain mappedHandler = null;
//是不是文件上传请求,默认为false
boolean multipartRequestParsed = false;
//请求有没有异步
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
//检查是否文件上传请求
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
//决定那个handler来处理请求
//会出现com.atguigu.boot.Controller.HelloController#putUser()****
mappedHandler = this.getHandler(processedRequest);
//
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
- getHandler()
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//handlerMappings:处理器映射 /xxx->>xxx 表示请求交予谁来处理
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;
}
HandlerMapping默认有5个
我们所写的API接口会在RequestMappingHandlerMapping中的mappingRegister(映射注册中心)中找到能处理请求handler
所有的请求映射都在HandlerMapping中
- SpringBoot自动配置欢迎页的HandlerMapping,访问/能访问道index.html
- 我们所写的API接口会在RequestMappingHandlerMapping中的mappingRegister(映射注册中心)中找到能处理请求handler
- 请求今来,挨个尝试所有的HandlerMapping看是否有请求信息
- 如果有,就找到这个请求所对应的handler
- 如果没有的话,就换下一个HandlerMapping中
- 我们需要一些自定义的映射处理,我么也可以自己给容器中方HanndlerMapping,自定义HandlerMapping
3.3 常用web开发注解
- 前端Rest的get请求
<!--Rest风格的get请求路径-->
<a href="/car/1/owner/lisi?age=18&inters=game&inters=basketball">car/{id}/owner/{username}</a>
- 常用注解
@RestController
public class ParameterTestController {
//Rest风格求请
@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar( //路径变量的获取
@PathVariable("id") Integer id,
@PathVariable("username") String username,
@PathVariable Map<String,String> pv,
//请求头参数的获取
@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map<String,String> head,
//请求参数的获取
@RequestParam("age") Integer age,
@RequestParam("inters") List<String> inters,
@RequestParam Map<String,String> params,
//Cookie的获取
@CookieValue("_ga") String _ga,
@CookieValue("_ga") Cookie cookie) {
Map<String,Object> map = new HashMap<>();
map.put("id",id);
map.put("username",username);
map.put("pv",pv);
map.put("User-Agent",userAgent);
map.put("head",head);
map.put("age",age);
map.put("inters",inters);
map.put("params",params);
map.put("_ga",_ga);
map.put("_ga_cookie",cookie);
return map;
}
//Post请求的获取
@PostMapping("/save")
public Map postMethod(@RequestBody String content){
Map<String,Object> map = new HashMap<>();
map.put("contnet",content);
return map;
}
}
- 请求转发
//请求转发
@Controller
public class RequestController {
@GetMapping("/goto")
public String goToPage(HttpServletRequest request){
request.setAttribute("msg","成功了.........");
request.setAttribute("code","200");
return "forward:/success";
}
//请求转发到success中
@ResponseBody
@GetMapping("/success")
public Map success(@RequestAttribute("msg") String msg,
@RequestAttribute("code") String code,
HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
map.put("msg",msg);
map.put("code",code);
map.put("msg_request_Attribute",request.getAttribute("msg"));
return map;
}
}
-
矩阵变量注解
- 前端代码
<!--未使用矩阵变量--> /cars/{path}?xxx=xxx&aaa=ccc 查询字符串时,使用@RequestParam <!--使用矩阵变量--> /cars/{path;low=34;brand=byd,bm,yd}
- 使用场景
页面开发,cookie禁止了,session内容使用 session.set(a,b)--->jsessionid--->cookie--->每次发请求携带 解决方法:url重写:/abc;jsessionid===xxx
- 后端代码
//Spring boot 默认是禁用了矩阵变量的功能 // 需要手动开启,侧面反应应用较少 @GetMapping("/cars/sell") public Map carsSell(@MatrixVariable("low") Integer low, @MatrixVariable("brand") List<String> brand){ Map<String,Object> map = new HashMap<>(); map.put("low",low); return map; }
原理:对于路径的处理都是经过UrlPathHelper进行处理的
sion内容使用
session.set(a,b)—>jsessionid—>cookie—>每次发请求携带
解决方法:url重写:/abc;jsessionid===xxx
- 后端代码
```java
//Spring boot 默认是禁用了矩阵变量的功能
// 需要手动开启,侧面反应应用较少
@GetMapping("/cars/sell")
public Map carsSell(@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List<String> brand){
Map<String,Object> map = new HashMap<>();
map.put("low",low);
return map;
}
原理:对于路径的处理都是经过UrlPathHelper进行处理的