第一個SpringBoot程序
第一個SpringBoot程序
例子來自慕課網廖師兄的免費課程
2小時學會SpringBoot
Spring Boot進階之Web進階
使用IDEA新建工程,選擇SpringBoot Initializr,勾選Web一路next就搭建了一個最簡單的SpringBoot工程。如下:
package com.shy.springboot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringbootApplication { public static void main(String[] args) { SpringApplication.run(SpringbootApplication.class, args); } }
@SpringBootApplication整合了三個常用的註解,分別是:
- @ComponentScan:會自動掃描指定包下的全部標有@Component的類,並註冊成bean,當然包括@Component下的子註解@Service,@Repository,@Controller;
- @SpringBootConfiguration:可以當成Spring的標準配置註解@Configuration來使用。而@Configuration表明這是一個JavaConfig配置類。通常配合@Bean註解,@Bean註解告訴Spring這個方法將返回一個對象,該對象將會註冊為Spring應用上下文中的bean;
- @EnableAutoConfiguration:能夠自動配置spring的上下文,試圖猜測和配置你想要的bean類,通常會自動根據你的類路徑和你的bean定義自動配置。
配置文件相關
SpringBoot的配置文件可以使用xml和yml格式,比如使用yml格式
# 自定義屬性
cupSize: b
age: 18
# 可以在yml裏通過${}來引用
content: "cupSize: ${cupSize}, age: ${age}"
# 指定端口為8080,(不配置默認8080)
server:
port: 8080
可以使用註解@Value("${...}")
獲取配置文件中的值,@Value和@Autowired註解作用類似。
Spring提供了兩種在運行時求值的方式:
- 屬性占位符:
${...}
- Spring表達式語言(SpEL):
#{...}
如果cupSize和age都是屬於同一類屬性下的子屬性,比如都屬於girl。
那麽可以寫成下面的形式:
girl:
cupSize: b
age: 18
在java中註入時,也不用一個個屬性註入,可以註入girl的全部屬性。不過需要將girl的屬性抽象成一個java類。
package com.shy.springboot.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 讀取配置文件的信息並自動封裝成實體類
* 註入SpringBoot配置文件中前綴是"girl"的全部屬性
*/
@Component
@ConfigurationProperties(prefix = "girl")
public class GirlProperties {
private String cupSize;
private Integer age;
public String getCupSize() {
return cupSize;
}
public Integer getAge() {
return age;
}
}
屬性配置方式
- @Value,從配置文件中註入屬性
- @ConfigurationProperties(prefix = "...")讀取配置文件中對應前綴中的屬性,並映射成對象實體
環境配置:
可以建立多個application-xxx.yml文件,然後在application.yml文件中配置其中一個環境.
比如我有application-dev.yml文件表示開發環境下的配置文件,application-prod.yml文件表示生產環境下的配置文件。那麽再按application.yml中配置如下
spring:
profiles:
active: prod
就表示使用application-dev.yml中的配置。
一些常用註解
- Controller,作用於類上,表示MVC中的控制層,可以被@ComponentScan掃描到並註入。用於處理Http請求,返回字符串代表的模板,如xx.jsp, xx.ftl。
- @RestController,是@ResponseBody和@Controller的整合,處理http請求,可以返回實體對象或字符串,以json格式表示。
- @ResqustMapping,配置url映射。可以在類上使用(作為類中方法的前綴),可以在方法上使用。
package com.shy.springboot.controller;
import com.shy.springboot.config.GirlProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/girl")
public class Hello {
/**
* 表示value中的值都可作為url路徑,如果不指定請求方法method,那麽GET和POST方式都可以,但是一般不推薦
*/
@RequestMapping(value = {"/hello", "/hi"}, method = RequestMethod.GET)
public String hello() {
return "Hello";
}
}
- @PathVariable,獲取url路徑中的數據
- @RequstParam,獲取請求參數中的值
- @GetMapping,組合註解,是@RequestMapping(value = "...", method = RequestMethod.GET)的縮寫形式,當然也有PostMapping了。
package com.shy.springboot.controller;
import com.shy.springboot.config.GirlProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/girl")
public class Hello {
// 可以響應 http://localhost:8080/girl/hello/xx
@RequestMapping(value = {"/hello/{id}"}, method = RequestMethod.GET)
public String hello(@PathVariable("id") Integer id) {
return "My id is " + id;
}
// 可以響應 http://localhost:8080/girl/hello?id=xx
// required = false表示這個參數可以為空,defaultValue表示當參數為空時的默認值,因此訪問http://localhost:8080/girl/hello,將使用默認值1。
@RequestMapping(value = {"/hello"}, method = RequestMethod.GET)
public String hello2(@RequestParam(value = "id", required = false,defaultValue = "1") Integer id) {
return "My id is " + id;
}
}
數據庫配置
本例子使用JPA和MySQL,所以在pom中加入如下依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
在application.yml中配置數據源和jpa相關。
spring:
profiles:
active: dev
# 以下使用了jpa和mysql
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/dbgirl
username: root
password: admin
jpa:
hibernate:
ddl-auto: create
show-sql: true
JPA(Java Persistence API),即Java持久化API,Hibernate實現了這個規範。
package com.shy.springboot.database;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Girl {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Integer age;
private String cupSize;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getCupSize() {
return cupSize;
}
public void setCupSize(String cupSize) {
this.cupSize = cupSize;
}
public Girl() {
}
}
- @Entity 表示這是個實體類,可以映射成數據表。
- @Id表示該屬性為主鍵
- @GeneratedValue表示該屬性字段為自增,一般搭配@Id使用
@GeneratedValue有幾種策略
- IDENTITY:采用數據庫ID自增長的方式來自增主鍵字段,Oracle 不支持這種方式,使用MySQL時,配置該策略可以實現主鍵自增。
- AUTO:JPA自動選擇合適的策略,是默認選項;
- SEQUENCE:通過序列產生主鍵,通過@SequenceGenerator 註解指定序列名,MySql不支持這種方式 ;
- TABLE:通過表產生主鍵,框架借由表模擬序列產生主鍵,使用該策略可以使應用更易於數據庫移植;
jpa.hibernate.ddl-auto
,共有五種配置方式。
- ddl-auto:create: 每次運行該程序,沒有表格會新建表格,表內有數據會清空
- ddl-auto:create-drop: 每次程序結束的時候會清空表
- ddl-auto:update: 每次運行程序,沒有表格會新建表格,若已經存在表格,則只會更新
- ddl-auto:validate: 運行程序會校驗數據與數據庫的字段類型是否相同,不同會報錯
- none: 禁止DDL處理
Controller和幾個簡單的請求
Repository提供了最基本的數據訪問功能,通過新建一個接口繼承JpaRepository<T, ID>
,可以直接使用接口中現成的方法來實現對數據的訪問。
package com.shy.springboot.database;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface GirlRepo extends JpaRepository<Girl, Integer> {
// 自定義的查詢方法,方法名要嚴格按照一定規則來命名
List<Girl> findByAge(Integer age);
}
泛型中的Girl表示該Repository可以訪問由Girl映射的數據表,Integer表示ID的數據類型。
寫一個Controller,處理各種請求來看JPA是如何與數據庫交互的。
package com.shy.springboot.controller;
import com.shy.springboot.database.Girl;
import com.shy.springboot.database.GirlRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
public class GirlController {
@Autowired
private GirlRepo girlRepo;
/**
* 查詢所有女生
* @return
*/
@GetMapping("/girls")
public List<Girl> girls() {
return girlRepo.findAll();
}
/**
* 添加一個女生
* @param cupSize
* @param age
* @return
*/
@PostMapping("/addGirl")
public Girl addGirl(@RequestParam("cupSize") String cupSize,
@RequestParam("age") Integer age) {
Girl girl = new Girl();
girl.setAge(age);
girl.setCupSize(cupSize);
return girlRepo.save(girl);
}
/**
* 通過id更新一個女生
* @param id
* @param cupSize
* @param age
* @return
*/
@PostMapping("/updateGirl/{id}")
public Girl updateGirl(@PathVariable("id") Integer id,
@RequestParam("cupSize") String cupSize,
@RequestParam("age") Integer age) {
Girl girl = new Girl();
girl.setId(id);
girl.setAge(age);
girl.setCupSize(cupSize);
return girlRepo.save(girl);
}
/**
* 根據id刪除一個女生
* @param id
*/
@GetMapping("/deleteGirl/{id}")
public void deleteGirl(@PathVariable("id") Integer id) {
Girl girl = new Girl();
girl.setId(id);
girlRepo.delete(girl);
}
/**
* 根據id查詢一個女生
* @param id
* @return
*/
@GetMapping("girls/{id}")
public Girl girlFindOne(@PathVariable("id") Integer id) {
return girlRepo.findById(id).get();
}
/**
* 根據年齡查詢一個女生
* @param age
* @return
*/
@GetMapping("girls/age/{age}")
public List<Girl> findGirlsByAge(@PathVariable("age") Integer age) {
return girlRepo.findByAge(age);
}
}
沒有寫一句SQL語句,就完成了對girl表的增刪改查,用起來還是很舒服的。
事務管理
下面的insertTwo方法插入兩條數據,如果不進行事務管理,則插入girlA成功,插入girlB失敗。加上@Transactional註解後(有兩個同名註解,導入spring的),要麽兩條數據都插入成功,要麽兩條都插入失敗。因為在本例中會出現異常,所以兩條都插入失敗。
// Service中
package com.shy.springboot.service;
import com.shy.springboot.database.Girl;
import com.shy.springboot.database.GirlRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class GirlService {
@Autowired
private GirlRepo girlRepo;
@Transactional
public void insertTwo() {
Girl girlA = new Girl();
girlA.setCupSize("B");
girlA.setAge(18);
girlRepo.save(girlA);
// 除0異常,退出
int a = 3 / 0;
Girl girlB = new Girl();
girlB.setCupSize("C");
girlB.setAge(20);
girlRepo.save(girlB);
}
}
// Controller中
@PostMapping("/girls/insertTwo")
public void insertTwo() {
girlService.insertTwo();
}
因為Hibernate創建的表默認引擎是MyISAM,所以如果發現事務沒有作用,要手動修改引擎為InnoDB。
ALTER TABLE xxx ENGINE=INNODB;
表單驗證
在上面的例子中如果要對年齡作限制,比如小於18歲的girl不能添加。可以在實體類中對其中的字段屬性使用註解來加以限制。
@Min(value = 18, message = "未滿18歲不得入內!")
private Integer age;
這句代碼限制了girl的年齡不能低於18歲。在Controller中修改添加女生的邏輯
/**
* 添加一個女生
* @return
*/
@PostMapping("/addGirl")
public Girl addGirl(@Valid Girl girl, BindingResult result) {
if (result.hasErrors()) {
System.out.println(result.getFieldError().getDefaultMessage());
return null;
}
return girlRepo.save(girl);
}
@Valid可以對對象進行驗證,加了@Valid註解的參數,其後要緊跟著BindingResult或者Errors(前者是後者的實現類),用於保存驗證結果。如果對象中有屬性不滿足驗證條件,其結果將體現中BindingResult中。
AOP
首先在pom中添加依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
然後編寫切面
package com.shy.springboot.aspect;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class HttpAspect {
private static final Logger LOG = LoggerFactory.getLogger(HttpAspect.class);
@Pointcut("execution(public * com.shy.springboot.controller.GirlController.*(..))")
public void log() {}
@Before("log()")
public void doBefore() {
LOG.info("我在方法調用前執行");
}
@After("log()")
public void doAfter() {
LOG.info("我在方法調用後執行");
}
}
因為在方法調用的前後都要對相同的方法進行通知,為了避免代碼冗余,把@Before和@After的execution表達式抽取成切點。
@Pointcut("execution(public * com.shy.springboot.controller.GirlController.*(..))")
表示對GirlController中所有public的任意返回值、任意參數的方法進行通知。註意該註解需要用在方法上,所以public log() {}
在這裏只是起一個標識作用,供@Pointcut依附,所以它的方法體是空的。
該切面使用了slf4j的日誌。當請求http://localhost:8080/addGirl時,控制臺輸出以下日誌,可以顯示比System.out.println()
更詳細的信息。
2018-10-03 10:03:46.899 INFO 1892 --- [nio-8080-exec-3] com.shy.springboot.aspect.HttpAspect : 我在方法調用前執行
Hibernate: insert into girl (age, cup_size) values (?, ?)
2018-10-03 10:03:47.031 INFO 1892 --- [nio-8080-exec-3] com.shy.springboot.aspect.HttpAspect : 我在方法調用後執行
輸出的Hibernate: insert into girl (age, cup_size) values (?, ?)
表示了Controller中addGirl方法的執行,在其前後分別輸出了@Before和@After執行的邏輯,所以AOP確實是生效了的。
現在修改doBefore方法,使它能從Request域中獲取請求url、IP地址、請求方法、請求中傳遞的參數。
@Aspect
@Component
public class HttpAspect {
private static final Logger LOG = LoggerFactory.getLogger(HttpAspect.class);
@Pointcut("execution(public * com.shy.springboot.controller.GirlController.*(..))")
public void log() {}
@Before("log()")
public void doBefore(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// url
LOG.info("url={}", request.getRequestURI());
// ip
LOG.info("IP={}", request.getRemoteAddr());
// method
LOG.info("method={}", request.getMethod());
// 參數
LOG.info("args={}", joinPoint.getArgs());
// class-method
LOG.info("class_method={}", joinPoint.getSignature().getDeclaringTypeName() + " " + joinPoint.getSignature().getName());
LOG.info("我在方法調用前執行");
}
@AfterReturning(value = "log()",returning = "obj")
public void doAfterReturning(Object obj) {
if (obj != null) {
LOG.info("Girl={}", obj.toString());
}
}
@After("log()")
public void doAfter() {
LOG.info("我在方法調用後執行");
}
}
在通知方法中可以聲明一個JoinPoint類型的參數,通過JoinPoint可以訪問連接點的細節。
- getArgs():獲取連接點方法運行時的入參列表;
- getSignature() :獲取連接點的方法簽名對象;
- getSignature().getName():獲取連接點的方法名
- getSignature().getDeclaringTypeName():獲取連接點所在類的名稱
還新增了一個@AfterReturning的通知,在方法成功返回後執行(若拋出異常將不會執行該通知),和@After的區別在於:被增強的方法不論是執行成功還是拋出異常,@After通知方法都會得到執行。
AOP中 @Before @After @AfterThrowing @AfterReturning的執行順序如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result;
try {
// @Before
result = method.invoke(target, args);
// @After
return result;
} catch (InvocationTargetException e) {
Throwable targetException = e.getTargetException();
// @AfterThrowing
throw targetException;
} finally {
// @AfterReturning
}
}
可知@AfterReturning的執行在@After之後。
如果請求http://localhost:8080/addGirl,將輸出以下日誌(日誌一些無關緊要的內容已被刪除)
url=/addGirl
IP=0:0:0:0:0:0:0:1
method=POST
args=Girl{id=null, age=26, cupSize=‘C‘}
class_method=com.shy.springboot.controller.GirlController addGirl
我在方法調用前執行
Hibernate: insert into girl (age, cup_size) values (?, ?)
我在方法調用後執行
Girl=Girl{id=27, age=26, cupSize=‘C‘}
統一異常處理
前面的addGirl方法,當驗證不通過時,返回null並在控制臺打印相關信息;當驗證通過又返回Girl。返回值不統一,而且如果我們希望將錯誤信息顯示在頁面,怎麽辦呢?
可定義一個Result<T>
,將要呈現的信息統一化,分別是錯誤碼code,錯誤信息msg和承載的對象T,這樣不管是成功還是發生各種各樣的異常,都可以返回統一的Result對象。
package com.shy.springboot.domain;
public class Result<T> {
/** 錯誤碼 */
private Integer code;
/** 信息 */
private String msg;
/** 對象 */
private T data;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
再寫一個工具類,可在成功和異常時候設置對應的狀態和信息,可有效減少重復代碼。
package com.shy.springboot.util;
import com.shy.springboot.domain.Result;
public class ResultUtil {
public static Result success(Object obj) {
Result result = new Result();
result.setMsg("成功");
result.setCode(0);
result.setData(obj);
return result;
}
public static Result success() {
return success(null);
}
public static Result error(Integer code, String msg) {
Result result = new Result();
result.setMsg(msg);
result.setCode(code);
return result;
}
}
於是我們的addGirl方法可以重構成下面的樣子
@PostMapping("/addGirl")
public Result<Girl> addGirl(@Valid Girl girl, BindingResult bindingResul) {
if (bindingResul.hasErrors()) {
return ResultUtil.error(1,bindingResul.getFieldError().getDefaultMessage());
}
girlRepo.save(girl);
return ResultUtil.success(girl);
}
現在新增一個檢查年齡的邏輯,小於14歲的認為在上小學,14~17歲認為在上初中,這兩種情況都不允許其進入,當檢查到年齡不符合要求時,拋出異常。
在GirlService中
public void checkAge(Integer id) {
Girl girl = girlRepo.findById(id).get();
int age = girl.getAge();
if (age < 14) {
throw new GirlException(ResultEnum.PRIMARY_SCHOOL);
} else if (age < 17) {
throw new GirlException(ResultEnum.MIDDLE_SCHOOL);
}
// 其他年齡的邏輯處理
}
註意上面使用枚舉來統一管理各種code對應的msg。
package com.shy.springboot.enums;
public enum ResultEnum {
SUCCESS(0, "成功"),
ERROR(-1, "未知錯誤"),
PRIMARY_SCHOOL(100, "你可能還在上小學"),
MIDDLE_SCHOOL(101, "你可能還在上初中");
private Integer code;
private String msg;
ResultEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
GirlException是個自定義異常類,除了message還把code整合進去了。
package com.shy.springboot.exception;
import com.shy.springboot.enums.ResultEnum;
public class GirlException extends RuntimeException{
private Integer code;
public GirlException(ResultEnum resultEnum) {
super(resultEnum.getMsg());
this.code = resultEnum.getCode();
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
在Controller中只是簡單調用下Service中的方法而已
@GetMapping("/girlAge/{id}")
public void getAge(@PathVariable("id") Integer id) {
girlService.checkAge(id);
}
如果現在啟動程序,請求http://localhost:8080/girlAge/22, 將按照自定義異常,但是返回的結果其格式是下面這樣的:
{
timestamp: 14XXXXXXX,
status: 500,
exception: XXX,
message: XXX,
path: "/girlAge/22"
}
因為系統內部發生了錯誤,不斷往上拋異常就會得到上面的信息。如果要保持不管在什麽情況下統一返回Result<T>
中的信息,像下面這樣:
{
code: xxx,
msg: xxx,
data: XXX
}
則需要對異常做一個捕獲,取出有用的message部分,然後再封裝成Result對象,再返回給瀏覽器。為此新建一個異常捕獲類
package com.shy.springboot.handle;
import com.shy.springboot.domain.Result;
import com.shy.springboot.exception.GirlException;
import com.shy.springboot.util.ResultUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* ControllerAdvice註解將作用在所有註解了@RequestMapping的控制器的方法上。
* 配合@ExceptionHandler,用於全局處理控制器裏的異常
*/
@ControllerAdvice
public class ExceptionHandle {
private static final Logger LOG = LoggerFactory.getLogger(ExceptionHandle.class);
@ExceptionHandler(Exception.class)
@ResponseBody
public Result handle(Exception e) {
if (e instanceof GirlException) {
GirlException exception = (GirlException) e;
return ResultUtil.error(exception.getCode(),exception.getMessage());
}
LOG.error("系統異常:{}", e.getMessage());
return ResultUtil.error(-1,"未知錯誤");
}
}
該類使用了註解@ControllerAdvice,@ControllerAdvice會作用在所有註解了@RequestMapping的控制器的方法上,再配合@ExceptionHandler,用於全局處理控制器裏的異常。@ExceptionHandler(Exception.class)表示可以處理Exception類及其子類。
因為除了會拋出自定義異常GirlException外,還有可能因為系統原因拋出其他類型的異常(如空指針異常),因此針對不同類型的異常返回不同的狀態碼,上面使用了instanceof來判斷異常類型。如果不是GirlException,被統一歸類為未知錯誤,但是各種異常都顯示未知錯誤不便於排查問題,因此在可控制臺輸出了異常原因來加以區分。
單元測試
SpringBoot中進行單元測試十分便捷,SpringBoot中默認使用了Junit4。
在src/test下可以創建單元測試類,當然更簡單的方法是在IDEA下右鍵,Go To -> Test Subject,然後選擇想要進行測試的方法即可。
下面的單元測試針對service層,主要是判斷某數據庫中某id的girl,其年齡實際值和預期值是否一致。有兩個比較關鍵的註解
- @RunWith(SpringRunner.class):當一個類用@RunWith註釋或繼承一個用@RunWith註釋的類時,JUnit將調用它所引用的類來運行該類中的測試而不是開發者去在Junit內部去構建它,因此這句代碼意思是讓測試運行於Spring測試環境中,SpringRunner僅僅繼承了SpringJUnit4ClassRunner而已,並沒有擴展什麽功能,前者可以看作是後者的“別名”。
- @SpringBootTest:可以自動搜尋@SpringBootConfiguration;在沒有明確指定@ContextConfiguration(loader=...)時,使用SpringBootContextLoader作為默認的ContextLoader,等等。
package com.shy.springboot.service;
import com.shy.springboot.domain.Girl;
import com.shy.springboot.repository.GirlRepo;
import org.junit.Assert;
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.test.context.junit4.SpringRunner;
/**
* RunWith(SpringRunner.class),讓測試運行於Spring測試環境
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class GirlServiceTest {
@Autowired
private GirlRepo girlRepo;
@Test
public void findOne() {
Girl girl = girlRepo.findById(22).get();
Assert.assertEquals(new Integer(14), girl.getAge());
}
}
然後針對Controller層,對某次請求進行測試,這裏使用到了MockMvc。
package com.shy.springboot.controller;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class GirlControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void girls() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/girls")).andExpect(MockMvcResultMatchers.status().isOk());
/* 下面這條測試不能通過 */
// mockMvc.perform(MockMvcRequestBuilders.get("/girls")).andExpect(MockMvcResultMatchers.content().string("abc"));
}
}
第一條測試模擬以get方法請求/girls
,並期望狀態碼是200 OK。註釋掉的第二條測試期望響應的內容是abc,然而我們返回的是json格式,所以肯定不能通過測試的。
2018.10.4
第一個SpringBoot程序