欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

第一个SpringBoot程序

程序员文章站 2022-04-25 18:25:44
第一个SpringBoot程序 例子来自慕课网廖师兄的免费课程 "2小时学会SpringBoot" "Spring Boot进阶之Web进阶" 使用IDEA新建工程,选择SpringBoot Initializr,勾选Web一路next就搭建了一个最简单的SpringBoot工程。如下: @Spri ......

第一个springboot程序

使用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