SpringBoot之旅第四篇-web開發
一、引言
有了自動配置,springboot使web開發變得簡單,這個在springboot之旅中的第一篇中就有體現,實際的開發中當然不會這麼簡單,很多時候我們都需要自己去定製一些東西。web開發的東西比較多, 我們先掌握一些必要知識點,剩下的就是CRUD開發。
二、靜態資源的對映規則
現在大部分公司都是前後端分離的開發模式,一般作為後臺開發不用關心前端,只需要提供相應介面,但是有關前端的知識我們最好還是能基本掌握一些。我們先了一套bootstrap框架,然後開始進行開發。
在之前的web開發中,在main目錄下面會有webapp資料夾,我們將所有的靜態資源放在裡面,但是springboot的預設生成中並沒有這個資料夾,那麼springboot是怎麼對映靜態資源。
ctrl+N快捷鍵,找到WebMvcAutoConfiguration類,再找到裡面的addResourceHandlers 方法
public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); return; } Duration cachePeriod = this.resourceProperties.getCache().getPeriod(); CacheControl cacheControl = this.resourceProperties.getCache() .getCachecontrol().toHttpCacheControl(); //webjar形式 if (!registry.hasMappingForPattern("/webjars/**")) { customizeResourceHandlerRegistration(registry .addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/") .setCachePeriod(getSeconds(cachePeriod)) .setCacheControl(cacheControl)); } //匹配/** String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { customizeResourceHandlerRegistration( registry.addResourceHandler(staticPathPattern) .addResourceLocations(getResourceLocations( //對映的資原始檔夾 this.resourceProperties.getStaticLocations())) .setCachePeriod(getSeconds(cachePeriod)) .setCacheControl(cacheControl)); } }
2.1 webjars
這裡的程式碼告訴我們:如果是訪問/webjars/**下的請求 ,都去 classpath:/META-INF/resources/webjars/ 找資源。webjars是指以jar包的方式引入靜態資源。開啟https://www.webjars.org/ ,可以找到我們前端開發常用的一些元件,我們選擇相應的版本,例:
<dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.3.1-1</version> </dependency>
引入後可以看到jquer檔案被引入了:
如果順利的話,此時訪問http://localhost:8080/webjars/jquery/3.3.1-1/jquery.js可以得到檔案,結果如下:
2.2 自己的靜態檔案
另外當訪問當前專案的任何資源,都去(靜態資源的資料夾)找對映,資原始檔夾是一個數組,包括:
"classpath:/META-INF/resources/", "classpath:/resources/","classpath:/static/", "classpath:/public/" ,
"/":當前專案的根路徑。只要將靜態檔案放入其中,那麼springboot就能找到。
2.3 首頁
在訪問"/**",會去找靜態資原始檔夾下的所有index.html頁面。
2.4 圖示
所有的 **/訪問都是靜態資原始檔下找favicon.ico。
我們將一些靜態檔案放在static下,並將index.html放入public資料夾下,如圖:
訪問http://localhost:8080/index.html ,可得到正確返回
三、模板引擎
模板引擎有很多,如JSP、Velocity、Freemarker、Thymeleaf,springboot推薦的是Thymeleaf,那我們就來簡單看看Thymeleaf語法。匯入starter:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
進入之後可以看到預設版本,我們也可以改成自己需要的版本。
<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version> <!-- 佈局功能的支援程式 thymeleaf3主程式 layout2以上版本 --> <!-- thymeleaf2 layout1--> <thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
3.1 Thymeleaf使用
通過原始碼我們知道,只要我們把HTML頁面放在classpath:/templates/,thymeleaf就能自動渲染
@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"); public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html";
我們可以去官網檢視教程,這裡只是簡單的進行介紹,主要步驟
第一步:匯入名稱空間,匯入之後會有相應提示
<html lang="en" xmlns:th="http://www.thymeleaf.org">
第二步:使用語法
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>成功!</h1> <!--th:text 將div裡面的文字內容設定為 --> <div><th th:text="${hello}"></th>這是顯示歡迎資訊</div> </body> </html>
更具體的使用方法,可以去檢視官網教程,這種如果沒有使用到的話不建議花太多時間去學,很多公司都是前後端分離,即使不是前後端分離,也有很多前端框架給我們使用。這些可以再我們使用的時候再去學習,速度也是很快的。
四、SpringMVC自動配置
4.1 自動配置
springboot預設將為我們配置如下一些SpringMvc的必要元件:
-
必要的ViewResolver(檢視解析器:根據方法的返回值得到檢視物件(View)),如ContentNegotiatingViewResolver和
BeanNameViewResolver
。 -
將必要的
Converter
,GenericConverter
,Formatter
等bean註冊到ioc容器中。 -
添加了一系列的
HttpMessageConverters
以便支援對web請求和相應的型別轉換。 -
自動配置和註冊
MessageCodesResolver
任何時候,我們對預設提供的元件設定不滿意,都可以註冊新的同類型的bean定義來替換,web的所有自動場景都在org.springframework.boot.autoconfigure.web包中,我們可以參照進行配置。
當然完全靠自動配置在實際開發時不夠的,我們經常需要自己配置一些東西,比如攔截器,檢視對映規則。
4.2 擴充套件配置
在sprinboot2.0之前 配置類繼承WebMvcConfigurerAdapter,但是現在這個方法已經過時,現在可以使用兩種方式,繼承WebMvcConfigurer介面或者繼承WebMvcConfigurationSupport類,推薦使用的是WebMvcConfigurationSupport。
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/yuan").setViewName("success"); } }
這段程式碼就實現了自定義的檢視對映。上面這種寫法使SpringMVC的自動配置和我們的擴充套件配置都會起作用
我們甚至可以全面接管springmvc,只要在配置類中增加@EnableWebMv註解,這樣所有的SpringMVC的自動配置都失效了。當然,一般情況下我們不會這麼做。
五、登陸
web系統一般少不了登入頁面,我們先設定預設頁面為登入頁。
registry.addViewController("/").setViewName("login"); registry.addViewController("/index.html").setViewName("login");
5.1 登入方法
具體登入html的程式碼就不貼了,可以下載原始碼檢視,新建controller
@Controller public class LoginController { @PostMapping(value = "/user/login") public String login(@RequestParam("username") String username, @RequestParam("password") String password, Map<String,Object> map, HttpSession httpSession){ if(!StringUtils.isEmpty(username)&& "123456".equals(password)){ //設定session httpSession.setAttribute("loginUser",username); //重定向到主頁 return "redirect:/main.html"; }else { map.put("msg","使用者名稱密碼錯誤"); return "login"; } } }
5.2 登入攔截器
登入操作完成之後,為了對每個頁面進行登入驗證,我們還需要設定登入攔截器。先建立登入攔截器
@Component public class LoginHandlerInterceptor implements HandlerInterceptor { 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; } } }
然後再加入配置
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private LoginHandlerInterceptor loginHandlerInterceptor; public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginHandlerInterceptor).addPathPatterns("/**") .excludePathPatterns("/index.html","/","/user/login"); } }
這樣在訪問其他頁面時都會進行登入攔截操作
六、錯誤處理機制
在進行開發時,錯誤處理是非常重要的,不管是直接顯示給使用者,或者返回給前端,都需要儘量友好和清晰。
6.1 預設的錯誤處理機制
springboot有自身的預設錯誤處理機制,分為兩種
第一種:瀏覽器,瀏覽器會返回一個預設的錯誤頁面,如:
第二種:客戶端,客戶端預設返回的是一個響應一個json資料
如果我們用postman訪問,則返回:
6.2 定製錯誤響應
定製錯誤響應也分為兩種,一種是定製錯誤頁面,第二種是定製錯誤json資料。
6.2.1 定製錯誤頁面
如果我們想要展示更加詳細的資訊,就將頁面放在模板引擎資料夾下,路徑名為 error/狀態碼,【將錯誤頁面命名為錯誤狀態碼.html 放在模板引擎資料夾裡面的 error資料夾下】,發生此狀態碼的錯誤就會來到 對應的頁面。在這個頁面我們可以獲取到一些錯誤資訊,如:
-
timestamp:時間戳
-
status:狀態碼
-
error:錯誤提示
-
exception:異常物件
-
message:異常訊息
-
errors:JSR303資料校驗的錯誤都在這裡
我們可以根據這些錯誤資訊來展示錯誤,一般不需要這麼做,丟擲的錯誤不應該讓使用者去分析,我們只需要返回靜態頁面即可,返回錯誤靜態頁面是做法也是一樣的,只是我們不用將檔案放在模板引擎資料夾下。
6.2.2 定製錯誤的json資料
在實際的開發中我們會對我們的錯誤碼進行規範處理,根據錯誤會返回相應的錯誤碼,所以我們會自己進行json資料包裝處理。
@ControllerAdvice public class GlobalDefaultExceptionHandler { @ExceptionHandler(value = RequestException.class) public String requestExceptionHandler(RequestException 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()); //轉發到/error return "forward:/error"; } }
七、配置嵌入式Servlet容器
springboot預設使用Tomcat作為嵌入式的Servlet容器,我們既可以修改Tomcat的一些屬性配置,也可以使用其他的Servlet容器,我們這篇就來學習嵌入式Servlet容器的配置。
7.1 、定製和修改Servlet容器的相關配置
servlet的配置類為ServerProperties,進入程式碼可以看到server能夠配置的屬性,我們可以對此進行修改。
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) public class ServerProperties {
我們既可以修改通用的Servlet容器設定,如:
server: port: 8089
也可以修改某一種容器的配置,如:
server: tomcat: uri-encoding: utf-8
7.2 、註冊Servlet三大元件【Servlet、Filter、Listener】
註冊三大元件用以下方式:
Servlet:ServletRegistrationBean
建立一個MyServlet類:
public class MyServlet extends HttpServlet { @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req,resp); } @Override public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("hello MyServlet"); } }
注入容器:
@Bean public ServletRegistrationBean myServlet(){ ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet"); return registrationBean; }
Filter:FilterRegistrationBean
建立MyFilter:
public class MyFilter implements 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("My filter process"); filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy() { } }
注入容器:
@Bean public FilterRegistrationBean myFilter(){ FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(new MyFilter()); registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet")); return registrationBean; }
Listener:ServletListenerRegistrationBean
建立MyListener:
public class MyListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { System.out.println("contextInitialized ...web啟動"); } @Override public void contextDestroyed(ServletContextEvent sce) { System.out.println("contextDestroyed ...web銷燬"); } }
注入容器
@Bean public ServletListenerRegistrationBean myListener(){ ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener()); return registrationBean; }
7.3、 替換為其他嵌入式Servlet容器
springboot預設為tomcat容器,要替換其他容器就必須修改pom依賴
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>
以上是我們在web開發需要先掌握的一些基本技術,有了這些基本知識之後,我們就可以進行CRUD開發,當然在實際的開發中,不管是登入攔截還是錯誤處理都比這個要複雜,我們以後再詳