溫馨提示 : 本 blog 皆使用專案管理工具 Maven,其相關依賴要自己去https://mvnrepository.com/ 複製唷!

What is Spring MVC?

為 Spring framework 中的一個模組(webmvc),設計模式上為前置控制器模式(Front Controller Pattern)。
在 Spring MVC 中,前置控制器稱為 DispatcherServlet,底層依然依靠 Servlet API 運作。

使用概念 :

  • 使用 Spring MVC 提供之 API Controller 來撰寫控制器(Controller)
  • 其也提供了 Model,View 等 API
  • 一樣使用 IoC/DI 將 Controller 託管至 IoC 容器
  • DispatcherServlet 收到請求(Request),會控制交由指定控制器做處理

Front Controller Pattern

  • Design Pattern 中,針對 MVC 架構提出的模式
  • 所有請求集中給 Front Controller 接收,再經由⼀個分派器(Dispatcher),將請求分派給對應的控制器處理
  • 處理完後回到前置控制器,再由前置控制器取得 View
  • 最後由前置控制器做出回應(Response)

DispatcherServlet

  • 將前置控制器與分派器合而為一,型態為 DispatcherServlet
  • 為 Servlet API 之類別 HttpServlet 之子代類別
  • 在⼀般使⽤情況下,不會直接使⽤到 DispatcherServlet 物件,而是透過 web.xml 註冊
  • Spring MVC 中唯一的 Servlet

MVC 架構

Spring MVC 的 MVC 架構,是以 DispatcherServlet 為中⼼設計,再配合其他重要物件來完成請求回應

  • HandlerMapping : 網址與控制器的映射表
  • Controller : 控制器,即 Spring MVC 中的控制器 類別/⽅法
  • Model : 在此僅指 Model 層運算完的結果
  • ViewResolver : View 解析器,可將 View 的邏輯名稱解析成真實的 View
  • View : 真實的 View,EX. JSP 檔

註冊 DispatcherServlet

⼀般會將 DispatcherServlet 設定映射網站根路徑”/“,即對此網站的所有請求,皆會經過 DispatcherServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<web-app ..略>
<!-- 略 -->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>core.config.MvcConfig</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>

同時註冊核心組態類別

除了加上@Configuration 以外,需要實作 WebMvcConfiguer 介面
以及做驅動設定@EnableWebMvc,還需要加上@ComponentScan 將 Controller 做掃描

1
2
3
4
5
6
@Configuration
@EnableWebMvc
@ComponentScan("web.*.controller")
public class MvcConfig implements WebMvcConfigurer {
// 略
}

託管 ViewResolver 元件與靜態資源管理

託管 ViewResolver

需要 override configureViewResolvers(),實作並且設定

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
@EnableWebMvc
@ComponentScan("web.*.controller")
public class MvcConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/");
viewResolver.setSuffix(".jsp");
viewResolver.setContentType("text/html;charset=UTF-8");
registry.viewResolver(viewResolver);
}
}

靜態資源管理

需要針對靜態資源處理(DefaultServlet)以及重新映射靜態資源

靜態資源處理(DefaultServlet) : 覆寫 configureDefaultServletHandling()

重新映射靜態資源 : 覆寫 addResourceHandlers()
(可隱藏真實路徑)
在 MvcConfig 類別中 override configureDefaultServletHandling()

1
2
3
4
5
6
7
8
9
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("/WEB-INF/");
}

如果有一靜態資源 /WEB-INF/index.html,會被重新映射至/index.html

Model 支援

  • Spring MVC 實作 MVC 概念,其支援了 Model(指 Model 層運算完之結果)
  • 提供 Model 相關 API,做資料綁定(Data Binding),其資料即為 Servlet API 中的屬性物件(Attribute Object)
  • 取代 Servlet API 中,scope object 寫法
    像是 request.setAttribute、request.getAttribute

大部分 MVC / MVVM 框架,對 Model 支援僅有資料綁定

使用@ModelAttribute(“識別名”)來綁定屬性物件

  • ⽤在⽅法 : 綁定⽅法的回傳值。執⾏控制器⽅法前,會先執⾏此⽅法
  • ⽤在控制器⽅法的參數 : 綁定請求參數

每次對 member/useModelAttribute1 發出請求,會先呼叫 memberList(),並綁定此⽅法的回傳值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Controller
@RequestMapping("member")
public class MemberController {
@Autowired
private MemberService service;

@GetMapping("useModelAttribute1")
public String useModelAttribute1() {
return "member/manage";
}
@ModelAttribute("memberList")
private List<Member> memberList() {
return service.findAll();
}
}

