【SpringBoot實戰】實現WEB的常用功能
- 2022 年 4 月 25 日
- 筆記
- JAVA, springboot, SpringMVC
前言
通常在 Web 開發中,會涉及靜態資源的訪問支援、視圖解析器的配置、轉換器和格式化器的訂製、文件上傳下載等功能,甚至還需要考慮到與Web伺服器關聯的 Servlet相關組件的訂製。Spring Boot框架支援整合一些常用Web框架,從而實現Web開發,並默認支援Web開發中的一些通用功能。本文將對Spring Boot實現Web開發中涉及的三大組件Servlet、Filter、Listener以及文件上傳下載功能以及打包部署進行實現。
SpringMVC整合支援
為了實現並簡化Web開發,Spring Boot為一些常用的Web開發框架提供了整合支援,例如 Spring MVC、Spring WebFlux等框架。使用Spring Boot進行Web開發時,只需要在項目中引入對應Web開發框架的依賴啟動器即可。
Spring MVC自動配置
在Spring Boot項目中,一旦引入了Web依賴啟動器spring-boot-starter-web,那麼SpringBoot整合 Spring MVC 框架默認實現的一些xxxAutoConfiguration自動配置類就會自動生效,幾乎可以在無任何額外配置的情況下進行Web開發。Spring Boot為整合Spring MVC 框架實現Web開發,主要提供了以下自動化配置的功能特性。
(1)內置了兩個視圖解析器:ContentNegotatingViewResolver和BeanNameViewReso
(2)支援靜態資源以及WebJars。
(3)自動註冊了轉換器和格式化器。
(4)支援Http消息轉換器。
(5)自動註冊了消息程式碼解析器。
(6)支援靜態項目首頁index.html。
(7)支援訂製應用圖標favicon.ico。
(8)自動初始化Web數據綁定器ConfigurableWebBindinglnitializer。
Spring Boot 整合 Spring MVC進行Web開發時提供了很多默認配置,而且大多數時候使用默認配置即可滿足開發需求。例如,Spring Boot整合Spring MVC進行Web開發時,不需要外配置視圖解析器。
Spring MVC功能擴展實現
Spring Boot 整合 Spring MVC進行Web開發時提供了很多的自動化配置,但在實際開發中還需要開發者對一些功能進行擴展實現。下面我們通過一個具體的案例講解 Spring Boot整合Spring MVC框架實現Web開發的擴展功能。
項目基礎環境搭建
使用Spring Inifializr方式創建名稱為springboot02的Spring Boot項目,並導入Web依賴和Thymeleaf依賴。
讓後我們啟動該項目訪問//localhost:8080/ 可以看到下面的介面就表示訪問成功,也代表我們項目創建成功。
我們在resources下的templates包里創建一個登錄介面login.html
<!DOCTYPE html>
<html>
<head>
<title>login</title>
</head>
<body>
<form>
<input type="text" name="username">
<input type="password" name="password">
<input type="submit" value="submit">
</form>
</body>
</html>
最後在com.hjk包下創建controller包並創建LoginController類
package com.hjk.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.Calendar;
@Controller
public class LoginController {
/**
* 獲取並封裝當前年份跳轉到登錄頁login.html
*/
@GetMapping("/toLoginPage")
public String toLoginPage(Model model){
model.addAttribute("currentYear", Calendar.getInstance().get(Calendar.YEAR));
return "login";
}
}
功能擴展實現
接下來使用Spring Boot 整合Spring MVC進行Web開發,實現簡單的頁面跳轉功能,這裡我們將使用Spring Boot提供的WebMvcConfigurer介面編寫自定義配置,並對Web功能進行適當擴展。我們在這裡分別演示視圖管理器和攔截器的實現。
註冊視圖管理器
在springboot項目的 com.hjk下創建config包並創建一個實現WebMvcConfigurer 介面的配置類 MyMVCconfig,用於對 MVC框架功能進行擴展
package com.hjk.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
@Configuration
public class MyMVCconfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry){
registry.addViewController("/toLoginPage").setViewName("login");
registry.addViewController("/login.html").setViewName("login");
}
}
- MMVCconig實現了介面 WebMvcConigurer 的addViewControllerse(ViewControllerRegistry registry)方法。在addViewControllers()方法內部,使用ViewControllerRegistry的 addviewController()方法分別定義了「tologinPage」和「login.html」的請求控制,並使setViewName(“login”)方法將路徑映射為login.html頁面。
訂製完MVC的視圖管理功能後, - 就可以進行效果測試了。為了演示這種訂製效果,重啟chapter05項目,項目啟動成功態,在瀏覽器上分別訪問//localhost:8080/toLoginPage和//localhost:8080/login.htm 都可以訪問login.html頁面
- 使用WebMvcConfigurer介面定義的用戶請求控制方法也實現了用戶請求控制跳轉的效果,相比於傳統的請求處理方法而言,這種方法更加簡潔、直觀和方便。同時也可以看出,使用這種方式無法獲取後台處理的數據。需要說明的是,使用WebMvcConfigurer 介面中的addViewControllers(ViewControllelRegistry registry)方法訂製視圖控制,只適合較為簡單的無參數視圖Get方式請求,有參數或需要業務處理的跳轉需求,最好還是採用傳統方式處理請求。
註冊自定義攔截器
WebMvcConfigurer介面提供了許多MVC開發相關方法,添加攔截器方法addInterceptors(),添加格式化的器的方法addFormatters()我們這裡實現攔截器的方法。
我們在config包下創建一個自定義攔截器類MyInterceptor,程式碼如下。
package com.hjk.config;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Calendar;
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
Object loginUser = request.getSession().getAttribute("loginUser");
if (uri.startsWith("/admin")&& null==loginUser){
try {
response.sendRedirect("/toLoginPage");
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("攔截器攔截");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
- 自定義攔截器類Mylnterceptor實現了HandlerInterceptor介面。在preHandle()方法方法中,如果用戶請求以「/admin」開頭,即訪問如//localhost:8080/admin 的地址則判斷用戶是否登錄,如果沒有登錄,則重定向到「hoLoginPage」請求對應的登錄頁面。
- 在postHandle()方法中,在控制台列印攔截器攔截。
然後在config包下自定義配置類MyMVCconfig中,重寫addlnterceptors()方法註冊自定義的攔截器。添加以下程式碼。
@Autowired
private MyInterceptor myInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor).addPathPatterns("/**").excludePathPatterns("/login.html");
}
- 先使用@Autowired註解引入自定義的 Mylnterceptor攔截器組件,然後重寫其中的 addinterceptors()方法註冊自定義的攔截器。在註冊自定義攔截器時,使用addPathPatterns(“/**)方法攔截所有路徑請求,excludePathPatterns(“/login.htm”)方法對「login.html」路徑的請求進行了放行處理。
測試:我們可以訪問//localhost:8080/admin 可以發現它重定向大toLoginPage介面了。
Spring整合Servlet三大組件
在這裡我們使用組件註冊方式對Servlet、Filter、Listener三大組件進行整合,我們只需要將自定義的組件通過ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean類註冊到容器中即可。
使用註冊方式整合
使用組件註冊方式整合Servlet
我們在com.hjk包下創建servletComponent的包,在該包下創建MyServlet類並繼承HttpServlet類。
package com.hjk.servletCompont;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("hello MyServlet");
}
}
- @Component註解將MyServlet類作為組件注入Spring容器。MySeret類繼承自HttpServlet,通過HttpServletResponse對象向頁面輸出「hello MyServlet」。
創建 Servlet組件配置類。在項目com.hjk.confg包下創建一個Servlet組件配置類servietConfig,用來對 Servlet相關組件進行註冊,
package com.hjk.config;
import com.hjk.servletCompont.MyServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ServletConfig {
@Bean
public ServletRegistrationBean getServlet(MyServlet myServlet){
ServletRegistrationBean registrationBean = new ServletRegistrationBean(myServlet, "/myServlet");
return registrationBean;
}
}
- 使用@Configuration 註解將ServletConfig標註為配置類,ServletConfig類內部的 getServlet()方法用於註冊自定義的MyServlet,並返回 ServletRegistrationBean類型的Bean對象。
測試:項目啟動成功後,在瀏覽器上訪問「//localhost:8080/myServlet“myServlet並正常顯示數據,說明 Spring Boot成功整合Servlet組件。
使用組件註冊方式整合Filter
在servletCompont包下創建一個MyFilter類並實現Filter介面,這個Filter的包別導錯了
package com.hjk.servletCompont;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;
@Component
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("hello MyFilter");
}
@Override
public void destroy() {
}
}
在config包下的ServletConfig類中進行註冊,即在該類中添加方法。
@Bean
public FilterRegistrationBean getFilter(MyFilter myFilter){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/toLogin","/myFilter"));
return filterRegistrationBean;
}
- 使用 setUrilPatterns(Arrays.asList(“/toLoginPage”,/myFilter’)方法定義了過濾的請求路徑
「/toLoginPage」和「/myFilter」,同時使用@Bean 註解將當前組裝好的FilterRegistrationBea對象作為Bean組件返回。
測試:在瀏覽器上訪問「//localhost:8080/myFilter」查看控制台列印效果(由於沒有編寫對應路徑的請求處理方法,所以瀏覽器會出現404 錯誤頁面,這裡重點關注控制台即可),瀏覽器訪問「//localhost:8080/
myFilter」時,控制台列印出了自定義 Filter中定義 圖5-6 使用組件註冊方式整合Filter的運行結果的輸出語句「hello MyFilter」,這也就說明Spring Boot 整合自定義Filter 組件成功。
使用組件註冊方式整合 Listener
(1)創建自定義Listener類。在com.itheima.senleiComponent包下創建一個類MyListener實現ServletContextListener介面
package com.hjk.servletCompont;
import org.springframework.stereotype.Component;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
@Component
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("contextnitialized...");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("contextDestroyed...");
}
}
在servletConfig添加註冊
@Bean
public ServletListenerRegistrationBean getServletListener(MyListener myListener){
ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean(myListener);
return servletListenerRegistrationBean;
}
需要說明的是,Servlet 容器提供了很多 Listener 介面,例如 ServletRequestListener、ritpSessionListener、ServletContextListener等,我們在自定義Listener類時要根據自身需求選擇實現對應介面即可。
測試:程式啟動成功後,控制台會列印出自定義Listener組件中定義的輸出語句「contextlnitialized..」。單擊圖中的【Exit】按鈕關閉當前項目(注意,如果直接單擊紅色按鈕會強制關閉程式,瀏覽器就無法列印關閉監聽資訊),再次查看控制台列印效果。
程式成功關閉後,控制台列印出了自定義Listener組件中定義的輸出語句「contextDestroyed..」。通過效果演示,說明了Spring Boot整合自定義Listener組件成功。
文件上傳與下載
開發web應用時,文件上傳是很常見的一個需求,瀏覽器通過表單形式將文件以流的形式傳遞給伺服器,伺服器在對上傳的數據解析處理。
文件上傳
編寫上傳表單介面
這個表單介面名為upload.html,在templates文件夾下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件上傳</title>
</head>
<body>
<div style="text-align: center">
<form action="/uploadFile" method="post" enctype="multipart/form-data">
上傳:<input type="file" name="filename"/>
<input type="submit" value="submit"/>
</form>
</div>
</body>
</html>
我們通過表單
我們通過提交的地址也應該清楚,我們肯定會寫一個uploadFile的控制器。
添加文件上傳的相關配置
我們在application.properties文件中添加配置,上傳文件的大小限制。
## 文件最大限制為10mb,默認為1mb
spring.servlet.multipart.max-file-size=1MB
- 如果文件超過限制大小,會報錯。
編寫控制器
我們在com.hjk.controller包下船艦一個名為FileController的類,用於實現文件上傳的控制器。
我們這個文件上傳只是實現一個簡單的文件上傳,並沒有考慮上傳文件重名的情況,實際上重名的話會覆蓋之前的文件。要實現文件上傳,我們肯定要給它一個唯一名稱這個可以使用uuid實現,這裡也沒考慮文件存放位置問題,都是我自己把地址寫死了,這裡我們就不實現了。
實現歷程:寫這個控制器的時候,我的程式碼是正確的,前端文件也能提交,但是後端獲取的文件就是null,我也看了很多部落格,有的說是沒有註冊multipartResolver這個Bean,有的說是版本問題等等,但是都沒有解決。最後一個不經意的小細節導致了我這次的程式碼不能獲取到文件。那就是我們有在(@RequestParam("filename") MultipartFile file)
前面加@RequestParam這個註解。反正我的這個是加上之後就能用了,我的這個springboot版本是2.6.6.至於真正原因現在不想思考了,等以後遇到再改吧。
-
@RequestPara(“filename”)必須獲取參數名為filename的file參數
-
@RequestParam()默認為必傳屬性,可以通過@RequestParam(required = false)設置為非必傳。因為required值默認是true,所以默認必傳
-
@RequestParam(“filename”)或者@RequestParam(value = “filename”)指定參數名
-
@RequestParam(defaultValue = “0”)指定參數默認值
package com.hjk.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@Controller
public class FileController {
@GetMapping("/toUpload")
public String toUpload(){
return "upload";
}
@RequestMapping(value = "/uploadFile",method = RequestMethod.POST)
public String uploadFile(@RequestParam("filename") MultipartFile file){
String filename = file.getOriginalFilename();
String dirPath = "D:/file/";
File filePath = new File(dirPath);
if (!filePath.exists()){
filePath.mkdir();
}
try {
file.transferTo(new File(dirPath+filename));
} catch (IOException e) {
e.printStackTrace();
}
return "upload";
}
}
在這裡我們提交三張圖片用於下面的文件下載
文件下載
文件下載很多框架都沒有進行封裝處理,不同的瀏覽器解析處理不同,有可能出現亂碼情況。
在添加完依賴之後我們創建一個名為filedownload.html的html,一會用於編寫下載介面。
添加依賴
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
下載處理控制器
我們還是再FileController類里添加下載處理方法。直接在裡面添加就行。
@GetMapping("/toDownload")
public String toDownload(){
return "filedownload";
}
@GetMapping("/download")
public ResponseEntity<byte[]> fileDownload(String filename){
//指定下載地址文件路徑
String dirPath = "D:/file/";
//創建文件下載對象
File file = new File(dirPath + File.separator + filename);
//設置響應頭
HttpHeaders httpHeaders = new HttpHeaders();
//通知瀏覽器以下載方式打開
httpHeaders.setContentDispositionFormData("attachment",filename);
//定義以流的形式下載返迴文件
httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
try {
return new ResponseEntity<>(FileUtils.readFileToByteArray(file),httpHeaders, HttpStatus.OK);
} catch (IOException e) {
e.printStackTrace();
return new ResponseEntity<byte[]>(e.getMessage().getBytes(), HttpStatus.EXPECTATION_FAILED);
}
}
編寫前端程式碼
<!DOCTYPE html>
<html lang="en" xmlns:th="//www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>文件下載</title>
</head>
<body>
<div style="margin-bottom: 10px">文件下載列表</div>
<table>
<tr>
<td>0000001.jpg</td>
<td><a th:href="@{/download(filename='0000001.jpg')}">下載文件</a> </td>
</tr>
<tr>
<td>0000002.jpg</td>
<td><a th:href="@{/download(filename='0000002.jpg')}">下載文件</a> </td>
</tr>
<tr>
<td>0000003.jpg</td>
<td><a th:href="@{/download(filename='0000003.jpg')}">下載文件</a> </td>
</tr>
</table>
</body>
</html>
我們這次使用了thymeleaf寫前端程式碼。
實際上我們可能會遇到下載中文文件的問題,那樣可能會亂碼。
我么在這裡寫一個解決中文亂碼的例子。例如:我把0000001.jpg改為”你好jpg”再重新部署下載,會發現名字為_.jpg
下面我們直接在我們在fileController類的裡面加一個getFileName方法,並修改fileDownload方法上做修改。
public String getFileName(HttpServletRequest request,String filename) throws Exception {
String[] IEBrowserKeyWords = {"MSIE","Trident","Edge"};
String userAgent = request.getHeader("User-Agent");
for (String ieBrowserKeyWord : IEBrowserKeyWords) {
if (userAgent.contains(ieBrowserKeyWord)){
return URLEncoder.encode(filename,"UTF-8").replace("+"," ");
}
}
return new String(filename.getBytes(StandardCharsets.UTF_8),"ISO-8859-1");
}
@GetMapping("/download")
public ResponseEntity<byte[]> fileDownload(HttpServletRequest request,String filename) throws Exception {
//指定下載地址文件路徑
String dirPath = "D:/file/";
//創建文件下載對象
File file = new File(dirPath + File.separator + filename);
//設置響應頭
HttpHeaders httpHeaders = new HttpHeaders();
//通知瀏覽器下載七千及性能轉碼
filename = getFileName(request,filename);
//通知瀏覽器以下載方式打開
httpHeaders.setContentDispositionFormData("attachment",filename);
//定義以流的形式下載返迴文件
httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
try {
return new ResponseEntity<>(FileUtils.readFileToByteArray(file),httpHeaders, HttpStatus.OK);
} catch (IOException e) {
e.printStackTrace();
return new ResponseEntity<byte[]>(e.getMessage().getBytes(), HttpStatus.EXPECTATION_FAILED);
}
}
SpringBoot的打包部署
springboot使用的嵌入式Servlet容器,所以默認是以jar包打包的。也可以進行war包打包,但是需要進行一些配置。
jar包形式打包
我們在創建springboot項目是默認會給我們導入maven的打包插件,如果沒有我們手動加上即可。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
雙擊package等待即可
等待完成,可以看到打包時間,存放jar包位置等資訊。我們也可以在target包下查看打成的jar包。
啟動jar包
我們可以在關閉已啟動的springboot項目後,在idea控制台輸入命令啟動。
java -jar target\springboot02-0.0.1-SNAPSHOT.jar
我們也可以在系統自帶的終端窗口啟動
war包形式打包
我們首先要把默認打包方式修改為war包
<name>springboot02</name>
<description>Demo project for Spring Boot</description>
<packaging>war</packaging>
<properties>
<java.version>1.8</java.version>
</properties>
導入外部Tomcat伺服器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
打開啟動類,繼承springbootServletInitializer類
package com.hjk;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@ServletComponentScan
@SpringBootApplication
public class Springboot02Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Springboot02Application.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Springboot02Application.class);
}
}
然後就和jar包方式一樣了,雙擊package,等待打包完成。
war包的部署
war包的部署相比於jar包比較麻煩,我們需要外部的伺服器,我們需要把war包複製到tomcat安裝目錄下的webapps目錄中,執行目錄里的startup.bat命令啟動war包,這樣我們就完成了。
總結
我們對MVC進行了功能擴展和訂製、servlet三大組件訂製、文件上傳和下載、以及兩種方式打包部署。