Spring Boot 入门和进阶

来源:互联网 发布:淘宝的违禁词 编辑:程序博客网 时间:2024/06/03 03:23

慕课网 Spring Boot 入门和进阶

课程链接地址:

  • 1、2小时学会Spring Boot
  • 2、Spring Boot进阶之Web进阶

目录

  • 入门
    • 1.第一个SpringBoot程序
    • 2.自定义属性配置
    • 3.Controller的使用
    • 4.Spring-data-jpa
  • 进阶
    • 5.表单验证
    • 6.AOP处理请求
    • 7.统一异常处理
    • 8.单元测试

1.第一个SpringBoot程序

1.1创建新的工程步骤:

创建新工程

1.2修改maven的默认选项,不修改的话,会报错误找不到spring*的包。

maven

1.3目录结构

目录结构

1.4pom.xml文件

  • 默认生成,无需修改

1.5程序启动入口

  • GirlApplication类上有标注@SpringBootApplication,可以右键单击此类进行启动项目。

1.6添加新的访问

  • 给新的Controller类加上注解@RestController
  • 给say()方法加上注解@RequestMapping(),value是路径,method是请求方式。

这里写图片描述

  • 启动项目后,访问http://127.0.0.1:8080/hello

2.自定义属性配置

2.1用properties文件

这里写图片描述

2.2用yml文件

  • 格式相对于properties文件更简便。
  • 关键词:空格+值

这里写图片描述

2.3用注入方式配置变量

  • 在注入时定义变量类型,配置时不用定义。例如图中的cupSize,并不是在配置文件中定义的类型,而是在Controller类引入时定义的,private String cupSize
  • 也可以在配置文件中,再使用配置。

这里写图片描述

2.4配置文件的分组配置使用

  • 配置文件的属性分组
  • 创建属性类,加入注解@Component,@ConfigurationProperties(prefix = “girl”)
  • Controller类,@Autowired注解引用属性类对象,注意给引用的类加上@Component注解,这里是GirlProperties类

这里写图片描述

2.5不同环境下不同配置的用法。

这里写图片描述

  • 不同的启动方式
    • 命令行启动prod环境
      • 项目目录下输入mvn install,等待maven编译完成
      • 输入java -jar target/girl-0.0.1-SHAPSHOT.jar –spring.profiles.active=prod,项目启动
    • IDE中启动dev环境
    • 分别访问 http://127.0.0.1:8080/hello,http://127.0.0.1:8081/hello,得到2个环境下的结果。

3.Controller的使用

  • @Controller:处理http请求
  • @RestController:Spring4之后新加的注解,原来返回json需要@ResponseBody配合@Controller
  • @RequestMapping:配置url映射

3.1Controller

  • 使用模板,类似于jsp页面,pom文件中加入模板引擎thymeleaf依赖。
<!--spring官方的模板,因为用模板会影响性能,所以不建议使用,改用前后端分离Restful--><dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>
  • resources目录下,加入文件夹templates,加入index.html页面。
 <h1>hello Spring Boot!</h1>
  • Controller类中。方法返回值为return “index”;
@Controllerpublic class HelloController {    //使用模板 返回index.html    @RequestMapping(value = "{"/hi","/hello"}",method = RequestMethod.GET)    public String say() {        return "index";    }}
  • 访问 http://127.0.0.1:8080/hello,得到index.html显示的结果。

3.2RestController=Controller+ResponseBody

3.3RequestMapping

  • @PathVariable获取url中的数据,请求地址:/http/say/10
  • @RequestParam获取请求参数的值,请求地址:/http/say?id=10
  • @GetMapping组合注解,@RequestMapping(value = “{“/say”}”,method = RequestMethod.GET)简写为@GetMapping(value = “/say”)
@GetMapping(value = "/say/{id}")//@GetMapping(value = "/{id}/say")//请求地址:/http/say/10public String say(@PathVariable("id") Integer myId) {    return "id: " + myId;}//@RequestMapping(value = "/say2",method = RequestMethod.GET)//请求地址:/http/say2?id=10,required是否必传,defaultValue默认值,不能是int,需要是字符"0"public String say2(@RequestParam(value="id",required=false,defaultValue="0") Integer myId) {    return "id: " + myId;}

4.数据库操作Spring-data-jpa