請求參數會⾃動封裝⾄ member 中,加上@ModelAttribute 會再將其綁定

1
2
3
4
5
6
7
8
9
10
11
@Controller
@RequestMapping("member")
public class MemberController {
@Autowired
private MemberService service;
@GetMapping("useModelAttribute2")
public String useModelAttribute2(@ModelAttribute("member") Member member) {
// 略..
return "member/result";
}
}

Session scope

如要綁定範圍物件,需先使用@ModelAttribute 綁定屬性物件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Controller
@RequestMapping("member")
@SessionAttributes({ "member" })
public class MemberController {
@Autowired
private MemberService service;
@PostMapping("login")
public String login(Member member) {
service.login(member);
return "redirect:/result.jsp"; //重新導向
}
@ModelAttribute("member")
public Member member() {
return new Member();
}
}

為了取得 Session 範圍物件,使用@SessionAttribute(“識別名”)
**只能取得

1
2
3
4
5
6
7
8
9
@Controller
@RequestMapping("member")
public class MemberController {
@GetMapping("getInfo")
public String getInfo(@SessionAttribute("member") Member member) {
// 略..
return "member/edit";
}
}

RedirectAttributes

  • Model 的⼦型態,用來綁定暫存(Flash)屬性物件
  • Redirect 時可以共享屬性物件
  • 接收到屬性物件後,自動取消綁定
  • Jsp 無法使用
  • 傳送端與接收端 :
    • addFlashAttribute(“識別名”)
    • @ModelAttribute(“識別名”)
  • 若接收端要再往下共享,則可接著使⽤ Model 物件
1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
public class TestController {
@GetMapping("deliverer")
public String deliverer(RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("name", "William");
return "redirect:/recipient";
}
@GetMapping("recipient")
public String recipient(@ModelAttribute("name") String name, Model model) {
model.addAttribute("name", name);
return "some_page";
}
}

若想要綁定屬性物件至 Context Scope,可以直接使用 Servlet API 中的 ServletContext 物件,透過@Autowired 注⼊,即可取得 ServletContext 物件

1
2
3
4
5
6
@Controller
public class TestController {
@Autowired
private ServletContext servletContext;
// 略..
}

ViewResolver

  • ViewResolver 為 Spring MVC 重要物件,為 View 的解析器
  • 將控制器回傳的 View 的邏輯名稱 解析成 真實的 View
  • 真實的 View,包含網址中的路徑、檔名,及其他細節設定
  • Spring MVC 架構中,⾄少要有⼀個 ViewResolver
  • 共同⽗型態為 ViewResolver

InternalResourceViewResolver

  • 未託管任何 View 解析器時,預設的 View 解析器
  • 配合 InternalResourceView 使⽤,即 View 為 Servlet 或 JSP 或 HTML
  • 將控制器回傳的 View 邏輯名稱,加上 前綴字/後綴字
    • setPrefix(“前綴字”) : 設定 View 名稱的前綴字
    • setSuffix(“後綴字”) : 設定 View 名稱的後綴字
    • setContentType(“MIME Type”) :設定 View 內容的 MIME   Type,亦可同時設定⽂字編碼
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
@EnableWebMvc
@ComponentScan("web.*.controller")
public class MvcConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/");
viewResolver.setSuffix(".jsp");
viewResolver.setContentType("text/html;charset=UTF-8");
registry.viewResolver(viewResolver);
}
}

若控制器回傳邏輯名稱”member/result”,則會解析出的真實名稱為”/WEB-INF/member/result.jsp”

ContentNegotiatingViewResolver

  • Spring MVC 架構中,可同時有多個 View 解析器,用以對應多種 View 格式
  • 所以需要 ContentNegotiatingViewResolver 來管理多個 View 解析器
  • 解析方式是依照請求標頭(Request Header)中的 Accept 屬性之值,選擇對應的 View 解析器
