第一個SpringBoot程式

来源:https://www.cnblogs.com/sun-haiyu/archive/2018/10/04/9742713.html
-Advertisement-
Play Games

第一個SpringBoot程式 例子來自慕課網廖師兄的免費課程 "2小時學會SpringBoot" "Spring Boot進階之Web進階" 使用IDEA新建工程,選擇SpringBoot Initializr,勾選Web一路next就搭建了一個最簡單的SpringBoot工程。如下: @Spri ...


第一個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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在面向對象程式設計中,對象可以看做是數據(特性)以及由一系列可以存取、操作這些數據的方法所組成的集合。編寫代碼時,我們可以將所有功能都寫在一個文件里,這樣也是可行的,但是這樣不利於代碼的維護,你總不希望維護代碼前,還需要從頭至尾的通讀一遍吧,就好像一間雜亂無章的房子,你想找一件想要的東西,但是需要地 ...
  • python-GUI編程-PyQt5 編寫出你開心就好的界面!! ...
  • [TOC] 前言 暑假搞數學建模接觸到了Python,不得已成為了一個Py吹,Python作為動態的高級語言,在方便的同時也伴隨了想當強的靈活性,學Python首先是為了寫爬蟲,在寫爬蟲之前先來點小前奏,用Python的Selenium包實現模擬點擊,完成啟明星工作室論壇的自動簽到。(因為本人老是沉 ...
  • 視圖函數返回HTML模板:使用“from flask import render_template”,在函數中傳入相對於文件夾“templates”HTML模板路徑名稱字元串即可,flask會自動到項目根目錄的“templates”文件夾(創建flask項目時,PyCharm會自動創建兩個空文件夾, ...
  • 將屬於一類的對象放在一起: 如果一個函數操縱一個全局變數,那麼兩者最好都在類內作為特性和方法實現。 不要讓對象過於親密: 方法應該只關心自己實例的特性,讓其他實例管理自己的狀態。 簡單就好: 讓方法小巧起來,一般來說,多數方法都應在30秒內被讀完,儘量在代碼的行數控制在一頁或者一屏之內。 小心繼承, ...
  • JPA概述 JPA(Java Persistence API)的簡稱,用於持久化的API。 JAVAEE5.0平臺標準的ORM的規範使得應用程式以統一的方式訪問持久層。 JPA和Hibernate的關係 JPA是Hibernate的一個抽象,就像JDBC和JDBC驅動的關係一樣。 PA是規範:JPA ...
  • 1. 點擊菜單欄的File >New Project 2. 打開Terminal, 進入剛剛創建的路徑執行如下命令: python manage.py startapp app01 顯示效果如下: 3. 配置靜態文件路徑 4. 在view.py文件新增方法: 5. 在urls.py文件中進行路由匹配 ...
  • 一、JVM中的類載入器類型 從Java虛擬機的角度講,只有兩種不同的類載入器:啟動類載入器和其他類載入器。 1.啟動類載入器(Boostrap ClassLoader):這個是由c++實現的,主要負責JAVA_HOME/lib目錄下的核心 api 或 -Xbootclasspath 選項指定的jar ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...