慢慢來比較快,虛心學技術
一、SpringMVC配置的替代方案
Ⅰ、註冊Filter
SpingMVC的AbstractAnnotationConfigDispatcherServletInitializer提供了十分方便的註冊過濾器的方法,通過重載getServletFilters()方法將我們自定義的過濾器註冊到上下文中
如下代碼:
public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/*AbstractAnnotationConfigDispatcherServletInitializer 會同時創
建 DispatcherServlet 和 ContextLoaderListener 。 GetServlet-ConfigClasses() 方法返回的帶有 @Configuration 註解的
類將會用來定義 DispatcherServlet 應用上下文中的 bean 。 getRootConfigClasses() 方法返回的帶有 @Configuration 註解的類將
會用來配置 ContextLoaderListener 創建的應用上下文中的 bean 。*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
@Override
protected String[] getServletMappings() {
logger.debug("DispatcherServlet獲取匹配的前端控制器。。。。。。");
return new String[]{"/"};
}
/**
* 註冊過濾器
*/
@Override
protected Filter[] getServletFilters() {
//將自定義過濾器實例數組返回
return new Filter[]{new MyFilter()};
}
}
/**
* 過濾器類
*/
public class MyFilter implements Filter {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void init(FilterConfig filterConfig) throws ServletException {
logger.debug("過濾器初始化");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
logger.debug("執行過濾器");
Map<String, String[]> parameterMap = servletRequest.getParameterMap();
Set<String> keySet = parameterMap.keySet();
for(String key : keySet){
logger.debug("參數名:{},參數值:{}",key,parameterMap.get(key));
}
/**
* 執行該方法,如果有下一個過濾器則執行下一個過濾器,如果沒有,則執行目標方法
* 如果不執行該方法,將無法訪問目標路徑請求
*/
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
頁面訪問結果
2019-03-07 17:19:53.859 DEBUG com.my.spring.filter.MyFilter - 執行過濾器
2019-03-07 17:19:53.860 DEBUG com.my.spring.filter.MyFilter - 參數名:data,參數值:[{'id':0}]
2019-03-07 17:19:53.863 DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/SpringAction07/getBean?data={%27id%27:0}", parameters={masked}
Ⅱ、XML配置SpringMVC
如果是在Servlet3.0以下環境(tomcat7.0以下),使用純註解實現SpringMVC就不可能實現了,這時候我們需要藉助web.xml文件進行配置,但是我們並不希望全部使用xml進行配置,所以我們可以簡單配置初始化的內容,其他配置仍使用javaConfig配置方式。
將SpittrWebAppInitializer初始化類去除,使用web.xml代替:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--配置使用java配置-->
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<!--指定根配置類:RootConfig-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.my.spring.config.RootConfig</param-value>
</context-param>
<!--註冊ContextLoaderListener-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--註冊DispatcherServlet-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--使用java配置-->
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<!--指定DispatcherServlet配置類:WebConfig-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.my.spring.config.WebConfig</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
啓動應用,訪問正常!
二、處理文件上傳multipart
前面所遇到的表單處理,我們處理的都是簡單的字符串形式提交,但是上傳文件進行提交是一個應用十分常見的需求,文件上傳提交的格式是multipart格式,自然不可以像處理字符串形式參數一樣去處理。SpringMVC提供了MultipartFile接口用來處理上傳的文件:
public interface MultipartFile extends InputStreamSource {
String getName();
@Nullable
String getOriginalFilename();
@Nullable
String getContentType();
boolean isEmpty();
long getSize();
byte[] getBytes() throws IOException;
InputStream getInputStream() throws IOException;
default Resource getResource() {
return new MultipartFileResource(this);
}
void transferTo(File var1) throws IOException, IllegalStateException;
default void transferTo(Path dest) throws IOException, IllegalStateException {
FileCopyUtils.copy(this.getInputStream(), Files.newOutputStream(dest));
}
}
可以看到,MultipartFile接口可以用來獲取文件名,文件大小等信息,還提供了一個InputStream,用來將文件以流的方式讀取,還提供了一個便利的 transferTo() 方法,它能夠幫助我們將上傳的文件寫入到文件系統中。
使用MultiparFile實現文件上傳之前需要先配置Multipart解析器MultipartResolver,Spring3.1後內置兩個MultipartResolver的實現供我們選擇:
- CommonsMultipartResolver: 使用Jakarta Commons FileUpload解析multipart請求。
- StandardServletMultipartResolver: 依賴於Servlet3.0對multipart請求的支持。
一般來說,StandardServletMultipartResolver會是更好的方案,因爲它並不需要依賴於其他項目,使用原生的Servlet支持。只不過它只支持Servlet3.0以上的版本,如果低於等於Servlet3.0環境,需要使用CommonsMultipartResolver實現
Ⅰ、使用StandardServletMultipartResolver實現文件上傳
①配置解析器,在上文代碼的WebConfig中配置
public class WebConfig extends WebMvcConfigurationSupport {
/**
* 定義一個視圖解析器
*
**/
@Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver resourceViewResolver = new InternalResourceViewResolver();
resourceViewResolver.setPrefix("/WEB-INF/view/");
resourceViewResolver.setSuffix(".jsp");
resourceViewResolver.setExposeContextBeansAsAttributes(true);
resourceViewResolver.setViewClass(org.springframework.web.servlet.view.JstlView.class);
return resourceViewResolver;
}
/**
* 配置Multipart解析器
*
*/
@Bean
public MultipartResolver multipartResolver(){
return new StandardServletMultipartResolver();
}
@Override
protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
②在SpittrWebAppInitializer中配置文件上傳的初始化參數(必須)
//通過重載customizeRegistration()方法來配置multipart的默認參數
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
registration.setMultipartConfig(
new MultipartConfigElement("C:\\Users\\xxx\\Desktop\\uploads",2097152,4194304,20000000));
//設置寫入的臨時路徑(可絕對路徑)
//上傳文件的最大容量(字節爲單位),默認無限制。
//整個multipart請求的最大容量(字節爲單位),默認無限制。
//在上傳的過程中,如果文件大小達到了一個指定的最大容量,將會寫入到臨時文件路勁中。默認爲0,也就是上傳的文件都會寫入到磁盤上。
}
③編寫文件上傳controller方法
@RequestMapping(value = "/upload",method = RequestMethod.POST)
public String upload(@RequestPart("file") MultipartFile multipartFile, Model model) throws IOException {
String fileName = new String(multipartFile.getOriginalFilename().getBytes("utf-8"));
//使用multipartFile的transferTo方法將文件存放到桌面
multipartFile.transferTo(new File("C:\\Users\\xxx\\Desktop\\"+multipartFile.getOriginalFilename()));
model.addAttribute("fileName",fileName);
model.addAttribute("fileSize",multipartFile.getSize());
return "showFile";
}
④編寫上傳文件的JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<meta charset="UTF-8">
<body>
<h2>Hello World!</h2>
<!--此處必須設置form的enctype屬性設置爲multipart/form-data,否則會報CrrentRquest not a MultipartFile Request-->
<form action="./upload" method="post" enctype="multipart/form-data">
<label>文件;</label><input type="file" name="file">
<button type="submit">提交</button>
</form>
</body>
</html>
⑤編寫文件信息視圖:showFile.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page isELIgnored="false" %>
<html>
<head>
<title>文件展示</title>
</head>
<body>
文件名稱:
<c:out value="${fileName}"></c:out><br>
文件大小:
<c:out value="${fileSize}"></c:out>KB
</body>
</html>
頁面效果:
點擊提交,將文件提交至服務器,上傳成功
Ⅱ、使用CommonsMultipartResolver實現文件上傳
Spring內置了 CommonsMultipartResolver ,可以作爲 StandardServletMultipartResolver 的替代方案,但是,由於使用的是Commons FileUpload的上傳方式,需要引入jar支持:
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
最簡單的配置方式就是其構造函數:
@Bean
public MultipartResolver multipartResolver() throws IOException
{
return new CommonsMultipartResolver();
}
配置文件上傳參數,與 StandardServletMultipartResolver 有所不同,CommonsMultipart-Resolver 不會強制要求設置臨時文件路徑。默認情況下,這個路徑就是 Servlet 容器的臨時目錄。不過,通過設置 uploadTempDir 屬性,我們可以將其指定爲一個不同的位置
@Bean
public MultipartResolver multipartResolver() throws IOException
{
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
//設置臨時文件夾
multipartResolver.setUploadTempDir(new FileSystemResource("C:\\Users\\xxx\\Desktop\\uploads"));
//設置最大內存大小
multipartResolver.setMaxInMemorySize(100000);
//設置上傳文件的最大容量
multipartResolver.setMaxUploadSize(2097152);
return multipartResolver;
}
經測試,上傳成功
注:如果沒有引入上述兩個jar包,會報文件找不到的錯誤:
javax.servlet.ServletException: Servlet.init() for servlet dispatcher threw exception
Factory method 'multipartResolver' threw exception; nested exception is java.lang.NoClassDefFoundError: org/apache/commons/fileupload/FileItemFactory
Ⅲ、文件下載實現
文件下載使用Spring提供的ResponseEntity實現
ResponseEntity:可以添加HttpStatus狀態碼的HttpEntity的擴展類。被用於RestTemplate和Controller層方法
①編寫下載文件的controller方法:
@RequestMapping("/download")
public ResponseEntity<byte[]> filedownload(HttpServletRequest request, String filename) throws Exception{
//此處指定只從桌面獲取文件
String path = "C:\\Users\\xxx\\Desktop\\";
File file = new File(path+File.separator+filename);
//返回頭部設置
HttpHeaders headers = new HttpHeaders();
headers.setContentDispositionFormData("attachment",filename);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
//獲取文件輸入流
InputStream is = new FileInputStream(file);
//將文件轉換成byte數組
byte[] bytes = new byte[is.available()];
is.read(bytes);
//封裝信息返回
return new ResponseEntity<byte[]>(bytes,headers, HttpStatus.OK);
}
②編寫JSP文件下載資源:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page isELIgnored="false" %>
<html>
<head>
<title>文件展示</title>
</head>
<body>
文件名稱:
<c:out value="${fileName}"></c:out><br>
文件大小:
<c:out value="${fileSize}"></c:out>KB
<a href="${pageContext.request.contextPath}/download?filename=${fileName}">文件下載</a>
</body>
</html>
頁面展示如下,點擊文件下載,彈出資源管理窗口,將其保存至目標路徑
下載完成
三、處理異常
自定義異常在SpringMVC中是十分普遍的,有時候我們需要控制異常的輸出樣式,而不是赤裸裸的將錯誤信息展示在用戶面前,那樣並不友好,可能還有點辣眼睛
那麼,SpringMVC中,怎麼捕獲異常並友好輸出呢?也許你會想到使用try-catch的方式去處理,但是那樣耦合性太強了,SpringMVC提供了兩個註解進行捕捉處理
Ⅰ、@ExceptionHandler(MyException.class)捕捉異常
@ExceptionHandler()註解可以捕捉當前控制器內所有方法拋出的特定異常,而不需要在控制器內每個方法上去標識
①編寫自定義異常類,繼承Exception
public class MyException extends Exception{
public MyException() {
super();
}
public MyException(String message) {
super(message);
}
public MyException(String message, Throwable cause) {
super(message, cause);
}
}
②編寫測試Controller
@Controller
public class ExceptionController {
@ExceptionHandler(MyException.class)//捕捉當前控制器內任意方法拋出MyException
public String toError(MyException myException, Model model){
model.addAttribute("error",myException.getMessage());
//返回指定的錯誤視圖,經過特殊編寫,會比瀏覽器默認錯誤頁面更美觀
return "error";
}
@RequestMapping("/exception")
public String testException() throws MyException {
//爲了測試拋出異常,設置條件恆爲true
if(1==1){
throw new MyException("系統搞錯咯");
}
return "123";
}
}
③編寫錯誤頁面:error.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page isELIgnored="false" %>
<html>
<head>
<title>錯誤頁面</title>
</head>
<body>
<h1>您好,系統異常,請稍後重試</h1>
<hr>
<!--使用JSTL標籤和EL表達式從modle獲取錯誤信息-->
<c:out value="${error}"></c:out>
</body>
</html>
測試結果:
Ⅱ、@ControllerAdvice,爲所有控制器處理異常
即便如上述代碼已經十分方便,我們仍有可能需要在每個有可能拋出異常的控制器內編寫@ExceptionHandler()註解方法,顯然Spring可以做的更好。
@ControllerAdvice註解標註的類可以捕捉應用內所有的錯誤,並結合@ExceptionHandler()在其內進行特殊處理,系統內拋出的所有錯誤,都會經過該類處理
①編寫統一處理類
@ControllerAdvice
public class ExceptionHelper {
//對特殊異常進行處理
@ExceptionHandler(MyException.class)
public String toError(MyException myException, Model model){
model.addAttribute("error",myException.getMessage());
return "error";
}
}
②普通controller方法
@Controller
public class ExceptionController {
@RequestMapping("/exception")
public String testException() throws MyException {
//爲了測試拋出異常,設置條件恆爲true
if(1==1){
throw new MyException("系統搞錯咯");
}
return "123";
}
}
測試結果:顯然我們並沒有在ExceptionController中對該錯誤進行顯式處理,結果卻跳轉到錯誤頁面,證明處理成功