  • jpa定义了一系列对象持久化的标准,可以看做是spring对hibernate的整合。

4.1 RESTful API设计

这里写图片描述

4.2 添加依赖和配置文件

  • pom.xml文件,加入jpa依赖
<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-jpa</artifactId></dependency>
  • application.yml文件,加入datasource和jpa配置
    • ddl-auto的参数,常用的有create,update
  datasource:    driver-class-name: com.mysql.jdbc.Driver    url: jdbc:mysql://127.0.0.1:3306/dbgirl    username: root    password: root  jpa:    hibernate:      ddl-auto: update    show-sql: true

4.3 创建实体类

  • 新建Girl类,并加上@Entity,类中的属性值对应数据库表中的字段。
  • @GeneratedValue注释为自增长,
  • @Id表示id字段,
  • 必须要有空参数的构造方法。
@Entitypublic class Girl {    @Id    @GeneratedValue//自增长注解    private Integer id;    //@NotBlank(message = "这个字段必须传")    private String cupSize;    @Min(value = 18, message = "未成年少女禁止入门")    private Integer age;    public Girl() {}    public Integer getId() {return id;}    public void setId(Integer id) {this.id = id;}    public String getCupSize() {return cupSize;}    public void setCupSize(String cupSize) {this.cupSize = cupSize;}    public Integer getAge() {return age;}    public void setAge(Integer age) {this.age = age;}}

4.4 Controller类中写处理方法

  • 创建Controller类,根据RESTful API,创建增删改查方法。
  • 创建Repository接口,继承JpaRepository<Girl, Integer>。括号里是我们的类类型和id类型。
  • 如果JpaRepository中的方法不够用,就在自己的Repository接口中扩展新的方法。public List<Girl> findByAge(Integer age)
@RestControllerpublic class GirlController {    @Autowired    private GirlRepository girlRepository;    //查询所有女生,get方式,/girls    @GetMapping(value="/girls")    public List<Girl> girlList(){        return girlRepository.findAll();    }    //添加一个女生,post方式,/girls    @PostMapping(value = "/girls")    public Girl girlAdd(@RequestParam("cupSize") String cupSize,                        @RequestParam("age") Integer age){        Girl girl = new Girl();        girl.setCupSize(cupSize);        girl.setAge(age);        return girlRepository.save(girl);    }    //查询一个    @GetMapping(value="/girls/{id}")    public Girl girlFindOne(@PathVariable("id") Integer id){        return girlRepository.findOne(id);    }    //更新    @PutMapping(value="/girls/{id}")    public Girl girlUpdate(@PathVariable("id") Integer id,                           @RequestParam("cupSize") String cupSize,                           @RequestParam("age") Integer age){        Girl girl = new Girl();        girl.setid(id);        girl.setCupSize(cupSize);        girl.setAge(age);        return girlRepository.save(girl);    }    //删除    @DeleteMapping(value="/girls/{id}")    public String girlDelete(@PathVariable("id") Integer id){        girlRepository.delete();    }    //通过年龄查询出列表    @GetMapping(value="/girls/age/{age}")    public List<Girl> girlListByAge(@PathVariable("age") Integer age){        return girlRepository.findByAge(age);    }}//自己写子类来扩展方法。public interface GirlRepository extends JpaRepository<Girl, Integer> {    //扩展JpaRepository方法,通过年龄来查询    public List<Girl> findByAge(Integer age);}
  • put方式,需要选择x-www-form-urlencoded,不能选择form-data,multipart/form-data与x-www-form-urlencoded区别:
    • multipart/form-data:既可以上传文件等二进制数据,也可以上传表单键值对,只是最后会转化为一条信息;
    • x-www-form-urlencoded:只能上传键值对,并且键值对都是间隔分开的。

4.5 事务管理

