SpringBoot 系列:
1.Spring Boot 入门
在 Spring Boot 入门 中,我们知道了:
SpringBoot对静态资源的映射规则:
"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
"/":当前项目的根路径
//由 ResourceProperties 可以设置和静态资源有关的参数,默认目录、缓存时间等
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties implements ResourceLoaderAware {
// WebMvcAuotConfiguration 中对静态资源的映射
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Integer cachePeriod = this.resourceProperties.getCachePeriod();
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(
registry.addResourceHandler("/webjars/**")
.addResourceLocations(
"classpath:/META-INF/resources/webjars/")
.setCachePeriod(cachePeriod));
}
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
//静态资源文件夹映射
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(
registry.addResourceHandler(staticPathPattern)
.addResourceLocations(
this.resourceProperties.getStaticLocations())
.setCachePeriod(cachePeriod));
}
}
//配置欢迎页映射
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(
ResourceProperties resourceProperties) {
return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
}
//配置喜欢的图标
@Configuration
@ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
public static class FaviconConfiguration {
private final ResourceProperties resourceProperties;
public FaviconConfiguration(ResourceProperties resourceProperties) {
this.resourceProperties = resourceProperties;
}
@Bean
public SimpleUrlHandlerMapping faviconHandlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
//所有 **/favicon.ico
mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",
faviconRequestHandler()));
return mapping;
}
@Bean
public ResourceHttpRequestHandler faviconRequestHandler() {
ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
requestHandler
.setLocations(this.resourceProperties.getFaviconLocations());
return requestHandler;
}
}
模板引擎: html 页面+数据经过模板引擎的处理
而 SpringBoot 推荐使用的 Thymeleaf 模板引擎:语法简单,功能强大
引入 Thymeleaf
<properties>
<thymeleaf.version>3.0.2.RELEASEthymeleaf.version>
<thymeleaf-layout-dialect.version>2.1.1thymeleaf-layout-dialect.version>
properties>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
默认的配置: 默认从 classpath:/templates/ 下找相应的 html 页面进行自动渲染
使用:
导入thymeleaf的名称空间
<html lang="en" xmlns:th="http://www.thymeleaf.org">
语法:
例子
contoller:
@RequestMapping("/test")
public String testThymeleaf(Map<String,Object> map){
map.put("user","moke");
return "test";//test.html
}
html:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Testtitle>
head>
<body>
<h1>测试 h1>
<div th:text="${user}">用户名div>
body>
html>
哪些默认的自动配置?来自官方文档
ContentNegotiatingViewResolver
and BeanNameViewResolver
beans.
index.html
support.
Favicon
support (see below).
Converter
, GenericConverter
, Formatter
beans.
@Bean
@ConditionalOnProperty(prefix = "spring.mvc", name = "date-format")//在文件中配置日期格式化的规则
public Formatter<Date> dateFormatter() {
return new DateFormatter(this.mvcProperties.getDateFormat());//日期格式化组件
}
HttpMessageConverters
(see below).
MessageCodesResolver
(see below).
ConfigurableWebBindingInitializer
bean (see below).
… 其他自动配置可以参考:org.springframework.boot.autoconfigure.web 下的 xxAutoConfiguration
扩展 SpringMVC(修改默认配置)
既保留了所有的自动配置,也能用我们扩展的配置:
编写一个配置类(@Configuration),是WebMvcConfigurerAdapter类型;不能标注@EnableWebMvc,根据扩展的功能重写相应的方法:
//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//浏览器发送 /test请求来到 test 页面
registry.addViewController("/test").setViewName("test");
}
}
原理:
替换 SpringMVC 的配置
只需在我们自定义的配置类上,标注 @EnableWebMvc。
@EnableWebMvc 原理:
步骤:
编写国际化配置文件,注意 properties 文件的编码设置:
SpringBoot 通过 MessageSourceAutoConfiguration 自动配置来管理国际化资源文件,默认国际化基础名为 messages
修改国际化配置基础名:
使用 th 修改页面信息:
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign inh1>
<label class="sr-only" th:text="#{login.username}">Usernamelabel>
<input type="text" class="form-control" placeholder="Username" th:placeholder="#{login.username}" required="" autofocus="">
<label class="sr-only" th:text="#{login.password}">Passwordlabel>
<input type="password" class="form-control" placeholder="Password" th:placeholder="#{login.password}" required="">
国际信息的切换: 根据 request 请求头中的区域信息,通过 LocaleResolver 获取区域信息对象,所以可以重写 LocaleResolver:
<a class="btn btn-sm" th:href="@{/index.html(l=zh_CN)}">中文a>
<a class="btn btn-sm" th:href="@{/index.html(l=en_US)}">Englisha>
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest httpServletRequest) {
String l = httpServletRequest.getParameter("l");
Locale locale = Locale.getDefault();
if(!StringUtils.isEmpty(l)){
String[] ss = l.split("_");
locale = new Locale(ss[0],ss[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
SpringBoot 默认的页面:
首先说下 SpringBoot 发送 4xx/5xx 错误的处理步骤:
发生错误 ErrorPageCustomizer 就会生效并来到 /error 请求,而 /error 请求会交给BasicErrorController 进行处理,而处理之后去到哪个页面是由 DefaultErrorViewResolver 解析得到的。
而这些组件都是由 ErrorMvcConfiguration 自动配置的:
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//默认SpringBoot可以去找到一个页面-> error/404
String errorViewName = "error/" + viewName;
//用模板引擎解析这个地址
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
/*
模板引擎可用的情况下返回到errorViewName指定的视图地址
模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面 error/404.html
*/
return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}
根据上面的分析:
1.自定义错误页面
默认请求是 error/状态码,所以:
2.自定义错误JSON
//@ControllerAdvice 主要是用于全局的异常拦截和处理,这里的异常可以使自定义异常也可以是JDK里面的异常
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(UserNotExistException.class)//UserNotExistException为自定义异常
public Map<String,Object> handleException(Exception e){
Map<String,Object> map = new HashMap<>();
map.put("code","user.notexist");
map.put("message",e.getMessage());
return map;
}
}
@ExceptionHandler(UserNotExistException.class)
public String handleException(Exception e, HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
//传入我们自己的错误状态码 4xx 5xx,否则就不会进入定制错误页面的解析流程
request.setAttribute("javax.servlet.error.status_code",500);
map.put("code","user.notexist");
map.put("message",e.getMessage());
//request.setAttribute("ext",map);
return "forward:/error";
}
上面的这种方法其实没有将我们自己的 map 中的数据作为 json 返回,因为map并未被放到 DefaultErrorAttributes 中,所以我们可以将 map 放入到 request 中并自定义 ErrorAttributes:
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
map.put("test","moke");
//保存的map
Map<String,Object> ext = (Map<String,Object>)requestAttributes.getAttribute("ext",0);
map.put("ext",ext);
return map;
}
}
由依赖可知,SpringBoot 默认使用 Tomcat 作为嵌入式的Servlet容器
而修改 Servlet 容器的相关配置有两种方式:
server:
# 通用配置
port: 8081
context-path: /demo
# tomcat 配置
tomcat:
uri-encoding: UTF-8
@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
return new EmbeddedServletContainerCustomizer() {
//定制嵌入式的Servlet容器相关的规则
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.setPort(8082);
}
};
}
注册三大组件:
之前我们是在 web.xml 中进行配置,而 SpringBoot 是一个 JAR。
@Bean
public ServletRegistrationBean myServlet(){
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet");//映射我们的 Servlet
return registrationBean;
}
@Bean
public FilterRegistrationBean myFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new MyFilter());//设置我们的过滤器
registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));//拦截的请求
return registrationBean;
}
@Bean
public ServletListenerRegistrationBean myListener(){
ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener());
return registrationBean;
}
其实 SpringBoot 对 DispatcherServlet的配置也是通过 ServletRegistrationBean 进行配置的,详见 DispatcherServletAutoConfiguration。
使用其他 Servlet 容器
前面使用 EmbeddedServletContainerCustomizer 进行配置的修改,通过 ConfigurableEmbeddedServletContainer 可以看到我们可使用的三种嵌入式容器:tomcat、undertow、jetty
从依赖入手,排除默认的 tomcat,并引入其他的 Servlet 容器:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcatartifactId>
<groupId>org.springframework.bootgroupId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jettyartifactId>
dependency>
嵌入式Servlet容器自动配置原理(EmbeddedServletContainerAutoConfiguration):
使用外部 Servlet 容器
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
<scope>providedscope>
dependency>
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//传入SpringBoot应用的主程序
return application.sources(SpringBootJspApplication.class);
}
}
外部容器加载 Spring 应用的原理: