Like Share Discussion Bookmark Smile

J.J. Huang   2019-03-15   Spring Boot   瀏覽次數:2471次   DMCA.com Protection Status

SpringBoot - 第五章 | 常用註解

在前面文章中我們大量使用到Spring Boot的annotation(@註解),例如:@RestController、@Controller、@RequestMapping注解,這邊會盡量把一些常用的@註解做使用方式和使用場景的介紹。

常用註解

@SpringBootApplication

在前面幾章中,在啟動類裡面,都會看到這個啟動類註解,此註解是集合以下這些註解,@SpringBootConfiguration、@EnableAutoConfiguration 和 @ComponentScan 注解。

  • @SpringBootConfiguration 繼承 @Configuration,對於熟悉spring的開發者而言,此註解當前類是配置類,並會將當前類內聲明的一個或多個以@Bean註解標記的方法的實例納入到Spring容器中,並且實例名就是方法名。

  • @EnableAutoConfiguration 這個註解就是SpringBoot能自動配置的所在了。通過此註解,能將所有符合自動配置條件的bean的定義加載到Spring容器中。這個在後面會再拉出來特別說。

  • @ComponentScan 顧名思義,就是掃描當前包及其子包下被 @Component、@Controller、@Service、@Repository 等註解的類並加入到Spring容器中進行管理。


@Controller 和 @RestController

  • @Controller 註解在類上,表示這是一個控制層bean

  • @RestController 是一個結合了 @ResponseBody 和 @Controller 的註解

@RestController Spring4之後加入的註解,原來在@Controller中返回json需要@ResponseBody來配合,如使用@RestController就不需要再配置@ResponseBody。


@RequestMapping

處理請求網址對應的註解,可用於類或方法上。用於類上,表示類中的所有響應請求的方法都是以該網址作為父路徑。

參數 說明
value 指定請求的網址
method 請求方法類型
consumes 請求的提交內容類型
produces 指定返回的內容類型 僅當request請求頭中的(Accept)類型中包含該指定類型才返回
params 指定request中必須包含某些引數值
headers 指定request中必須包含指定的header值
name 指定給mapping一個名稱,沒有什麼作用
  • 常用的基本上就是 value 和 method
1
2
3
4
5
6
7
8
9
10
@RequestMapping(value = "/get" , method = RequestMethod.GET)
@RequestMapping(value = "/post" , method = RequestMethod.POST)
@RequestMapping(value = "/put" , method = RequestMethod.PUT)
@RequestMapping(value = "/delete" , method = RequestMethod.DELETE)

// 另一種撰寫方式,與上面相同意思
@GetMapping(value = "/get")
@PostMapping(value = "/post")
@PutMapping(value = "/put")
@DeleteMapping (value = "/delete")

@RequestBody 和 @ResponseBody

  • @RequestBody註解允許request的參數在reqeust體中,常常結合前端POST請求,進行前後端交互。

  • @ResponseBody註解支持將的參數在reqeust體中,通常返回json格式給前端。


@PathVariable、@RequestParam、@RequestAttribute

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
package com.jj.learning.springboot.chapter5.controller;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {
    
    @RequestMapping(value = "/path/{id}", method = RequestMethod.GET)
    public String pathVariable(@PathVariable("id") String id) {
        return "ID:" + id;
    }
    
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
package com.jj.learning.springboot.chapter5.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @RequestMapping(value = "/path", method = RequestMethod.GET)
    public String requestParamGet(@RequestParam("id") String id){
        return "ID:" + id;
    }
    
    @RequestMapping(value = "/path", method = RequestMethod.POST)
    public String requestParamPost(@RequestParam("id") String id){
        return "ID:" + id;
    }

}
  • @RequestAttribut,用於取得過濾器或攔截器建立的、預先存在的請求屬性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
package com.jj.learning.springboot.chapter5.controller;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @ModelAttribute
    void beforeInvokingHandlerMethod(HttpServletRequest request) {
        request.setAttribute("id", "Hello world!");
    }

    @RequestMapping(value = "/req/attr", method = RequestMethod.GET)
    public String requestAttribute(@RequestAttribute("id") String id) {
        return "ID:" + id;
    }

}

@Component、@Service、@Repository

  • @Component 一般的組件,可以被注入到Spring容器進行管理

  • @Repository 註解於類上,表示於持久層

  • @Service 註解於類上,表示於邏輯層

註:通常一些類無法確定是使用@Service還是@Component時,註解使用@Component,比如Redis的配置類..等


@ModelAttribute

@ModelAttribute註解可被應用在方法或方法參數上。

方法使用 @ModelAttribute

  • 註解在方法上的@ModelAttribute 說明方法是用於添加一個或多個屬性到model上。這樣的方法能接受與@RequestMapping註解相同的參數類型,只不過不能直接被對應到具體的請求上。

  • 在同一個控制器中,註解了@ModelAttribute的方法實際上會在@RequestMapping方法之前被調用。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
package com.jj.learning.springboot.chapter5.controller;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @ModelAttribute
    void beforeInvokingHandlerMethod(HttpServletRequest request) {
        request.setAttribute("id", "Hello world!");
    }

    @RequestMapping(value = "/req/attr", method = RequestMethod.GET)
    public String requestAttribute(@RequestAttribute("id") String id) {
        return "ID:" + id;
    }

}