  • 给自己的业务方法加上@Transactional,一般只有查询的时候不用加事务。
  • 数据库中cupSize字段,改成1个字符长度。插入girlB数据就不成功了。
@Servicepublic class GirlService {    @Autowired    private GirlRepository girlRepository;    @Transactional    public void insertTwo() {        Girl girlA = new Girl();        girlA.setCupSize("A");        girlA.setAge(18);        girlRepository.save(girlA);        Girl girlB = new Girl();        girlB.setCupSize("BBBB");        girlB.setAge(19);        girlRepository.save(girlB);    }}
@RestControllerpublic class GirlController {    ……    @Autowired    private GirlService girlService;    ……    //事务管理业务方法    @PostMapping(value = "/girls/two")    public void girlTwo() {        girlService.insertTwo();    }}
  • 启动项目后访问 http://127.0.0.1:8080/girls/two,结果是数据插不成功的。

PS:项目进行分层整理,然后进入下个阶段

项目进行分层整理

5.表单验证

5.1 修改添加方法,参数改为实体对象

    /*修改添加的方法    @PostMapping(value = "/girls")    public Girl girlAdd2(@RequestParam("cupSize") String cupSize,@RequestParam("age") Integer age){        Girl girl= new Girl();        girl.setCupSize(cupSize);        girl.setAge(age);        return girlRepository.save(girl);    }*/    //修改后的方法,参数是一个实体    @PostMapping(value = "/girls")    public Girl girlAdd(Girl gril) {        girl.setCupSize(girl.getCupSize());        girl.setAge(girl.getAge());        return girlRepository.save(girl);    }

5.2 给添加的对象加验证

* Girl类中给age变量加18岁限制条件。 ` @Min(value = 18,message = "未成年少女禁止入门")`* GirlController类中添加BindingResult参数及方法,验证的结果会放到这个对象中。
    //Girl类中给age变量加18岁限制条件。    private String cupSize;    @Min(value = 18,message = "未成年少女禁止入门")    private Integer age;    //GirlController类中添加BindingResult参数及方法。    @PostMapping(value = "/girls")    public Girl girlAdd(@Valid Girl girl, BindingResult bindingResult) {        if (bindingResult.hasErrors()){            System.out.println(bindingResult.getFieldError().getDefaultMessage());            return null;        }        girl.setCupSize(girl.getCupSize());        girl.setAge(girl.getAge());        return girlRepository.save(girl);    }

6.AOP处理请求

  • 用来进行统一的操作处理

6.1 一、pom.xml文件添加依赖

        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-aop</artifactId>        </dependency>

6.2 二、启动类GirlApplication.class上加注解,但是aop不用加

6.3 三、建立处理文件HttpAspect.class

  • @Before和@After注解,可以增加对Controller类中方法访问时需要进行的操作。
  • 增加@Pointcut后,可以在切面上操作。
  • logger.info(“这个方法可以打印日志”);,这个方法可以打印日志
package com.imooc.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//1.加入此注解@Component//2.将HttpAspect 加入到spring容器public class HttpAspect {    private final  static Logger logger= LoggerFactory.getLogger(HttpAspect.class);    @Pointcut("execution(public * com.imooc.controller.GirlController.*(..))")    public void log(){    }    //@Before("execution(public * com.imooc.controller.GirlController.girlList(..))")//拦截getList方法的任何参数都拦截    //@Before("execution(public * com.imooc.controller.GirlController.*(..))")//拦截所有方法    @Before("log()")    public void doBefore(){        //System.out.println("Before1111");        logger.info("doBefore1111");    }    //@After("execution(public * com.imooc.controller.GirlController.*(..))")    @After("log()")    public  void doAfter(){        //System.out.println("After22222");        logger.info("doAfter2222");    }}

6.4 四、测试