1
2
3
4
5
6
7
8
9
// 自定義JSON格式的View解析器類別
public class JsonViewResolver implements ViewResolver {
@Override
public View resolveViewName(String viewName, Locale locale) {
MappingJackson2JsonView view = new MappingJackson2JsonView();
view.setPrettyPrint(true);
return view;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Configuration
@EnableWebMvc
@ComponentScan("web.*.controller")
public class MvcConfig implements WebMvcConfigurer {
// 略..
// 託管JSP格式專用View解析器
@Bean
public ViewResolver jspViewResolver() {
InternalResourceViewResolver internalResourceViewResolver
= new InternalResourceViewResolver();
internalResourceViewResolver.setPrefix("/WEB-INF/");
internalResourceViewResolver.setSuffix(".jsp");
return internalResourceViewResolver;
}
// 託管JSON格式專用View解析器
@Bean
public ViewResolver jsonViewResolver() {
return new JsonViewResolver();
}
// 託管管理多個View解析器的ContentNegotiatingViewResolver元件
@Bean
public ViewResolver contentNegotiatingViewResolver(ContentNegotiationManager manager) {
ContentNegotiatingViewResolver cnvr = new ContentNegotiatingViewResolver();
cnvr.setContentNegotiationManager(manager);
// 設定欲管理的View解析器
cnvr.setViewResolvers(Arrays.asList(jspViewResolver(), jsonViewResolver()));
return cnvr;
}
// 設定ContentNegotiatingViewResolver
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.TEXT_HTML);
}
}

RedirectView

  • 不會經過 View 解析器的解析,所以內含的 View 名稱應為真實名稱
  • 僅表⽰要重新導向,View 名稱可以是任何資源
  • View 名稱不能包含”WEB-INF”
1
2
3
4
5
6
7
8
9
10
11
@Controller
@RequestMapping("member")
public class MemberController {
@Autowired
private MemberService service;
@PostMapping("login")
public View login(Member member) {
service.login(member);
return new RedirectView("../index.html");
}
}

跳脫ViewResolver

  • 在某些情況下,確實會希望能不經過View解析器,意即跳脫View解析器
  • 託管了InternalResourceViewResolver,固定會替View名稱..
    • 前⾯加上”/WEB-INF/“;後⾯加上”.jsp”
    • 但想轉發(forward/redirect)⾄”index.html”
  • 除了使⽤RedirectView之外,Spring MVC另提供特殊前綴字,⽤以跳脫(不能包含”WEB-INF”)

特殊前綴字 :

  • 定義在 UrlBasedViewResolver 型態
  • FORWARD_URL_PREFIX : 值為”forward:”
  • REDIRECT_URL_PREFIX : 值為”redirect:”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Controller
@RequestMapping("member")
public class MemberController {
@Autowired
private MemberService service;
@GetMapping("login")
public String login(Member member) {
boolean result = service.login(member);
if (result) {
return "redirect: ../index.html";
} else {
return "forward: login.html";
}
}
}

Spring 於 JSTL 支援

  • 有定義自己的 JSTL(Jsp Standard Tag Library)
  • JSP 這種伺服器端渲染(Server Side Render)技術,會造成伺服器負荷過高
  • 現今則多採⽤客⼾端渲染(Client Side Render)技術,由客⼾端分擔負荷

現今有更好的選擇 – 前端框架

詳情請看講義。

欄位檢核

  • 實作 View 層時,常需處理欄位檢核
  • 欄位需符合當下定義的檢核條件,才會接續下⼀步動作
  • 實作可使⽤ Hibernate Validator
  • 另搭配 spring-form 標籤庫及 BindingResult 請看講義

@NotNull

  • 不可為 null

@NotBlank

  • 不可為 null/空字串
  • 只能⽤在 CharSequence 型態

@NotEmpty

  • 不可為 null/空
  • 可⽤在 CharSequence、Array、Collection、Map 等型態

@Size(min = 長度下限, max = 長度上限)

  • 元素個數上下限
  • 可⽤在 CharSequence、Array、Collection、Map 等型態
  • ⽤在 CharSequence 表⽰字元個數的上下限

@Pattern(regexp = “表示式”)

  • 正規表⽰式(Regular Expression)

@Email

  • 信箱格式

@Digital(integer = 整數位數, fraction = 小數位數)

  • 位數個數

@Max(最大值)、@Min(最小值)

  • 上/下 限

@Positive、@Negative

  • 正/負 數

@PositiveOrZero、@NegativeOrZero

  • 正或零/負或零

@DateTimeFormat(pattern = “格式”)

  • ⽇期時間格式
  • 由 Spring 提供
  • EX.@DateTimeFormat(pattern = “yyyy-MM-dd”)

@Past

  • 過去的⽇期時間

@PastOrPresent

  • 過去/當下 的⽇期時間

在 Spring MVC 中使用檔案傳輸

