SpringBoot02-webmvc

SpringBoot的web开发

webJar

​ 以jar包的形式来引入前端资源,比如jquery 或者是BootStrap:https://www.webjars.org

前端资源映射规则 核心源代码:WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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});
}

});
}
}

访问:http://localhost:8080//webjars/jquery/3.3.1-2/jquery.js

控制台打印:

image-20210811233641951

源码追踪:

1
org.springframework.web.servlet.resource.ResourceHttpRequestHandler#handleRequest方法 >org.springframework.web.servlet.resource.ResourceHttpRequestHandler#getResource >org.springframework.web.servlet.resource.ResourceResolverChain#resolveResource >org.springframework.web.servlet.resource.PathResourceResolver#resolveResourceInternal >org.springframework.web.servlet.resource.PathResourceResolver#getResource(真正的资源映射 处理逻辑)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Nullable
private Resource getResource(String resourcePath, @Nullable HttpServletRequest request,
List<? extends Resource> locations) {

for (Resource location : locations) {
try {
String pathToUse = encodeOrDecodeIfNecessary(resourcePath, request, location);
Resource resource = getResource(pathToUse, location);
if (resource != null) {
return resource;
}
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
String error = "Skip location [" + location + "] due to error";
if (logger.isTraceEnabled()) {
logger.trace(error, ex);
}
else {
logger.debug(error + ": " + ex.getMessage());
}
}
}
}
return null;
}

静态页面映射

我们直接把静态页面放在static的目录下,直接可以在路径直接访问

image-20210814182837595

Springboot整合springmvc

自动装配的组件

①:ContentNegotiatingViewResolver 和 BeanNameViewResolver 视图解析器 视图解析器的作用:根据方法的值找到对应的视图

②:Support for serving static resources, including support for WebJars 支持静态资源和webJars

③:Converter ,日期格式化器 Formatter

④:消息装换器: HttpMessageConverters

⑤:首页设置index.html

⑥:图标支持 Favicon

拓展SpringBoot的mvc配置

​ 自己写一个配置类 继承 WebMvcConfigurer 需要什么组件 就注册什么组件

如何往容器中添加一个拦截器

往容器中增加一个过滤器

往容器中增加一个servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@Configuration
public class MvcConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
HandlerInterceptor handlerInterceptor = new HandlerInterceptor() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("myHandlerInterceptor...前置拦截器:...");
return true;
}
};
registry.addInterceptor(handlerInterceptor).addPathPatterns("/**").excludePathPatterns("/index.html");
}


@Bean
public FilterRegistrationBean filterRegistrationBean(){
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("myFilter 的 doFilter方法");
filterChain.doFilter(servletRequest,servletResponse);
}

@Override
public void destroy() {

}
};

FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(filter);
filterRegistrationBean.addUrlPatterns("/*"); return filterRegistrationBean;
}

@Bean
public ServletRegistrationBean myHttpServlet(){

HttpServlet httpServlet = new HttpServlet() {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("myHttpServlet");
super.doPost(req,resp);
}
};
ServletRegistrationBean servletRegistration = new ServletRegistrationBean(httpServlet);
servletRegistration.addUrlMappings("/hello1");

return servletRegistration;
}
}

接管springboot的mvc配置

方式

​ 使用一个@EnableWebMvc来标识到配置类上,就会导致配置失效

原理:

​ @EnableWebMvc 为容器中导入了DelegatingWebMvcConfiguration的组件,是WebMvcConfigurationSupport的实现类,而WebMvcAutoConfiguration在WebMvcConfigurationSupport不存在时才会自动配置

1
2
3
4
5
6
7
8
9
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport

1
2
3
4
5
6
7
8
@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

SpringBoot错误处理机制

postman 请求 http://localhost:8080/hello3

1
2
3
4
5
6
{
"timestamp": 1629035261980,
"status": 404,
"error": "Not Found",
"path": "/hello3"
}

浏览器

image-20210815215334258

如何跳转到/error

1
2
3
DispatchServlet.doDispatch 找不到 返回
StandardHostValve.custom
ApplicationDispatcher.doForward 转发 到 /error

自动配置的异常处理的一些bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
DefaultErrorAttributes
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}

BasicErrorController:/error请求处理控制器
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
ObjectProvider<ErrorViewResolver> errorViewResolvers) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
errorViewResolvers.orderedStream().collect(Collectors.toList()));
}

ErrorPageCustomizer:错误页面定制器
@Bean
public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {
return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
}


DefaultErrorViewResolver
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resources);
}

注入默认错误页面 defaultErrorView
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
@Conditional(ErrorTemplateMissingCondition.class)
protected static class WhitelabelErrorViewConfiguration {

private final StaticView defaultErrorView = new StaticView();

@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
return this.defaultErrorView;
}

// If the user adds @EnableWebMvc then the bean name view resolver from
// WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment.
@Bean
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}

}

处理/error请求的过程,以浏览器请求为例

  1. org.springframework.web.servlet.DispatcherServlet#doDispatch

    1. org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter
      1. org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml (基础错误控制器)
        1. org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController#resolveErrorView 遍历errorViewResolver 解析错误视图
          1. org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorViewResolver#resolveErrorView //查找error/“ + viewName视图是否存在,如果没有对应的解析精确匹配的状态码 使用模糊匹配比如4XX 5XX
        2. return (modelAndView != null) ? modelAndView : new ModelAndView(“error”, model);找不到对应解析器,则查找 error 对应的model
    2. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 解析error视图View

定制错误异常信息

image-20210816095205231

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@ControllerAdvice
public class ExcepctionHandler {

@ExceptionHandler(Exception.class)
@ResponseBody
public BasicResponse handlerException(Exception e){
return BasicResponse.failed();
}
}

@Data
public class BasicResponse<T>{

private int code;
private String msg;
private T info;

private static final Integer SUCCESS_CODE = 200;
private static final String SUCCESS_MSG = "请求成功";
private static final Integer FAIL_CODE = 500;
private static final String FAIL_MSG = "服务器报错";

public static BasicResponse success() {
return new BasicResponse<>(SUCCESS_CODE,SUCCESS_MSG);
}

public static BasicResponse success(String successMsg) {
return new BasicResponse<>(SUCCESS_CODE, successMsg);
}



public static BasicResponse failed() {
return new BasicResponse<>(FAIL_CODE, FAIL_MSG);
}

public static BasicResponse failed(String failedMsg) {
return new BasicResponse<>(FAIL_CODE, failedMsg);
}

public static <T> BasicResponse failed(String failedMsg,T object) {
return new BasicResponse<>(FAIL_CODE, failedMsg,object);
}

public static BasicResponse failed(Integer code,String failedMsg) {
return new BasicResponse<>(code, failedMsg);
}


private BasicResponse(int code, String msg) {
this.code = code;
this.msg = msg;
}

private BasicResponse(int code, String msg, T info) {
this(code, msg);
this.info = info;
}
}