  • 给Controller.class中的getList()方法,添加查看顺序的语句
    private final static Logger logger = LoggerFactory.getLogger(GirlController.class);    /**     * 查询所有女生列表     * 和girlAdd()访问地址相同,注意用get方式提交是查询。post是添加     * @return     */    @GetMapping(value = "/girls")    public List<Girl> girlList() {        //System.out.println("getList 查看执行顺序");        logger.info("getList 执行");        return girlRepository.findAll();    }

6.5 扩展

  • 在HttpAspect文件中,处理http请求头中的路径url,method,ip,类方法,参数,获取返回的对象等。
    //@Before("execution(public * com.imooc.controller.GirlController.girlList(..))")//拦截getList方法的任何参数都拦截    //@Before("execution(public * com.imooc.controller.GirlController.*(..))")//拦截所有方法    @Before("log()")    public void doBefore(JoinPoint joinPoint){        //System.out.println("Before1111");        //logger.info("doBefore1111");        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();        HttpServletRequest request = attributes.getRequest();        //url        logger.info("url={}",request.getRequestURL());        //method        logger.info("method={}",request.getMethod());        //ip        logger.info("ip={}",request.getRemoteAddr());        //类方法        logger.info("class_method={}",joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName());        //参数        logger.info("args={}",joinPoint.getArgs());    }    //@After("execution(public * com.imooc.controller.GirlController.*(..))")    @After("log()")    public  void doAfter(){        //System.out.println("After22222");        logger.info("doAfter2222");    }    //获取返回的内容,比如此实例中返回的json对象    @AfterReturning(pointcut = "log()",returning="object")    public void doAfterReturning(Object object){        logger.info("response={}",object);//Girl实体类中没有toString()方法时,打印对象内存地址        logger.info("response={}",object.toString());//Girl实体类中添加toString方法,    }
  • get方式访问http://127.0.0.1:8080/girls/7,得到如下结果:

log日志

7.统一异常处理

7.1 异常情况模拟

  • 给Girl类添加金额属性,加上必传验证,生成get/set方法。
@Id@GeneratedValueprivate Integer id;@NotBlank(message = "这个字段必须传")private String cupSize;@Min(value = 18, message = "未成年少女禁止入门")private Integer age;@NotNull(message = "金额必传")private Double money;
  • 直接测试新增girlAdd()方法,不传金额,前台返回错误页面500。
  • 控制台是空指针异常,因为不能通过表单验证,girlAdd()方法返回的是null对象,HttpAspect类中的doAfterReturning()方法接收的是null。(注释logger.info()语句可以解决,此为忽略的方法。)

7.2定义发生错误或异常时返回的数据封装,格式

  • 控制台打印getDefaultMessage()结果,改为返回给前台。
  • 方法返回值改Girl为Object,getDefaultMessage()方法返回的是String类型,save()方法返回Girl类型,所有就定义为Object。
/*** 添加一个女生* @return*/@PostMapping(value = "/girls")//注意用get方式提交是查询。post是添加public Object girlAdd(@Valid Girl girl, BindingResult bindingResult) {    if (bindingResult.hasErrors()) {        return bindingResult.getFieldError().getDefaultMessage();    }    return girlRepository.save(girl);}
  • 返回的格式整理如下,返回3个字段的数据。
//错误时                                       //正确时{                                           {    "code": 1,                                  "code": 0,          "msg": "金额必传",                          "msg": "成功",            "data": null                                "data": {       }                                                   "id": 11,                                                    "cupSize": "A",                                                    "age": 29,                                                    "money": 300                                                }                                            }
  • domain包下,新建Result类,用来定义结果对象。
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;}}
  • 再次改造Controller
@PostMapping(value = "/girls")//注意用get方式提交是查询。post是添加public Result<Girl> girlAdd(@Valid Girl girl, BindingResult bindingResult) {    if (bindingResult.hasErrors()) {        Result result = new Result();        result.setCode(1);        result.setMsg(bindingResult.getFieldError().getDefaultMessage());        return result;    }    Result result = new Result();    result.setCode(0);    result.setMsg("成功");    result.setData(girlRepository.save(girl));    return result;}
  • 因为重复代码,写一个ResultUtil工具类
public class ResultUtil {    public static Result succss(Object object){        Result result = new Result();        result.setCode(0);        result.setMsg("成功");        result.setData(object);        return result;    }    //成功时也可能不含Object    public static Result success(){        return  succss(null);    }    public static Result error(Integer code,String msg){        Result result = new Result();        result.setCode(code);        result.setMsg(msg);        return result;    }}
  • 用ResultUtil工具类,简化Controller代码
public Result<Girl> girlAdd(@Valid Girl girl, BindingResult bindingResult) {    if (bindingResult.hasErrors()) {        return ResultUtil.error(1,bindingResult.getFieldError().getDefaultMessage());    }    return ResultUtil.succss(girlRepository.save(girl));}

7.3在Service类中写自己的业务逻辑