檔案可以放在

  • 資料庫端 : BLOB
  • applocation sever 端 EX. Tomcat 根⽬錄/files 可⽤以下⽅式取得 Tomcat 根⽬錄之路徑

以 Java based 為例

1
2
@Value("#{systemProperties['catalina.home']}")
private String tomcatRootPath;

在 web.xml 加上設定啟用檔案上傳功能

1
2
3
4
5
6
7
8
9
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
略..
</init-param>
<load-on-startup>1</load-on-startup>
<multipart-config /> <!-- this -->
</servlet>

並在 pom.xml 中加入依賴 Apache Commons FileUpload 支援

1
2
3
4
5
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>

託管 CommonsMultipartResolver 元件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@EnableWebMvc
@ComponentScan("web.*.controller")
public class MvcConfig implements WebMvcConfigurer {
// 略..
@Bean
public CommonsMultipartResolver commonsMultipartResolver() {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setDefaultEncoding("UTF-8"); // 設定檔名文字編碼
resolver.setMaxUploadSizePerFile(500 * 1024 * 1024); // 設定單檔大小上限
esolver.setMaxUploadSize(4 * 1024 * 1024 * 1024); // 設定總大小上限
return resolver;
}
}

檔案上傳

控制器方法 :

  • 接收檔案 : 參數加上@RequestParam,型態 MultipartFile[]
  • 另存新檔 (非必要) : 呼叫 transferTo()
1
2
3
4
5
6
@PostMapping("upload")
public void upload(@RequestParam("img") MultipartFile[] files) throws IOException {
for (MultipartFile file : files) {
file.transferTo(Paths.get(fileRootPath, file.getOriginalFilename()));
}
}

img 為請求參數的名稱,須對應前端(HTML/JavaScript)中的名稱

1
2
3
4
<form action="file/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="img" multiple>
<input type="submit">
</form>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<input type="file" multiple>
<button>Submit</button>
<script>
const input = document.querySelector('input');
document.querySelector('button').addEventListener('click', () => {
const formData = new FormData();
for (let file of input.files) {
formData.append('img', file, file.name);
}
fetch('file/upload', {
method: 'POST',
body: formData
});
});
</script>

檔案下載

  • 讓控制器⽅法回傳 byte[]
  • 須設定 MIME TYPE
    • @GetMapping(..,produces = MediaType.檔案的 MIME TYPE)
    • @PostMapping(..,produces = MediaType.檔案的 MIME TYPE)
  • 設定回傳值為是 HTTP 的回應本體(Response Body)
    • @ResponseBody

假設檔案放在 Tomcat 根⽬錄/files

1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
@RequestMapping("file")
public class FileController {

@Value("#{systemProperties['catalina.home'].concat('/files/')}")
private String fileRootPath;

@GetMapping(path = "download", produces = MediaType.IMAGE_PNG_VALUE)
@ResponseBody
public byte[] download() throws IOException {
return Files.readAllBytes(Paths.get(fileRootPath, "polar-bears.jpg"));
}
}

跳出下載視窗

  • 若希望跳出下載視窗,則可將MIME Type設定成application/octet-stream
1
2
3
4
5
6
7
8
@GetMapping(
path = "download",
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE
)
@ResponseBody
public byte[] download() throws IOException {
return Files.readAllBytes(Paths.get(fileRootPath, "polar-bears.jpg"));
}

另外可以指定預設檔名,詳看講義

Spring MVC 的例外處理

  • MVC 架構中,常將例外處理放在 Controller 層
  • 其他層則是直接將例外拋出(throw),⽽不可捕捉(catch)
  • Spring MVC 對此提供了⽀援,例外處理分成 類別範圍 跟 全域範圍
  • 對此提供了 例外映射 View 的設定

規則 :

  • 須加上 @ExceptionHandler(欲捕捉的例外型態.class)
  • 可加⼊⼀個例外參數,型態為欲捕捉的例外型態

類別範圍的例外處理

1
2
3
4
5
6
7
8
9
10
@Controller
public class TestController {
private static final Logger logger = LogManager.getLogger(MemberController.class);
// 略..
@ExceptionHandler(SQLException.class)
public String handleSQLException(SQLException exception) {
logger.error(exception.getMessage(), exception);
return "error";
}
}

全域範圍例外處理

1
2
3
4
5
6
7
8
9
@ControllerAdvice
public class ExceptionAdvice {
private static final Logger logger = LogManager.getLogger(ExceptionAdvice.class);
@ExceptionHandler(SQLException.class)
public String handleSQLException(SQLException exception) {
logger.error(exception.getMessage(), exception);
return "error";
}
}

