四步清、Spring Boot與Web開發(fā)
1刨啸、使用Spring Boot
-
創(chuàng)建Spring Boot應用获茬,選中我們需要的模塊履恩,Spring Boot默認將選中的模塊配置好了锰茉,我們只需要在配置文件中指定少量的配置就可以運行
自動配置原理:根據之前的Spring Boot配置文章中的原理
自己編寫業(yè)務邏輯代碼
2、Spring Boot靜態(tài)資源的映射
在WebMvcAutoConfiguration下我們可以找到下面這個方法
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
if (!registry.hasMappingForPattern("/webjars/**")) {
this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
}
//歡迎頁映射
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));
return welcomePageHandlerMapping;
}
-
所有/webjars/**的請求都去classpath:/META-INF/resources/webjars/找資源切心,webjars就是以jar包的方式引入靜態(tài)資源
webjars官網飒筑,在官網中我們可以看到一些靜態(tài)資源我們可以以maven的方式導入
-
/**為訪問當前項目的任何資源,
"classpath:/META-INF/resources/" "classpath:/resources/" "classpath:/static/" "classpath:/public/"
歡迎頁映射绽昏,從2中路徑下查找index.html
-
我們也可以自己設置靜態(tài)資源的路徑
spring.resources.static-locations=classpath:/sp/,classpath:/sp1/ 多個文件用逗號分割
3协屡、Spring Boot模板引擎
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
我們把html頁面放在classpath:/template/下,thymeleaf就能自動渲染
-
導入thymeleaf的名稱空間
<html xmlns:th="http://www.thymeleaf.org">
-
使用thymeleaf
public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING; public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html"; private boolean checkTemplate = true; private boolean checkTemplateLocation = true; private String prefix = "classpath:/templates/"; private String suffix = ".html"; private String mode = "HTML"; //只要我們把HTML頁面放在classpath:/templates/下全谤,thymeleaf就能幫我們渲染
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div th:text="${hello}"> 這里是歡迎信息 </div> </body> </html>
-
語法規(guī)則
th:text 改變當前元素的文本內容
th:insert th:replace 片段包含
th:each 遍歷
th:if th:unless th:switch th:case 條件判斷
th:object th:with 聲明變量
th:attr th:attrprepend th:attrappend 任意屬性修改
th:value th:src th:href 修改指定屬性默認值
th:text th:utext 修改標簽體內容肤晓,utext不轉義特殊字符
${...} 獲取變量屬性,調用方法
*{...} 在功能上和${...}一樣
{...} 獲取國際化內容
@{...} 定義url
~{...} 片段引用
更多的請查看官方thymeleaf文檔
-
Spring Boot MVC
Spring MVC Auto-configuration
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
The auto-configuration adds the following features on top of Spring’s defaults:
-
Inclusion of
ContentNegotiatingViewResolver
andBeanNameViewResolver
beans.自動配置了ViewResolver(視圖解析器:根據方法的返回值得到視圖對象,視圖對象決定如何渲染(轉發(fā)或者重定向))
-
Support for serving static resources, including support for WebJars (covered later in this document)).
靜態(tài)資源路徑
-
Automatic registration of
Converter
,GenericConverter
, andFormatter
beans.Converter:轉換器补憾,類型轉換
Formatter:格式化器
-
Support for
HttpMessageConverters
(covered later in this document).HttpMessageConverters:SpringMVC轉換http請求與響應的漫萄,是從容器中確定并獲得所有的HttpMessageConverters
-
Automatic registration of
MessageCodesResolver
(covered later in this document).定義代碼錯誤規(guī)則
Static
index.html
support.Custom
Favicon
support (covered later in this document).-
Automatic use of a
ConfigurableWebBindingInitializer
bean (covered later in this document).初始化WebDataBinder
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own
@Configuration
class of typeWebMvcConfigurer
but without@EnableWebMvc
. If you wish to provide custom instances ofRequestMappingHandlerMapping
,RequestMappingHandlerAdapter
, orExceptionHandlerExceptionResolver
, you can declare aWebMvcRegistrationsAdapter
instance to provide such components.If you want to take complete control of Spring MVC, you can add your own
@Configuration
annotated with@EnableWebMvc
. -
-
如何修改默認配置
- Spring Boot在自動配置組件的時候,先看用戶有沒有自己配盈匾,如果有就用用戶配置的腾务,沒有就自動配置,如果有多個組件就將用戶配置的和自己默認的組合起來
4削饵、擴展SpringMVC
<mvc:view-controller path="/hello" view-name="success">
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/hello"/>
<bean></bean>
</mvc:interceptor>
</mvc:interceptors>
編寫一個配置類(@Configuration)岩瘦,是WebMvcConfigurer
類型,不能標注@EnableWebMvc
@Configuration
public class MyConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//瀏覽器發(fā)送
registry.addViewController("/test").setViewName("success");
}
}
既保留了自動配置窿撬,又有我們的配置担钮,我們可以看到WebMvcAutoConfigurationAdapter也實現了WebMvcConfigurer,重要的是上面有
@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
EnableWebMvcConfiguration里的代碼
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
我們點開EnableWebMvcConfiguration可以看到這樣一段代碼
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
public DelegatingWebMvcConfiguration() {
}
//從容器中獲取所有的WebMvcConfigurer
@Autowired(
required = false
)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
//將所有的WebMvcConfigurer一起來調用
}
}
比如addViewControllers的實現
public void addViewControllers(ViewControllerRegistry registry) {
Iterator var2 = this.delegates.iterator();
while(var2.hasNext()) {
WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
delegate.addViewControllers(registry);
}
}
這樣Spring Boot的自動配置和我們自己的配置會一起起作用
5尤仍、全面接管SpringMVC
Spring Boot的自動配置不要了箫津,我們需要在配置類中加入@EnableWebMvc
,加入后所有的自動配置都會失效宰啦,例如靜態(tài)資源訪問的路徑Spring Boot默認的路徑就不會生效苏遥,需要你自己配置
@EnableWebMvc注解的核心就是
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
//DelegatingWebMvcConfiguration.class
@Configuration(
proxyBeanMethods = false
)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
為什么加了@EnableWebMvc自動配置就失效了,我們現在來看自動配置類
@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 {
我們加了@EnableWebMvc就會導入WebMvcConfigurationSupport類田炭,所以自動配置類判斷后發(fā)現容器里有WebMvcConfigurationSupport,就不會生效了
而導入的WebMvcConfigurationSupport只是最基本的功能漓柑,需要我們自己寫配置
在Spring Boot中會有很多xxxxConfigurer來幫助我們配置
6教硫、Web開發(fā)實驗
6.1、引入資源
index.html(即歡迎頁映射)會首先取static辆布、public瞬矩、resources里的文件,而在xxxxController中return "xxx"會先取templates中的文件
server.servlet.context-path=/test
之后訪問時需要在端口后加上/test
6.2锋玲、根據瀏覽器原信息展示頁面的國際化效果
-
編寫國際化配置文件景用,抽取頁面需要顯示的國際化消息
配置文件樹
在編寫過程中遇到了ceshi_zh_CN.properties被識別為普通文件的問題,在setting->Editor->File Types中在上方選擇Text惭蹂,然后在下方拉到最底會有你創(chuàng)建的文件伞插,刪除它就可以了,你會發(fā)現xxx_zh_CN.properties被正常識別了
-
Spring Boot自動配置好了管理國際化資源配置的組件
public class MessageSourceProperties { private String basename = "messages"; //默認基礎名是messages public class MessageSourceAutoConfiguration { private static final Resource[] NO_RESOURCES = new Resource[0]; public MessageSourceAutoConfiguration() { } @Bean @ConfigurationProperties( prefix = "spring.messages" ) @Bean public MessageSource messageSource(MessageSourceProperties properties) { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); if (StringUtils.hasText(properties.getBasename())) { messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename()))); //setBasenames設置國際化資源文件的基礎名(去掉語言國家代碼的) } if (properties.getEncoding() != null) { messageSource.setDefaultEncoding(properties.getEncoding().name()); } messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale()); Duration cacheDuration = properties.getCacheDuration(); if (cacheDuration != null) { messageSource.setCacheMillis(cacheDuration.toMillis()); } messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat()); messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage()); return messageSource; }
我們可以更改默認的基礎名盾碗,在application.properties里寫spring.messages.basename=languages.ceshi媚污,就能連接到我們自己寫的配置文件
-
頁面獲取
在ceshi.html頁面中
<h1 th:text="#{ceshi.name}"></h1> <label th:text="#{ceshi.password}"></label> [[#{ceshi.data}]] <!-- 這是行內寫法 -->
如果訪問時遇到亂碼,可以按照下圖改為utf-8并打上勾
解決中文亂碼
-
設置語言切換按鈕
國際化的原理廷雅,在WebMvcAutoConfiguration.class中可以看到
@Bean @ConditionalOnMissingBean @ConditionalOnProperty( prefix = "spring.mvc", name = {"locale"} ) public LocaleResolver localeResolver() { if (this.mvcProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.LocaleResolver.FIXED) { return new FixedLocaleResolver(this.mvcProperties.getLocale()); //可以指定用固定的 } else { AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); //否則是用AcceptHeaderLocaleResolver localeResolver.setDefaultLocale(this.mvcProperties.getLocale()); return localeResolver; } }
我們打開AcceptHeaderLocaleResolver
public Locale resolveLocale(HttpServletRequest request) { //獲取區(qū)域信息 Locale defaultLocale = this.getDefaultLocale(); if (defaultLocale != null && request.getHeader("Accept-Language") == null) { return defaultLocale; } else { //區(qū)域信息是在request中獲取 Locale requestLocale = request.getLocale(); List<Locale> supportedLocales = this.getSupportedLocales(); if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) { Locale supportedLocale = this.findSupportedLocale(request, supportedLocales); if (supportedLocale != null) { return supportedLocale; } else { return defaultLocale != null ? defaultLocale : requestLocale; } } else { return requestLocale; } } }
如果我們要設計為按按鈕切換那我們就不要使用自動配置里的區(qū)域解析器耗美,我們創(chuàng)建一個MyLocaleResolver
//我們既然要點擊鏈接切換國際化氢伟,那么我們可以在鏈接上帶有區(qū)域信息 public class MyLocaleResolver implements LocaleResolver { @Override public Locale resolveLocale(HttpServletRequest httpServletRequest) { String l = httpServletRequest.getParameter("l"); System.out.println(l); Locale locale = Locale.getDefault(); if (!StringUtils.isEmpty(l)){ String[] s = l.split("_"); locale = new Locale(s[0], s[1]); } return locale; } @Override public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) { } }
然后在MyConfig中將它加入容器,這樣我們自己的組件就會生效幽歼,可以實現按鈕切換了
@Bean public LocaleResolver localeResolver(){ return new MyLocaleResolver(); }
6.3朵锣、登錄與攔截器
登錄
模板引擎頁面開發(fā)期間需要實時生效,首先禁用緩存甸私,然后按Ctrl
+F9
诚些,就可以實時生效了
spring.thymeleaf.cache=false
@PostMapping(value = "/user/login")
//@RequestMapping(value = "/user/login", method = RequestMethod.POST),兩種都可以
//@RequestParam("username")皇型,如果沒有傳入參數就會報錯
public String loginceshi(@RequestParam("username") String username,
@RequestParam("password") String password){
System.out.println("登錄跳轉");
if (!StringUtils.isEmpty(username) && password.equals("123456")){
return "loginsuccess";
}else {
return "ceshi";
}
}
<form action="/user/login" method="post">
<input placeholder="用戶名" name="username">
<input placeholder="密碼" name="password">
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
<button type="submit">sign in</button>
</form>
這里注意有個重復提交表單的問題诬烹,我們按住F5
瀏覽器會提示是否重新發(fā)送表單,要解決重復提交的問題弃鸦,我們可以重定向
攔截器
我們創(chuàng)建自己的攔截器LoginHandlerInterceptor绞吁,這里實現HandlerInterceptor類不會報錯,在后來的版本中唬格,這個接口的方法從原來的抽象方法變成了default修飾的默認方法家破,因此不會顯紅線報需要實現的方法,需要按Ctrl
+ O
手動選擇购岗。
//目標方法執(zhí)行之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object user = request.getSession().getAttribute("name");
if (user == null){
//沒有登陸汰聋,應該返回登錄界面
request.setAttribute("msg", "請先登錄");
request.getRequestDispatcher("/ceshi").forward(request, response);
return false;
}else {
return true;
}
}
之后添加到我們的配置類中
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/main")
.excludePathPatterns("/asserts/**", "/webjars/**");
//也可以不用特意過濾靜態(tài)資源,SpringBoot會自動放行static下的
}
6.4喊积、Restful CRUD
-
RestfulCRUD:CRUD滿足Rest風格烹困;
URI: /資源名稱/資源標識 HTTP請求方式區(qū)分對資源CRUD操作
普通CRUD RestfulCRUD 查詢 getEmp emp---GET 添加 addEmp?xxx emp---POST 修改 updateEmp?id=xxx&xxx=xx emp/{id}---PUT 刪除 deleteEmp?id=1 emp/{id}---DELETE -
實驗的請求架構;
實驗功能 請求URI 請求方式 查詢所有員工 emps GET 查詢某個員工(來到修改頁面) emp/1 GET 來到添加頁面 emp GET 添加員工 emp POST 來到修改頁面(查出員工進行信息回顯) emp/1 GET 修改員工 emp PUT 刪除員工 emp/1 DELETE thymeleaf公共頁面抽取
<!-- 公共頁面抽取,copy可以隨意取名 --> <div th:fragment="copy"> © 2011 The Good Thymes Virtual Grocery </div> <!-- 引入公共片段 --> <!-- 一個是~{templatename::selector}:模板名::選擇器 另一種寫法是~{templatename::fragmentname}:模板名::片段名 --> <!-- 將公共片段插入到div中 --> <div th:insert="footer :: copy"></div> <!-- 將聲明引入的元素替換為公共片段 --> <div th:replace="footer :: copy"></div> <!-- 將被引入片段的內容包含進標簽中 --> <div th:include="footer :: copy"></div> <!-- 也可以寫為 --> <!-- 如果使用以上方法引入可以不用寫~{}乾吻,而行內寫法必須加上[[~{}]]髓梅、[(~{})] --> <div th:insert="~{footer :: copy}"></div> [[~{footer :: copy}]] <!-- 實際效果 --> <div> <footer> © 2011 The Good Thymes Virtual Grocery </footer> </div> <footer> © 2011 The Good Thymes Virtual Grocery </footer> <div> © 2011 The Good Thymes Virtual Grocery </div>
引入片段時傳入參數
<!-- 在引入片段需要傳參的地方寫入 --> <a class="nav-link active" th:class="${activeIf=="main"?"nav-link active":"nav-link"}"</a> <a class="nav-link active" th:class="${activeIf=="test"?"nav-link active":"nav-link"}"</a> <!-- 傳參時 --> <div th:replace="::frag (${value1},${value2})">...</div> <div th:replace="::frag (onevar=${value1},twovar=${value2})">...</div> <!-- 例如 --> <div th:replace="footer::copy (activeIf='main')"></div>
遍歷數據
<tbody> <!-- 會生成一個個tr --> <tr th:each="emp:${emps}"> <td th:text="${emp.name}"></td> <td th:text="${emp.gender}==0'男':'女'"></td> <td th:text="${emp.phone.number}"></td> <td th:text="${#dates.format(emp.date,'yyyy-MM-dd HH:mm')}"></td> </tr> </tbody>
自定義屬性
單個 th:attr="action=@{/subscribe}" 多個 th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}"
PUT和DELETE請求的發(fā)送
配置HiddenHttpMethodFilter
創(chuàng)建一個post表單和一個隱藏的input項,必須滿足name="_method"绎签,input值就是我們的請求方式
-
emp傳遞參數
@DeleteMapping("/emp/{id}") public String deleteEmployee(@PathVariable("id") Integer id){ //id為傳遞的參數 }
<!-- 若是通過重定向到了這個界面枯饿,即使用了redirectAttributes.addAttribute,則用 <form th:action="@{/emp/}+${param.id}" method="post"> --> <form th:action="@{/emp/}+${id}" method="post"> <input type="hidden" name="_method" value="delete"> <button type="submit">刪除</button> </form>
-
在application.properties中寫入
spring.mvc.hiddenmethod.filter.enabled=true
我們可以查看WebMvcAutoConfiguration里的OrderedHiddenHttpMethodFilter
@Bean @ConditionalOnMissingBean({HiddenHttpMethodFilter.class}) @ConditionalOnProperty( prefix = "spring.mvc.hiddenmethod.filter", name = {"enabled"}, matchIfMissing = false //自己將其設為true ) public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() { return new OrderedHiddenHttpMethodFilter(); }
7辜御、錯誤處理機制
7.1鸭你、Spring Boot默認的錯誤處理機制
錯誤處理機制
-
瀏覽器訪問localhost:8080/aaa
默認錯誤信息
-
Http Client訪問localhost:8080/aaa
點擊Tools -> Http Client -> Test RESTful Web Service,在path輸入/aaa
可以得到
HTTP Client
為什么會有默認的界面擒权,我們可以查看ErrorMvcAutoConfiguration錯誤處理的自動配置,這個類給容器添加了以下組件阁谆,原理方面將組件介紹與下面的運行步驟結合看
-
DefaultErrorAttributes
能獲得的信息有
- timestamp:時間戳
- status:狀態(tài)碼
- error:錯誤提示
- exception:異常對象
- message:異常消息
- errors:JSR303數據校驗的錯誤
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { //能獲取的信息的信息有timestamp:時間戳碳抄、status:狀態(tài)碼 Map<String, Object> errorAttributes = new LinkedHashMap(); errorAttributes.put("timestamp", new Date()); this.addStatus(errorAttributes, webRequest); this.addErrorDetails(errorAttributes, webRequest, includeStackTrace); this.addPath(errorAttributes, webRequest); return errorAttributes; }
-
BasicErrorController
@Controller @RequestMapping({"${server.error.path:${error.path:/error}}"}) //處理默認/error請求 public class BasicErrorController extends AbstractErrorController { @RequestMapping( produces = {"text/html"} ) //產生html類型的數據,瀏覽器的請求到這里處理 public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = this.getStatus(request); Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); //去哪個頁面作為錯誤頁面场绿,包含頁面地址和頁面內容 ModelAndView modelAndView = this.resolveErrorView(request, response, status, model); return modelAndView != null ? modelAndView : new ModelAndView("error", model); /** 返回是空剖效,即我們沒有自己設置錯誤頁面,就返回一個error視圖,相關視圖在 ErrorMvcAutoConfiguration下有設置 **/ } @RequestMapping //產生json類型的數據璧尸,其它客戶端的請求到這里處理 public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { HttpStatus status = this.getStatus(request); if (status == HttpStatus.NO_CONTENT) { return new ResponseEntity(status); } else { Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL)); return new ResponseEntity(body, status); } }
至于如何如何分辨的咒林,我們可以在瀏覽器查看請求頭
瀏覽器請求頭
而在Http Client中我們可以看到請求頭為
-
ErrorPageCustomizer
@Value("${error.path:/error}") private String path = "/error"; //系統(tǒng)出現錯誤以后來到error請求
-
DefaultErrorViewResolver
/** 以下為DefaultErrorViewResolver的方法 **/ public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) { //默認Spring Boot可以去找到一個頁面,例如 error/404 String errorViewName = "error/" + viewName; //如果模板引擎可以解析就用模板引擎解析 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext); //模板引擎可以使用則返回到errorViewName指定的視圖地址 //模板引擎不可用就在靜態(tài)資源文件夾下找到errorViewName對應的頁面爷光,error/404.html //然后返回產生一個ModelAndView對象 return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model); }
運行步驟
一旦出現4xx和5xx之類的錯誤垫竞,ErrorPageCustomizer就會生效(定制錯誤響應規(guī)則),就會來到/error請求蛀序,然后被BasicErrorController處理
-
響應頁面:去哪個頁面是由DefaultErrorViewResolver決定的欢瞪,在resolveErrorView得到所有的errorViewResolvers的ModelAndView,而DefaultErrorViewResolver會得到一個默認的ModelAndView
響應數據:查看getErrorAttributes方法里的errorAttributes對象徐裸,可以發(fā)現ErrorAttributes是一個抽象類遣鼓,而DefaultErrorAttributes正實現了它,所以我們查看ErrorMvcAutoConfiguration下的DefaultErrorAttributes組件
/** 以下為BasicErrorController類下的errorHtml方法里所使用的resolveErrorView方法 **/ protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { Iterator var5 = this.errorViewResolvers.iterator(); ModelAndView modelAndView; //所有的errorViewResolvers得到ModelAndView do { if (!var5.hasNext()) { return null; } ErrorViewResolver resolver = (ErrorViewResolver)var5.next(); modelAndView = resolver.resolveErrorView(request, status, model); } while(modelAndView == null); //模板引擎和靜態(tài)資源文件夾下都沒有文件重贺,即我們沒有自己設置錯誤頁面的情況下骑祟,就返回空 return modelAndView; } /** 以下為BasicErrorController類下的errorHtml方法里所使用的getErrorAttributes方法 **/ protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) { WebRequest webRequest = new ServletWebRequest(request); return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace); }
7.2、定制自己的錯誤處理頁面和數據
如何定制自己的錯誤頁面和頁面的錯誤信息
-
有模板引擎情況下气笙,就創(chuàng)建error/
狀態(tài)碼
.html曾我,例如 error/404.html,要想匹配狀態(tài)碼4xx或者5xx健民,就在error文件夾下創(chuàng)建4xx.html或者5xx.html抒巢,注意有精確匹配的情況下,即有404.html和4xx.html發(fā)生404錯誤會來到404.html頁面能獲取的信息秉犹,可以查看DefaultErrorAttributes組件蛉谜,獲取信息例子如下
<p>status:[[${status}]]</p> <p>timestamp:[[${timestamp}]]</p>
沒有模板引擎的情況下(模板引擎找不到這個錯誤頁面),在靜態(tài)資源文件夾下找
模板引擎和靜態(tài)資源文件夾下都沒有就來到Spring Boot默認的空白頁面
如何定制錯誤的json信息
-
首先我們可以創(chuàng)建一個自己的異常類崇堵,例如
//為了異常能夠拋出型诚,繼承運行時異常(RuntimeException) public class DataNullException extends RuntimeException{ public DataNullException() { super("數據空異常"); } }
再創(chuàng)建一個增強Controller(@ControllerAdvice)配合@ExceptionHandler來處理異常
@ControllerAdvice public class MyExceptionHandler { @ResponseBody @ExceptionHandler(DataNullException.class) public Map<String, Object> errorException(Exception e){ Map<String, Object> map = new HashMap<>(); map.put("code", "Data Null"); map.put("message", e.getMessage()); return map; } }
但是這樣無論是瀏覽器訪問還是客戶端訪問都只返回json字符串,注意要在配置文件加上下面的配置才能獲取Exception信息
server.error.include-exception=true
-
我們希望能夠有自適應效果鸳劳,在不改變原來json數據的同時往里面添加數據
@Component //繼承DefaultErrorAttributes狰贯,重寫getErrorAttributes方法 public class MyErrorAttributes extends DefaultErrorAttributes { //保證能夠得到自定義的exception,默認為false public MyErrorAttributes(){ super(true); } @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace); //得到父類里的數據 map.put("code", "Data Null"); map.put("message", "cs"); Map<String, Object> ceshi = (Map<String, Object>) webRequest.getAttribute("ceshi", 0); map.put("ceshi", ceshi); //將我們自己的數據與源數據合并 return map; } } @ControllerAdvice public class MyExceptionHandler { @ExceptionHandler(DataNullException.class) public String errorException(Exception e, HttpServletRequest httpServletRequest){ Map<String, Object> map = new HashMap<>(); httpServletRequest.setAttribute("javax.servlet.error.status_code", "500"); //設置狀態(tài)碼 map.put("code", "Data Null"); map.put("message", e.getMessage()); httpServletRequest.setAttribute("ceshi", map); //攜帶數據 return "/forward:/error"; //轉發(fā)到/error } }