  • 业务需求,获取某女生的年龄并判断:
    • 小于10,返回“应该在上小学”。
    • 大于10且小于16,返回“可能在上初中”。
@GetMapping(value = "/girls/getAge/{id}")public void getAge(@PathVariable("id") Integer id) throws Exception {    girlService.getAge(id);}
  • 在Service中写业务逻辑
public void getAge(Integer id) throws Exception {    Girl girl = girlRepository.findOne(id);    Integer age = girl.getAge();    if (age < 10) {        //返回,你还在上小学吧        throw new Exception("你还在上小学吧");    } else if (age > 10 && age < 16) {        //返回,你可能在上初中        throw new Exception("你可能上初中");    }}
  • 请求id号为12号的girl信息,前台控制台返回的结果:

这里写图片描述

7.4异常捕获

  • 通过上面的异常,里面的格式并不是我们想要的,所以需要自己写一个类捕获异常类:ExceptionHandle。
  • 因为返回浏览器的数据是json,而这个类又没有RestController注释,需要给ExceptionHandle.handle方法加入ResponseBody注解。
@ControllerAdvicepublic class ExceptionHandle {    @ExceptionHandler(value = Exception.class)//声明捕获哪个异常类    @ResponseBody//返回浏览器是json,而这个类又没有RestController注释,就得加入ResponseBody注释    public Result handle(Exception e) {        return ResultUtil.error(100,e.getMessage());    }}
  • 执行以后,前台和控制台的返回结果就是我们想要的格式了。前台是3个字段数据的json,控制台不报异常信息。

这里写图片描述

  • 业务处理逻辑保留在一个环节:Service.getAge(),验证<10,就直接往外抛异常,Controller.getAge()方法调用Service.getAge()方法,不用处理,也是抛出异常,最终由handle捕获处理。

7.5自定义异常

  • 创建自定义异常类,定义一个code变量,记录错误代码。
  • Spring框架中,自定义异常只有继承RuntimeException才能支持事务回滚。
public class GirlException extends RuntimeException {    //spring框架中,自定义异常只有继承RuntimeException才能支持事务回滚    private Integer code;//定义一个code变量,错误代码    public GirlException(Integer code, String message) {        super(message);        this.code = code;    }    public Integer getCode() {return code;}    public void setCode(Integer code) {this.code = code;}}
  • 使用自定义异常后的Service
public void getAge(Integer id) throws Exception {    Girl girl = girlRepository.findOne(id);    Integer age = girl.getAge();    if (age < 10) {        //返回,你还在上小学吧        throw new GirlException(100, "你还在上小学吧");    } else if (age > 10 && age < 16) {        //返回,你可能在上初中        throw new GirlException(101, "你可能上初中");    }}
  • 捕获异常类,加入代码if (e instanceof GirlException){},作用是判断异常是不是自定义的异常。
  • 加入日志记录,可以让控制台打印出异常信息,不加控制台是看不到的。
@ControllerAdvicepublic class ExceptionHandle {    private final static Logger logger = LoggerFactory.getLogger(ResponseBody.class);    @ExceptionHandler(value = Exception.class)//声明捕获哪个异常类    @ResponseBody//返回浏览器是json,而这个类又没有RestController注释,就得加入ResponseBody注释    public Result handle(Exception e) {        if (e instanceof GirlException) {            GirlException girlException = (GirlException) e;            return ResultUtil.error(girlException.getCode(), girlException.getMessage());        } else {            logger.error("【系统异常】{}", e);            return ResultUtil.error(-1, "未知错误");        }    }}

7.6异常错误代码代码管理

  • 创建枚举类型的类来管理错误代码。
public enum ResultEnum {    UNKONW_ERROR(-1, "未知错误"),    SUCCESS(0, "成功"),    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的构造方法。
public GirlException(ResultEnum resultEnum) {    super(resultEnum.getMsg());    this.code = resultEnum.getCode();}
  • 修改Service代码,参数改为枚举类型的常量
if (age < 10) {    //throw new GirlException(100, "你还在上小学吧");    throw new GirlException(ResultEnum.PRIMARY_SCHOOL);} else if (age > 10 && age < 16) {    //throw new GirlException(101, "你可能上初中");    throw new GirlException(ResultEnum.MIDDLE_SCHOOL);}

8.单元测试

  • 使用场景,测试Service中的方法,通过id查询一个女生信息并返回。
  • 使用场景,测试Controller方法,浏览器返回的状态码,返回的内容。

8.1 测试方法1,通过test目录下的测试类service:

  • 创建Test类,加上注解@RunWith(SpringRunner.class),表示使用测试类(底层使用junit),注解@SpringBootTest,能够启动整个工程。其他测试如junit测试,@Test,Assert断言。
@RunWith(SpringRunner.class)//使用测试@SpringBootTest//启动整个spring工程public class GirlServiceTest {    @Autowired    private GirlService girlService;    @Test    public void findOneTest(){        Girl girl = girlService.findOne(12);        Assert.assertEquals(new Integer(9),girl.getAge());    }}

8.2 测试方法2,AutoConfigureMockMvc测试controller:

  • 通过IDEA自带测试方法,右键要测试的方法,选择Go To–Test–选择要测试的方法,然后会生成相应的测试类。
  • 给类加上@AutoConfigureMockMvc注解,结合mvc.perform()
  • MockMvcResultMatchers.status().isOk()测试返回状态码,
  • MockMvcResultMatchers.content().string(“abc”)测试返回内容。
@RunWith(SpringRunner.class)@SpringBootTest@AutoConfigureMockMvcpublic class GirlControllerTest {    /*    //传统测试方法    @Autowired    private GirlController girlController;    @Test    public void girlList() throws Exception {        girlController.girlList();    }*/    @Autowired    private MockMvc mvc;    @Test    public void girlList() throws Exception {        mvc.perform(MockMvcRequestBuilders.get("/girls"))                .andExpect(MockMvcResultMatchers.status().isOk())//测试返回状态码                .andExpect(MockMvcResultMatchers.content().string("abc"));//测试返回内容    }}

8.2 测试方法3,通过maven命令:

  • maven clean package,打包命令。
  • 中途如果出现测试用例失败,会增加打包时间,查看日志会看到失败的原因,注释掉测试中的错误语句,比如此例中的andExpect(MockMvcResultMatchers.content().string("abc")),可以缩短这个时间。
  • 还可以跳过单元测试,使用命令maven clean package -Dmaven.test.skip=true