RESTful API

大重點,我真滴不想再寫 JSP ㄌ

在 RESTful API 有其精神,包含 4 個參數傳遞請求方法~

前後端分離架構

  • 方便抽換後端技術

  • web 前後端分離,必須使用 AJAX 或是前端框架技術(也是用 AJAX)

  • 轉發(forward/redirect)⾏為,已不符現今設計

    • 資源浪費:轉發和重定向都需要重新創建請求和響應,這樣會浪費伺服器資源和網絡帶寬。

    • 速度緩慢:轉發和重定向需要多次網路往返,這會增加頁面加載時間和用戶等待時間。

    • SEO 不友好:搜索引擎爬取網頁時,通常只會解析一次 URL,如果網站使用了過多的轉發和重定向,搜索引擎可能會漏掉一些重要的內容,進而影響網站的 SEO。

    • 難以維護:轉發和重定向會導致多個 URL 之間產生複雜的關係,這樣會增加代碼的複雜度和維護難度。

Web API

  • 僅以回應本體(Response Body)回傳資料,不轉發(forward/redirect)
  • 前端只需透過網址及相關參數(有時可能會加⼊驗證),就可以跟後端交換資料
    EX. 透過網址 http://william.idv.tw/member/getInfo
    • 前端 : 傳”1”給後端
    • 後端 : 回”1 ithan0117 William”(會員編號、使⽤者名稱、暱稱)給前端
  • 資料傳遞,現今流行使⽤ JSON 格式
  • 業界常將 Web API 簡稱為 API

RESTful API

  • 一種架構風格(符合 REST ⾵格)
  • 操作同⼀個資源(Resource),使⽤同⼀個網址(URI)
  • 再以 HTTP 的請求⽅法(Request Method),來決定進⾏何種操作
  • 精神為使用請求方法要符合作的事情

四個請求方法 :

  • POST
    • 操作 : 新增
    • 參數傳遞方式 : 使用請求本體(Request Body)
    • 格式 : 現今流⾏使⽤ JSON 格式
  • PUT
    • 操作 : 修改
    • 參數傳遞方式 : 使用請求本體(Request Body)
    • 格式 : 現今流⾏使⽤ JSON 格式
  • DELETE
    • 操作 : 刪除
    • 參數傳遞方式 : 使用路徑變數(Path Variable)
    • 格式 : ⽤網址路徑當參數
  • GET
    • 操作 : 查詢
    • 參數傳遞方式 : 使用路徑變數(Path Variable)
    • 格式 : ⽤網址路徑當參數

因為需要傳遞 JSON 資料需要做組態設定,Spring MVC 提供 MappingJackson2HttpMessageConverter 型態,此型態底層使用 JSON API 為 JACKSON

1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.1</version>
</dependency>

託管 MappingJackson2HttpMessageConverter

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@EnableWebMvc
@ComponentScan("web.*.controller")
public class MvcConfig implements WebMvcConfigurer {
// 略..
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
messageConverter.setPrettyPrint(true);
converters.add(messageConverter);
}
}

對應各請求方法

  • @PostMapping/@DeleteMapping/@PutMapping/@GetMapping
  • 可⽤@RequestMapping(method = {方法 1,方法 2,…,方法 N}),對應多個請求⽅法

參數傳遞

  • 路徑變數 : @PathVariable
  • 請求本體 : @RequestBody

回應本體

  • @ResponseBody
  • 控制器類別改加上@RestController,則可省去@ResponseBody
  • 可配合 POJO、List<POJO>、Map、void(無回應本體)、ResponseEntity 等型態
1
2
3
4
@RestController
@RequestMapping("member")
public class MemberRestController {
}

member,前端統⼀使用此網址,操作會員資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 新增會員
@PostMapping
public Core create(@RequestBody Member member) {
return service.register(member);
}
// 刪除會員
@DeleteMapping("{id}")
public Core delete(@PathVariable Integer id) {
final boolean result = service.remove(id);
Core core = new Core();
core.setSuccessful(result);
core.setMessage(result ? "刪除成功" : "刪除失敗");
return core;
}
// 修改會員
@PutMapping
public Core update(@RequestBody Member member) {
final boolean result = service.save(member);
Core core = new Core();
core.setSuccessful(result);
core.setMessage(result ? "修改成功" : "修改失敗");
return core;
}
// 查詢會員
@GetMapping("{id}") //路徑變數,網址member/1, id值就會是1
public Member read(@PathVariable Integer id) {
return dao.selectById(id);
}
// 查詢全部會員
@GetMapping
public List<Member> readAll() {
return dao.selectAll();
}