方法使用 @ModelAttribute通常被用來加入一些公共的屬性或資料,比如一個下拉列表所預設的幾種狀態,或者寵物的幾種類型,或者去取得一個HTML表單渲染所需要的命令對象,比如Account等。


方法參數使用 @ModelAttribute

方法參數使用 @ModelAttribute 說明了該方法參數的值將由model中取得。如果model中找不到,那麼該參數會先被實例化,然後被添加到model中。在model中存在以後,請求中所有名稱匹配的參數都會填充到該參數中。

-它可能因為@SessionAttributes註記的使用已經存在於model中
-它可能因為在同個控制器中使用了@ModelAttribute方法已經存在於model中
-它可能是由URI模板變量和類型轉換中取得的
-它可能是調用了自身的默認構造器被實例化出來的

簡單範例:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
package com.jj.learning.springboot.chapter5.controller;

import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ModelAttributeController {

    @RequestMapping(value = "/ma")
    public String modelAttribute(@ModelAttribute("id") String id) {
        return "ID:" + id;
    }

    @RequestMapping(value = "/ma/{id}")
    public String modelAttributeByPath(@ModelAttribute("id") String id) {
        return "ID:" + id;
    }

}

建立RESTful API與單元測試

RESTful API具體設計如下

類型 URL 說明
GET /customers 查詢客戶列表
POST /customers 新增一個客戶
GET /customers/id 根據id查詢一個客戶
PUT /customers/id 根據id更新一個客戶
DELETE /customers/id 根據id刪除一個客戶
  • Customer 的定義
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
package com.jj.learning.springboot.chapter5.model;

public class Customer {

    private Long id;
    private String name;
    private Integer age;

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }

}
  • 實作對 Customer 對象的操作API
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
package com.jj.learning.springboot.chapter5.controller;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.jj.learning.springboot.chapter5.model.Customer;

@RestController
// 這個配置表示下面的網址對應都在/customers下
@RequestMapping(value = "/customers")
public class CustomerController {

    // 創建執行緒(thread)安全的Map
    static Map<Long, Customer> customers = Collections.synchronizedMap(new HashMap<Long, Customer>());

    // 處理"/customers/"的GET請求,查詢客戶列表
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public List<Customer> getCustomerList() {
        List<Customer> r = new ArrayList<Customer>(customers.values());
        return r;
    }

    // 處理"/customers/"的POST請求,新增一個客戶
    @RequestMapping(value = "/", method = RequestMethod.POST)
    public String postCustomer(@ModelAttribute Customer customer) {
        customers.put(customer.getId(), customer);
        return "success";
    }

    // 處理"/customers/{id}"的GET請求,用來獲取URL中該id值的Customer資料
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public Customer getCustomer(@PathVariable Long id) {
        return customers.get(id);
    }

    // 處理"/customers/{id}"的PUT請求,用来更新Customer資料
    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    public String putCustomer(@PathVariable Long id, @ModelAttribute Customer customer) {
        Customer u = customers.get(id);
        u.setName(customer.getName());
        u.setAge(customer.getAge());
        customers.put(id, u);
        return "success";
    }

    // 處理"/customers/{id}"的DELETE請求,用来删除Customer
    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public String deleteUser(@PathVariable Long id) {
        customers.remove(id);
        return "success";
    }

}
  • 針對 Customer API 實作測試案例
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
package com.jj.learning.springboot.chapter5.controller;

import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringRunner.class)
// 此為測試案例,設定Port為隨機,避免了不必要的Port衝突。
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class CustomerControllerTest {

    // 建立一個空的WebApplicationContext
    @Autowired
    private WebApplicationContext webApplicationContext;

    // 建立 MockMvc 的物件
    private MockMvc mvc;

    @Before
    public void setUp() throws Exception {
        // 使用 webApplicationContext 建立初始化 MockMvc
        mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

    @Test
    public void testCustomerController() throws Exception {
        RequestBuilder request = null; 
        
        // 確認目前的Customers是否為空
        request = get("/customers/");
        mvc.perform(request)
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("[]")));
        
        // 新增一個Customer
        request = post("/customers/")
                .param("id", "1")
                .param("name", "jjHuang") 
                .param("age", "99");
        
        // 確認Customer是否新增成功
        mvc.perform(request)
                .andExpect(content().string(equalTo("success")));
        
        // 取得目前Customer清單,確認剛剛新增的客戶是否存在
        request = get("/customers/");
        mvc.perform(request)
                .andExpect(status().isOk()) 
                .andExpect(content().string(equalTo("[{\"id\":1,\"name\":\"jjHuang\",\"age\":99}]")));
        
        // 根據id更新客戶資料
        request = put("/customers/1") 
                .param("name", "newJJHuang") 
                .param("age", "18");
        
        // 確認Customer是否更新成功
        mvc.perform(request)
                .andExpect(content().string(equalTo("success")));
        
        // 根據id取得Customer,確認更新資料是否正確
        request = get("/customers/");
        mvc.perform(request)
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("[{\"id\":1,\"name\":\"newJJHuang\",\"age\":18}]")));
        
        // 根據id刪除Customer
        request = delete("/customers/1");
        
        // 確認Customer是否刪除成功
        mvc.perform(request)
                .andExpect(content().string(equalTo("success")));
        
        // 最後確認目前的Customers是否為空
        request = get("/customers/");
        mvc.perform(request)
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("[]")));
        
    }

}
  • 驗證