使用SpringBootcss
自動配置原理?html
xxxAutoConfiguration自動給容器中配置組件xxxProperties 配置類來封裝配置文件的內容前端
能夠設置和資源有關的參數java
@ConfigurationProperties( prefix = "spring.resources", ignoreUnknownFields = false ) public class ResourceProperties implements ResourceLoaderAware
public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); } else { Integer cachePeriod = this.resourceProperties.getCachePeriod(); if (!registry.hasMappingForPattern("/webjars/**")) { this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(cachePeriod)); } String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(this.resourceProperties.getStaticLocations()).setCachePeriod(cachePeriod)); } } }
全部/webjars/** 請求,都去classpath:/META-INF/resources/webjars/ 找資源jquery
引入資源git
<!-- 引入jquery的webjar --> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.3.1</version> </dependency>
webjars:是指以jar的方式導入資源github
<img width="352" alt="wx20180518-205514 2x" src="https://user-images.githubuse...;>web
如訪問:http://localhost:8080/webjars/jquery/3.3.1/jquery.jsspring
"/**"訪問當前項目的任何資源(靜態資源的文件夾)express
"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/ "/"當前項目的根路徑
注意:目前SpringBoot下的resources是類路徑,並不是該路徑classpath:/resources/
localhost:8080/abc 默認去靜態文件夾裏面找abc
歡迎頁:靜態資源證下全部的index.html,被/**映射
@Bean public WelcomePageHandlerMapping welcomePageHandlerMapping( ResourceProperties resourceProperties) { return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(), this.mvcProperties.getStaticPathPattern()); }
全部的**/favicon.ico都是在靜態資源文件夾中找
@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); mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", faviconRequestHandler())); return mapping; } @Bean public ResourceHttpRequestHandler faviconRequestHandler() { ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler(); requestHandler .setLocations(this.resourceProperties.getFaviconLocations()); return requestHandler; } }
spring.resources.static-locations=classpath:/hello/,classpath:/meituan/
JSP、Welocity、Freemarker、Thymeleaf
Spring Boot推薦使用的模塊引擎Thymeleaf
<!-- 引入thymeleaf--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
使用Thymeleaf3
<properties> <thymeleaf.version>3.0.2.RELEASE</thymeleaf.version> <thymeleaf-layout-dialect.version>2.1.1</thymeleaf-layout-dialect.version> </properties>
增長thymeleaf-layout支持
<!--佈局功能的支持程序 thymeleaf3主程序 layout2以上版本--> <thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
@ConfigurationProperties(prefix = "spring.thymeleaf") public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8"); private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf("text/html"); //只要咱們把HTML頁面放在classpath:/templates/,thymeleaf就能夠自動渲染了 public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html";
只要咱們把HTML頁面放在classpath:/templates/,thymeleaf就能夠自動渲染了
<html xmlns:th="http://www.thymeleaf.org">
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>success</title> </head> <body> <h1>成功!</h1> <!--將div中的文本內容設置爲文本內指定的值--> <div th:text="${hello}">這是顯示歡迎信息</div> </body> </html>
th: 作生意html屬性,來替換原生屬性的值
Simple expressions: Variable Expressions: ${...} 1. 獲取對象的屬性、調用方法 2. 使用內置的基本對象 3. 內置工具對象 Selection Variable Expressions: *{...} 選擇表達式 補充:配合th:object使用 <div th:object="${session.user}"> <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p> <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p> <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p> </div> Message Expressions: #{...} 獲取國際化內容 Link URL Expressions: @{...} 定義URL連接 Fragment Expressions: ~{...} Literals 字面量 Text literals: 'one text' , 'Another one!' ,… Number literals: 0 , 34 , 3.0 , 12.3 ,… Boolean literals: true , false Null literal: null Literal tokens: one , sometext , main ,… Text operations: 文本操做 String concatenation: + Literal substitutions: |The name is ${name}| Arithmetic operations: 數學運算 Binary operators: + , - , * , / , % Minus sign (unary operator): - Boolean operations: 布爾運算 Binary operators: and , or Boolean negation (unary operator): ! , not Comparisons and equality: 比較運算 Comparators: > , < , >= , <= ( gt , lt , ge , le ) Equality operators: == , != ( eq , ne ) Conditional operators: 條件運算(三元運算) If-then: (if) ? (then) If-then-else: (if) ? (then) : (else) Default: (value) ?: (defaultvalue) Special tokens: Page 17 of 104 No-Operation: _
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(視圖解析器:根據方法的返回值獲得視圖對象View,視圖對象決定如何渲染,轉發或重定向)
ContentNegotiatingViewResolver
組合全部的視圖解析器- 如何定製:能夠本身給容器中添加一個視圖解析器,
ContentNegotiatingViewResolver
會自動將其組合起來- Support for serving static resources, including support for WebJars (see below).靜態資源文件夾路徑
Automatic registration of
Converter
,GenericConverter
,Formatter
beans.
- Converter 轉換器 頁面接收的數據類型轉換使用Converter
- Formatter 格式化器,例如2018.5.22=》Date
- 本身添加的Convertor、Formattor只需放在容器中便可
Support for
HttpMessageConverters
(see below).
HttpMessageConverter
SpringMVC用來轉換Http的請求和響應:User《=》jsonHttpMessageConverters
從容器中肯定;獲取全部的HttpmessageConverter- 本身給容器中添加HttpMessageConverter,只需將本身的組件註冊到容器中(@Bean @Component)
- Automatic registration of
MessageCodesResolver
(see below).定義錯誤代碼生成規則- Static
index.html
support.靜態首頁訪問- Custom
Favicon
support (see below).Automatic use of a
ConfigurableWebBindingInitializer
bean (see below).
- 能夠配置一個
ConfigurableWebBindingInitializer
來替換默認的(添加的容器中)ConfigurableWebBindingInitializer
的做用是初始化WebDataBinderorg.springframework.boot.autoconfigure.web : web的全部自動配置場景
If you want to keep Spring Boot MVC features, and you just want to add additional MVC configuration (interceptors, formatters, view controllers etc.) you can add your own
@Configuration
class of typeWebMvcConfigurerAdapter
, but without@EnableWebMvc
. If you wish to provide custom instances ofRequestMappingHandlerMapping
,RequestMappingHandlerAdapter
orExceptionHandlerExceptionResolver
you can declare aWebMvcRegistrationsAdapter
instance providing such components.If you want to take complete control of Spring MVC, you can add your own
@Configuration
annotated with@EnableWebMvc
.
<mvc:view-controller path="/hello" view-name="success" /> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/hello"/> <bean></bean> </mvc:interceptor> </mvc:interceptors>
編寫一個配置類(@Configuration),是WebMvcConfigurerAdapter類型,不能標註@EnableWebMvc
既保留了全部的自動配置,也能使用自定義的擴展配置
@Configuration public class MyMvcConfig extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/meituan").setViewName("success"); } }
原理:
@Configuration public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration @Configuration public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); @Autowired(required = false) public void setConfigurers(List<WebMvcConfigurer> configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers); //一個參考實現,將全部的WebMvcConfigurer相關配置得了來一塊兒調用 /** @Override public void addViewControllers(ViewControllerRegistry registry) { for (WebMvcConfigurer delegate : this.delegates) { delegate.addViewControllers(registry); } } */ } }
效果:SpringMVC的自動配置和自定義的擴展配置得了會起做用。
SpringBoot對SpringMVC的自動配置不須要了,全部都手動配置
此時須要在配置類中添加@EnableWebMvc,SpringBoot對SpringMVC的自動配置都失效
原理:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc
@Configuration public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
WebMvcAutoConfiguration要求沒有WebMvcConfigurationSupport註解的類存在在容器中,而添加了@EnableWebMvc的註解偏偏是個WebMvcConfigurationSupport的子類
@Configuration @ConditionalOnWebApplication @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class}) @ConditionalOnMissingBean({WebMvcConfigurationSupport.class}) @AutoConfigureOrder(-2147483638) @AutoConfigureAfter({DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class}) public class WebMvcAutoConfiguration
模式:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> <meta name="description" content="" /> <meta name="author" content="" /> <title>Signin Template for Bootstrap</title> <!-- Bootstrap core CSS --> <link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet" /> <!-- Custom styles for this template --> <link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet" /> </head> <body class="text-center"> <form class="form-signin" action="dashboard.html"> <img class="mb-4" th:src="@{asserts/img/bootstrap-solid.svg}" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72"> <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1> <label class="sr-only">Username</label> <input type="text" class="form-control" placeholder="Username" required="" autofocus=""> <label class="sr-only">Password</label> <input type="password" class="form-control" placeholder="Password" required=""> <div class="checkbox mb-3"> <label> <input type="checkbox" value="remember-me"> Remember me </label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button> <p class="mt-5 mb-3 text-muted">© 2017-2018</p> <a class="btn btn-sm">中文</a> <a class="btn btn-sm">English</a> </form> </body> </html>
採用thymeleaf的寫法,如<link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet" />
好處是更改項目目錄後不須要再修改代碼
在SpringMVC中須要如下幾個步驟
SpringBoot中的步驟爲:
SpringBoot自動配置好了管理國際化資源文件的組件
@ConfigurationProperties(prefix = "spring.messages") public class MessageSourceAutoConfiguration { private static final Resource[] NO_RESOURCES = {}; /** * Comma-separated list of basenames (essentially a fully-qualified classpath * location), each following the ResourceBundle convention with relaxed support for * slash based locations. If it doesn't contain a package qualifier (such as
*/ private String basename = "messages";//咱們的配置文件能夠直接放在類路徑下,叫messages.properties,此時不須要作任何配置便可生效;如自定義,能夠在application.properties中添加配置spring.messages.basename=i18n.login @Bean public MessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); if (StringUtils.hasText(this.basename)) { //設置國際化資源文件的基礎名(去掉語言國家代碼的) messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray( StringUtils.trimAllWhitespace(this.basename))); } if (this.encoding != null) { messageSource.setDefaultEncoding(this.encoding.name()); } messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale); messageSource.setCacheSeconds(this.cacheSeconds); messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat); return messageSource; } ```
在idea中能夠經過修改Other-Settings ---> Default Settings來修改全局配置
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> <meta name="description" content="" /> <meta name="author" content="" /> <title>Signin Template for Bootstrap</title> <!-- Bootstrap core CSS --> <link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet" /> <!-- Custom styles for this template --> <link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet" /> </head> <body class="text-center"> <form class="form-signin" action="dashboard.html"> <img class="mb-4" th:src="@{asserts/img/bootstrap-solid.svg}" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72"> <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1> <label class="sr-only" th:text="#{login.username}">Username</label> <input type="text" class="form-control" placeholder="Username" required="" autofocus=""> <label class="sr-only" th:text="#{login.password}">Password</label> <input type="password" class="form-control" placeholder="Password" required=""> <div class="checkbox mb-3"> <label> <input type="checkbox" value="remember-me" > [[#{login.remember}]] </label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button> <p class="mt-5 mb-3 text-muted">© 2017-2018</p> <a class="btn btn-sm">中文</a> <a class="btn btn-sm">English</a> </form> </body> </html>
效果:根據瀏覽器語言設置的信息切換了國際化;
原理:
國際化Locale(區域信息對象):LocaleResolver(獲取區域信息對象)
@Bean @ConditionalOnMissingBean @ConditionalOnProperty(prefix = "spring.mvc", name = "locale") public LocaleResolver localeResolver() { if (this.mvcProperties .getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) { return new FixedLocaleResolver(this.mvcProperties.getLocale()); } AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); localeResolver.setDefaultLocale(this.mvcProperties.getLocale()); return localeResolver; } 默認的區域信息解析器是根據請求頭帶來的區域信息獲取Locale進行國際化
自定義LocaleResolver
/** * 能夠在連接上攜帶區域信息 */ public class MylocaleResolver implements LocaleResolver { @Override public Locale resolveLocale(HttpServletRequest request) { String l = request.getParameter("l"); Locale locale = Locale.getDefault(); if (!StringUtils.isEmpty(l)) { String[] split = l.split("_"); locale = new Locale(split[0], split[1]); } return locale; } @Override public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { } } //在MyMvcConfig中添加組件 @Bean public LocaleResolver localeResolver() { return new MylocaleResolver(); }
開發期間模板引擎頁面修改之後,要實時生效
禁用模板引擎的緩存
#禁用緩存 spring.thymeleaf.cache=false
登錄錯誤消息提示
<p style="color:red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}" />
表單重複提交問題
//全部的WebMvcConfigurerAdapter組件都會共同起做用 @Bean//將組件註冊在容器中 public WebMvcConfigurerAdapter webMvcConfigurerAdapter() { WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("login"); registry.addViewController("/index.html").setViewName("login"); //增長一條視圖映射 registry.addViewController("/main.html").setViewName("dashboard"); } };
重定向:
@PostMapping(value = "/user/login") public String login(@RequestParam("username") String username, @RequestParam("password") String password, Map<String, Object> map) { if (!StringUtils.isEmpty(username) && password.equals("123456")) { //登錄成功,防止表單重複提交,能夠重定向到主頁 return "redirect:/main.html"; } else { map.put("msg", "用戶名密碼錯誤"); return "login"; } }
自定義攔截器
/** * 進行登錄檢查 */ public class LoginHandlerInterceptor implements HandlerInterceptor { //目標方法執行以前 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user = request.getSession().getAttribute("loginUser"); if (user == null) { //未登錄,返回登錄頁面 //獲取轉發器,進行轉發操做 request.setAttribute("msg", "沒有權限,請先登錄"); request.getRequestDispatcher("/index.html").forward(request, response); return false; } else { //已登錄 return true; } } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
攔截器的註冊
//全部的WebMvcConfigurerAdapter組件都會共同起做用 @Bean//將組件註冊在容器中 public WebMvcConfigurerAdapter webMvcConfigurerAdapter() { WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("login"); registry.addViewController("/index.html").setViewName("login"); registry.addViewController("/main.html").setViewName("dashboard"); } @Override public void addInterceptors(InterceptorRegistry registry) { //靜態資源:"*.css" "*.js" //SpringBoot已經作好了靜態資源映射,不須要排除 registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**") .excludePathPatterns("/index.html", "/user/login", "/"); } }; return adapter; }
實驗要求
RestfulCRUD:CRUD知足Rest風格
URI:/資源名稱/資源標識 HTTp請求方式區分對資源CRUD操做
普通CRUD(uri來區分操做) | 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/{id} 路徑變量 | GET |
來到添加頁面 | emp | GET |
添加員工 | emp | POST |
來到修改頁面(查出員工進行信息回顯) | emp/{id} | GET |
修改員工 | emp | PUT |
刪除員工 | emp/{id} | DELETE |
員工列表
thymeleaf公共頁面元素抽取
<div th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</div>
<div th:insert="~{footer :: copy}"></div>
<!--或者-->
<div th:insert="footer :: copy"></div>
~{templatename::selector} 模板名::選擇器
~{templatename::fragmentname} 模板名::片斷名
insert的功能片斷在div的標籤中
若是使用th:insert等屬性進行引入,能夠不用寫~{}
行內寫法能夠加上
三種引入功能片斷的th屬性: 1. th:insert 將公共片斷整個插入到聲明引入的元素中 2. th:replace 將聲明引入的片斷替換爲公共片斷 3. th:include 將被引入的片斷的內容包含進標籤中 區別:
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer>
引入方式
<body>
... <div th:insert="footer :: copy"></div> <div th:replace="footer :: copy"></div> <div th:include="footer :: copy"></div>
</body>
效果
<body>
... <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>
</body>
提交的數據格式不對問題:生日-日期 2017-12-12 2017/12/12 2017.12.12 日期的格式化:SpringMVC將頁面提交的值須要轉換爲指定的類型 2017-12-12---Date:類型轉換,格式化 默認使用/來分隔
spring.mvc.date-format=yyyy-MM-dd
添加頁面
<form>
<div class="form-group"> <label>LastName</label> <input type="text" class="form-control" placeholder="zhangsan"> </div> <div class="form-group"> <label>Email</label> <input type="email" class="form-control" placeholder="zhangsan@atguigu.com"> </div> <div class="form-group"> <label>Gender</label><br/> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="gender" value="1"> <label class="form-check-label">男</label> </div> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="gender" value="0"> <label class="form-check-label">女</label> </div> </div> <div class="form-group"> <label>department</label> <select class="form-control"> <option>1</option> <option>2</option> <option>3</option> <option>4</option> <option>5</option> </select> </div> <div class="form-group"> <label>Birth</label> <input type="text" class="form-control" placeholder="zhangsan"> </div> <button type="submit" class="btn btn-primary">添加</button>
</form>
提交的數據格式不對:生日:日期; 2017-12-12;2017/12/12;2017.12.12; 日期的格式化;SpringMVC將頁面提交的值須要轉換爲指定的類型; 2017-12-12---Date; 類型轉換,格式化; 默認日期是按照/的方式; ### 6.6 CRUD-員工修改 修改添加二合一表單
<!--須要區分是員工修改仍是添加;--> <form th:action="@{/emp}" method="post"> <!--發送put請求修改員工數據--> <!-- 一、SpringMVC中配置HiddenHttpMethodFilter;(SpringBoot自動配置好的) 二、頁面建立一個post表單 三、建立一個input項,name="_method";值就是咱們指定的請求方式 --> <input type="hidden" name="_method" value="put" th:if="${emp!=null}"/> <input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}"> <div class="form-group"> <label>LastName</label> <input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${emp.lastName}"> </div> <div class="form-group"> <label>Email</label> <input name="email" type="email" class="form-control" placeholder="zhangsan@atguigu.com" th:value="${emp!=null}?${emp.email}"> </div> <div class="form-group"> <label>Gender</label><br/> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp!=null}?${emp.gender==1}"> <label class="form-check-label">男</label> </div> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp!=null}?${emp.gender==0}"> <label class="form-check-label">女</label> </div> </div> <div class="form-group"> <label>department</label> <!--提交的是部門的id--> <select class="form-control" name="department.id"> <option th:selected="${emp!=null}?${dept.id == emp.department.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option> </select> </div> <div class="form-group"> <label>Birth</label> <input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}"> </div> <button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button> </form> ```
<tr th:each="emp:${emps}"> <td th:text="${emp.id}"></td> <td>[[${emp.lastName}]]</td> <td th:text="${emp.email}"></td> <td th:text="${emp.gender}==0?'女':'男'"></td> <td th:text="${emp.department.departmentName}"></td> <td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}"></td> <td> <a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">編輯</a> <button th:attr="del_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteBtn">刪除</button> </td> </tr> <script> $(".deleteBtn").click(function(){ //刪除當前員工的 $("#deleteEmpForm").attr("action",$(this).attr("del_uri")).submit(); return false; }); </script>
SpringBoot默認的錯誤處理機制
默認效果:
瀏覽器:返回一個默認的錯誤頁面
瀏覽器發送請求時的請求頭
若是是其餘客戶端訪問,默認返回一個json數據
原理:
參照 ErrorMvcAutoConfiguration
給容器中添加了如下組件
DefaultErrorAttributes
BasicErrorController @Controller @RequestMapping("${server.error.path:${error.path:/error}}") public class BasicErrorController extends AbstractErrorController { @RequestMapping(produces = "text/html")//將會產生html類型的數據 public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes( request, isIncludeStackTrace(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); //去哪一個頁面做爲錯誤頁面:包含頁面地址和頁面內容 ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView == null ? new ModelAndView("error", model) : modelAndView); } @RequestMapping @ResponseBody //產生json數據的 public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); HttpStatus status = getStatus(request); return new ResponseEntity<Map<String, Object>>(body, status); }
ErrorPageCustomizer @Value("${error.path:/error}") private String path = "/error";系統出現錯誤以後來到error請求進行處理(web.xml註冊的錯誤頁面規則)
DefaultErrorViewResolver @Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { ModelAndView modelAndView = resolve(String.valueOf(status), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) { //默認SpringBoot能夠去找到一個頁面 error/404 String errorViewName = "error/" + viewName; //模板引擎能夠解析這個頁面地址就用模板引擎解析 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders .getProvider(errorViewName, this.applicationContext); if (provider != null) { //模板引擎可用的狀況下返回到errorViewName指定的視圖地址 return new ModelAndView(errorViewName, model); } // 模板引擎不可用,就在靜態資源文件夾下找errorViewName對應的頁面error/404.html return resolveResource(errorViewName, model); }
步驟:
一旦系統出現4xx或5xx之類的錯誤,ErrorPageCustomizer
就會生效(定製錯誤的響應規則);就會來到/error請求;就會被BasicErrorController
處理;
響應頁面;去哪一個頁面是由DefaultErrorViewResolver
解析到的
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { for (ErrorViewResolver resolver : this.errorViewResolvers) { ModelAndView modelAndView = resolver.resolveErrorView(request, status, model); if (modelAndView != null) { return modelAndView; } } return null; }
如何定製錯誤響應
如何定製錯誤頁面
可使用4xx和5xx做爲錯誤頁面的文件名來匹配這種類型的全部錯誤,精確優先(優先尋找 狀態碼.html 頁面)
頁面能獲取的信息
timestamp 時間戳
status 狀態碼
error 錯誤提示
exception 異常
message 異常消息
errors JSR303數據校驗的錯誤都在這裏
如何定製錯誤的json數據
1)、自定義異常處理&返回定製json數據;
@ControllerAdvice public class MyExceptionHandler { @ResponseBody @ExceptionHandler(UserNotExistException.class) 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; } } //沒有自適應效果...
2)、轉發到/error進行自適應響應效果處理
@ExceptionHandler(UserNotExistException.class) public String handleException(Exception e, HttpServletRequest request){ Map<String,Object> map = new HashMap<>(); //傳入咱們本身的錯誤狀態碼 4xx 5xx,不然就不會進入定製錯誤頁面的解析流程 /**
.getAttribute("javax.servlet.error.status_code"); */ request.setAttribute("javax.servlet.error.status_code",500); map.put("code","user.notexist"); map.put("message",e.getMessage()); //轉發到/error return "forward:/error"; } ```
將咱們的定製數據攜帶出去;
出現錯誤之後,會來到/error請求,會被BasicErrorController處理,響應出去能夠獲取的數據是由getErrorAttributes獲得的(是AbstractErrorController(ErrorController)規定的方法);
一、徹底來編寫一個ErrorController的實現類【或者是編寫AbstractErrorController的子類】,放在容器中;
二、頁面上能用的數據,或者是json返回能用的數據都是經過errorAttributes.getErrorAttributes獲得;
容器中DefaultErrorAttributes.getErrorAttributes();默認進行數據處理的;
自定義ErrorAttributes
//給容器中加入咱們本身定義的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("company","atguigu"); return map; } }
最終的效果:響應是自適應的,能夠經過定製ErrorAttributes改變須要返回的內容,
SpringBoot默認使用Tomcat做爲嵌入式的Servlet容器;
server.port=8081 server.context-path=/crud server.tomcat.uri-encoding=UTF-8 //通用的Servlet容器設置 server.xxx //Tomcat的設置 server.tomcat.xxx
因爲SpringBoot默認是以jar包的方式啓動嵌入式的Servlet容器來啓動SpringBoot的web應用,沒有web.xml文件。
註冊三大組件用如下方式
ServletRegistrationBean
//註冊三大組件 @Bean public ServletRegistrationBean myServlet(){ ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet"); return registrationBean; }
FilterRegistrationBean
@Bean public FilterRegistrationBean myFilter(){ FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(new MyFilter()); registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet")); return registrationBean; }
ServletListenerRegistrationBean
@Bean public ServletListenerRegistrationBean myListener(){ ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener()); return registrationBean; }
SpringBoot幫咱們自動SpringMVC的時候,自動的註冊SpringMVC的前端控制器;DIspatcherServlet;
DispatcherServletAutoConfiguration中:
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public ServletRegistrationBean dispatcherServletRegistration( DispatcherServlet dispatcherServlet) { ServletRegistrationBean registration = new ServletRegistrationBean( dispatcherServlet, this.serverProperties.getServletMapping()); //默認攔截: 「/」 全部請求;包靜態資源,可是不攔截jsp請求; 「/*」會攔截jsp //能夠經過server.servletPath來修改SpringMVC前端控制器默認攔截的請求路徑 registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); registration.setLoadOnStartup( this.webMvcProperties.getServlet().getLoadOnStartup()); if (this.multipartConfig != null) { registration.setMultipartConfig(this.multipartConfig); } return registration; }
Jetty(長鏈接)
Undertow(不支持JSP,併發性能好)
默認支持:
Tomcat(默認使用)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> 引入web模塊默認就是使用嵌入式的Tomcat做爲Servlet容器; </dependency>
Jetty
<!-- 引入web模塊 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <artifactId>spring-boot-starter-tomcat</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions> </dependency> <!--引入其餘的Servlet容器--> <dependency> <artifactId>spring-boot-starter-jetty</artifactId> <groupId>org.springframework.boot</groupId> </dependency>
Undertow
<!-- 引入web模塊 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <artifactId>spring-boot-starter-tomcat</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions> </dependency> <!--引入其餘的Servlet容器--> <dependency> <artifactId>spring-boot-starter-undertow</artifactId> <groupId>org.springframework.boot</groupId> </dependency>
EmbeddedServletContainerAutoConfiguration:嵌入式的Servlet容器自動配置?
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @Configuration @ConditionalOnWebApplication @Import(BeanPostProcessorsRegistrar.class) //導入BeanPostProcessorsRegistrar:Spring註解版;給容器中導入一些組件 //導入了EmbeddedServletContainerCustomizerBeanPostProcessor: //後置處理器:bean初始化先後(建立完對象,還沒賦值賦值)執行初始化工做 public class EmbeddedServletContainerAutoConfiguration { @Configuration @ConditionalOnClass({ Servlet.class, Tomcat.class })//判斷當前是否引入了Tomcat依賴; @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)//判斷當前容器沒有用戶本身定義EmbeddedServletContainerFactory:嵌入式的Servlet容器工廠;做用:建立嵌入式的Servlet容器 public static class EmbeddedTomcat { @Bean public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() { return new TomcatEmbeddedServletContainerFactory(); } } /** * Nested configuration if Jetty is being used. */ @Configuration @ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class }) @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedJetty { @Bean public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() { return new JettyEmbeddedServletContainerFactory(); } } /** * Nested configuration if Undertow is being used. */ @Configuration @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class }) @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedUndertow { @Bean public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() { return new UndertowEmbeddedServletContainerFactory(); } }
1)EmbeddedServletContainerFactory(嵌入式Servlet容器工廠)
public interface EmbeddedServletContainerFactory { //獲取嵌入式的Servlet容器 EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers); }
2)EmbeddedServletContainer:(嵌入式的Servlet容器)
3)以TomcatEmbeddedServletContainerFactory爲例
@Override public EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers) { //建立一個Tomcat Tomcat tomcat = new Tomcat(); //配置Tomcat的基本環節 File baseDir = (this.baseDirectory != null ? this.baseDirectory : createTempDir("tomcat")); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); configureEngine(tomcat.getEngine()); for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } prepareContext(tomcat.getHost(), initializers); //將配置好的Tomcat傳入進去,返回一個EmbeddedServletContainer;而且啓動Tomcat服務器 return getTomcatEmbeddedServletContainer(tomcat); }
4)咱們對嵌入式容器的配置修改是怎麼生效?
ServerProperties、EmbeddedServletContainerCustomizer
EmbeddedServletContainerCustomizer:定製器幫咱們修改了Servlet容器的配置?
怎麼修改的原理?
5)容器中導入了EmbeddedServletContainerCustomizerBeanPostProcessor
//初始化以前 @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { //若是當前初始化的是一個ConfigurableEmbeddedServletContainer類型的組件 if (bean instanceof ConfigurableEmbeddedServletContainer) { // postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean); } return bean; } private void postProcessBeforeInitialization( ConfigurableEmbeddedServletContainer bean) { //獲取全部的定製器,調用每個定製器的customize方法來給Servlet容器進行屬性賦值; for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) { customizer.customize(bean); } } private Collection<EmbeddedServletContainerCustomizer> getCustomizers() { if (this.customizers == null) { // Look up does not include the parent context this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>( this.beanFactory //從容器中獲取全部這個類型的組件:EmbeddedServletContainerCustomizer //定製Servlet容器,給容器中能夠添加一個EmbeddedServletContainerCustomizer類型的組件 .getBeansOfType(EmbeddedServletContainerCustomizer.class, false, false) .values()); Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE); this.customizers = Collections.unmodifiableList(this.customizers); } return this.customizers; } ServerProperties也是定製器
步驟:
何時建立嵌入式的Servlet容器工廠?何時獲取嵌入式的Servlet容器並啓動Tomcat;
獲取嵌入式的Servlet容器工廠:
1)SpringBoot應用啓動運行run方法
2)refreshContext(context);SpringBoot刷新IOC容器【建立IOC容器對象,並初始化容器,建立容器中的每個組件】;若是是web應用建立AnnotationConfigEmbeddedWebApplicationContext,不然:AnnotationConfigApplicationContext
3)refresh(context);刷新剛纔建立好的ioc容器;
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }
4)onRefresh(); web的ioc容器重寫了onRefresh方法
5)webioc容器會建立嵌入式的Servlet容器;createEmbeddedServletContainer();
6)獲取嵌入式的Servlet容器工廠:
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
從ioc容器中獲取EmbeddedServletContainerFactory 組件;TomcatEmbeddedServletContainerFactory建立對象,後置處理器一看是這個對象,就獲取全部的定製器來先定製Servlet容器的相關配置;
7)使用容器工廠獲取嵌入式的Servlet容器:this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());
8)嵌入式的Servlet容器建立對象並啓動Servlet容器;
先啓動嵌入式的Servlet容器,再將ioc容器中剩下沒有建立出的對象獲取出來;
==IOC容器啓動建立嵌入式的Servlet容器==