RESTful 也有檔案傳輸,以及 JSON 格式屬性對應和日期時間格式化,處理跨域請求,詳見講義

處理(做掉) DispatcherServlet(自行定義部屬描述類別)

  • Servlet 3 後,web.xml 為非必要,可用 Java based 取代
  • Spring MVC 有支援,可在專案啟動時,就啟用 DispatcherServlet(不需要再設定 loadOnStartup)

自行定義部屬描述類別 :

  • 需要繼承 AbstractAnnotationConfigDispatcherServletInitializer
  • 覆寫 getRootConfigClasses() –指定 Spring 核⼼組態類別
  • 覆寫 getServletConfigClasses() – 指定 Spring MVC 核⼼組態類別
  • 覆寫 getServletMappings() – 設定 DispatcherServlet 映射路徑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class WebAppInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {

//指定 Spring 核⼼組態類別
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { AppConfig.class };
}
//指定 Spring MVC 核⼼組態類
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { MvcConfig.class };
}
//設定 DispatcherServlet 映射路徑
@Override
protected String[] getServletMappings() {
return new String[] { "/" }; // "/"映射網站根路徑
}
}

設定filter

在 部屬描述類別 實作 getServletFilters()

1
2
3
4
5
6
7
8
9
10
11
public class WebAppInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
// 略
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
OpenSessionInViewFilter openSessionInViewFilter = new OpenSessionInViewFilter();
return new Filter[] { characterEncodingFilter, openSessionInViewFilter };
}
}

檔案上傳

需要使用再加即可

1
2
3
4
5
6
7
8
public class WebAppInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
// 略
@Override
protected void customizeRegistration(Dynamic registration) {
registration.setMultipartConfig(new MultipartConfigElement(""));
}
}

預設首頁是放在 Spring MVC 組態類別而非部屬描述類別

  • 同 web.xml 中的<welcome-file-list><welcome-file>
  • 須配合 ViewResolver/靜態資源管理 使⽤
  • 前面設定前綴字的那個要註解掉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@EnableWebMvc
@ComponentScan("web.*.controller")
public class MvcConfig implements WebMvcConfigurer {
// 略
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("/WEB-INF/");
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index.html");
}
}

預設⾸⾴為 WEB-INF/index.html

跟 web.xml 說再見

pom.xml 預設下必須有 web.xml,要殺掉他需要設定略過檢查 web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<project 略..>
<build>
<finalName>spring-exercise</finalName>
<plugins>
<!-- 略 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml> <!-- 大重點 -->
</configuration>
</plugin>
</plugins>
</build>
</project>

Spring-WebSocket

dependency 自己加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ChatWebSocketHandler extends TextWebSocketHandler {

private Set<WebSocketSession> connectedSessionSet = Collections.synchronizedSet(new HashSet<>());

//當Client端連線連上時,會⾃動呼叫此⽅法
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
connectedSessionSet.add(session);
}

//當收到Client端傳遞的訊息時,會⾃動呼叫此⽅法
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
for (WebSocketSession connectedSession : connectedSessionSet) {
if (connectedSession.isOpen()) {
connectedSession.sendMessage(message);
} else {
connectedSessionSet.remove(connectedSession);
}
}
}
//當Client端連線關閉時,會⾃動呼叫此⽅法
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
connectedSessionSet.remove(session);
}
}

自定義WebSocket核心組態類別

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler(), "/chat");
}
@Bean
public WebSocketHandler webSocketHandler() {
return new ChatWebSocketHandler();
}
}

前端程式詳見講義

Spring STOMP 為進階寫法,詳情請洽官方文件

Spring Scheduling

定義在模組 Spring-Context

自定義 Scheduling 核心組態類別

  • 加上@Scheduled(cron = “CRON 表⽰式”) – 設定觸發時機
  • 加上@Async – 設定非同步
  • 撰寫想要做的事
1
2
3
4
5
6
7
8
9
10
@Configuration
@EnableScheduling
@EnableAsync
public class SchedulingConfig {
@Scheduled(cron = "* * * * * *") //類Cron表⽰式。每秒執行⼀次
@Async
public void task1() {
System.out.println("task1: "+ new Time(System.currentTimeMillis()));
}
}