SpringBoot进阶之Web进阶

一.表单验证

1.为要校验的对象添加对应的校验注解

  • 使用@Min(value=18,message="未成年少女金之入内")标记对应类中的属性。注解表示age最小值为18,错误提示为:未成年少女禁止入内

  • Girl类实例代码

    package com.study.demo.domain;
    
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    import javax.validation.constraints.Min;
    
    @Entity
    public class Girl{
        @Id
        @GeneratedValue
        private Integer id;
        private String cupSize;
    
        //表示age最小值为18,错误提示为:未成年少女禁止入内
        @Min(value=18,message = "未成年少女禁止入内")
        private Integer age;
    
        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;
        }
    
        @Override
        public String toString() {
            return "Girl{" +
                    "id=" + id +
                    ", cupSize='" + cupSize + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    

2.在对应方法的入参中使用@Vaild注解标记该对应需要被验证,并通过BindingResult类型参数接受验证结果

  • 结果对象的hasErrors()方法用于判断是否发生错误

  • 结果对象的getFieldError().getDefaultMessage()方法获得对应的错误信息

  • 对应方法的实例代码

    //新增女生
    @PostMapping(value="/girls")
    public Girl girlAdd(@Valid Girl girl, BindingResult bindingResult){//@Valid注解表示要验证的对象为girl,验证结果会返回到参数bindingResult对象中
        //通过对象的hasErrors()方法获得是否发生错误
        if(bindingResult.hasErrors()){
            //并通过getFieldError().getDefaultMessage()方法获得对应的错误信息
            System.out.println(bindingResult.getFieldError().getDefaultMessage());
            return null;
        }
        girl.setCupSize(girl.getCupSize());
        girl.setAge(girl.getAge());
        return girlRepository.save(girl);//方法返回的就是添加的对象
    }
    
  • 当发送post请求时,若cupSize属性超过18则会在控制台输出“未成年少女禁止入内”,并返回null值

二.AOP统一处理请求日志

1.什么是AOP?

  • AOP是一种编程范式,与语言无关,是一种程序设计思想
  • 面向切面(AOP)Aspect Oriented Programming:将通用逻辑从业务逻辑中分离出来
  • 面向对象(OOP)Object Oriented Programming
  • 面向过程(POP)Procedure Oriented Programming

2.网络请求的生命周期

收到HttpRequest请求 —> 记录请求 —> 处理网络请求 —> 生成HttpResponse —> 记录回复

3.操作数据库的生命周期

收到数据库操作请求 —> 记录请求 —> 增删改查 —> 生成处理结果 —> 记录回复

4.记录每一个http请求

  • 添加依赖

    <dependency>
    	<groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-aopartifactId>
    dependency>
    
  • 建立处理文件:aspect/HttpAspect.java

    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//标记类为Aspect类
    @Component//引入到Spring容器中
    public class HttpAspect {
    
        //org.slf4j.Logger是Spring自带的日志记录框架
        private final static Logger logger = LoggerFactory.getLogger(HttpAspect.class);
    
        @Pointcut("execution(public * com.study.demo.controller.HelloController.girlList(..))")//.girlList(..)表示girlList方法不管是任何参数都会被拦截;.*(..)则表示该类的所有方法都会被拦截
        public void log(){
    
        }
    
        //定义在http请求到方法之前将内容记录下来
        @Before("log()")
        public void doBefore(){
            logger.info("记录日志");//使用Logger的方法记录日志
        }
    
        //定义在执行完方法后调用
        @After("log()")
        public void doAfter(){
            logger.info("执行方法之后的调用...");
        }
    }
    
  • 访问接口时的显示内容(可以看出使用logger.info()方法打印的内容带有时间等相关信息)

    2018-09-19 12:58:55.250  INFO 44464 --- [nio-8082-exec-2] com.study.demo.aspect.HttpAspect         : 记录日志
    2018-09-19 12:58:55.253  INFO 44464 --- [nio-8082-exec-2] c.study.demo.controller.HelloController  : 执行到GirlList
    2018-09-19 12:58:55.280  INFO 44464 --- [nio-8082-exec-2] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
    Hibernate: select girl0_.id as id1_0_, girl0_.age as age2_0_, girl0_.cup_size as cup_size3_0_ from girl girl0_
    2018-09-19 12:58:55.364  INFO 44464 --- [nio-8082-exec-2] com.study.demo.aspect.HttpAspect         : 执行方法之后的调用...
    
  • 调整@Before注解的方法执行指定内容

    @Before("log()")
    public void doBefore(JoinPoint joinPoint){
        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());
        //请求的类方法,通过当前类的参数传入的JoinPoint对象获取对应的内容
        //joinPoint.getSignature().getDeclaringTypeName()获取的是类名
        //joinPoint.getSignature().getName()获取类方法名
        logger.info("class_method={}",joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName());
        //参数
        logger.info("args={}",joinPoint.getArgs());
    }
    
  • 通过@AfterReturning注解获得方法返回的内容

    //参数1为方法的入参变量名,参数2为使用的连接点方法名
    @AfterReturning(returning="object",pointcut="log()")
    //返回的内容可以是很多,但是对于程序而言他们都是对象
    public void doAfterReturning(Object object){
        logger.info("response={}",object);
    }
    
  • 访问接口时显示的内容

    2018-09-19 13:20:31.876  INFO 44599 --- [nio-8082-exec-1] com.study.demo.aspect.HttpAspect         : url=http://localhost:8082/girls
    2018-09-19 13:20:31.876  INFO 44599 --- [nio-8082-exec-1] com.study.demo.aspect.HttpAspect         : method=GET
    2018-09-19 13:20:31.876  INFO 44599 --- [nio-8082-exec-1] com.study.demo.aspect.HttpAspect         : ip=0:0:0:0:0:0:0:1
    2018-09-19 13:20:31.878  INFO 44599 --- [nio-8082-exec-1] com.study.demo.aspect.HttpAspect         : class_method=com.study.demo.controller.HelloController.girlList
    2018-09-19 13:20:31.878  INFO 44599 --- [nio-8082-exec-1] com.study.demo.aspect.HttpAspect         : args={}
    2018-09-19 13:20:31.882  INFO 44599 --- [nio-8082-exec-1] c.study.demo.controller.HelloController  : 执行到GirlList
    2018-09-19 13:20:31.911  INFO 44599 --- [nio-8082-exec-1] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
    Hibernate: select girl0_.id as id1_0_, girl0_.age as age2_0_, girl0_.cup_size as cup_size3_0_ from girl girl0_
    2018-09-19 13:20:31.991  INFO 44599 --- [nio-8082-exec-1] com.study.demo.aspect.HttpAspect         : 执行方法之后的调用...
    2018-09-19 13:20:31.991  INFO 44599 --- [nio-8082-exec-1] com.study.demo.aspect.HttpAspect         : response=[]
    

三.统一异常处理

统一异常处理的实例

  • 定义统一异常返回对象/src/main/java/com/study/demo/domain/Result.java

    package com.study.demo.domain;
    /**
     * Http请求返回的最外层对象
     */
    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;
        }
        @Override
        public String toString() {
            return "Result{" +
                    "code=" + code +
                    ", msg='" + msg + '\'' +
                    ", data=" + data +
                    '}';
        }
    }
    
  • 定义错误对象生成工具方法/src/main/java/com/study/demo/utils/ResultUtil.java

    package com.study.demo.utils;
    import com.study.demo.domain.Result;
    public class ResultUtil {
        public static Result success(Object object){
            Result result = new Result();
            result.setCode(0);
            result.setMsg("成功");
            result.setData(object);
            return result;
        }
        public static Result success(){
            return success(null);
        }
        public static Result error(Integer code,String msg){
            Result result = new Result();
            result.setCode(code);
            result.setMsg(msg);
            result.setData(null);
            return result;
        }
    }
    
  • 自定义异常,用于传递对应的错误编号/src/main/java/com/study/demo/exception/GirlException.java

    package com.study.demo.exception;
    import com.study.demo.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;
        }
    }
    
  • 定义异常处理内容,在方法抛出异常时执行的对应处理方法/src/main/java/com/study/demo/handle/ExceptionHandle.java

    package com.study.demo.handle;
    import com.study.demo.domain.Result;
    import com.study.demo.exception.GirlException;
    import com.study.demo.utils.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
    public class ExceptionHandle {
        public static final Logger logger = LoggerFactory.getLogger(ExceptionHandle.class);
        //声明要捕获哪个异常类
        @ExceptionHandler(value = Exception.class)
        //由于要返回的是一个json,但是类上方又没有@RestController注解,故需要用ResponseBody修饰
        @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,"未知错误");
            }
        }
    }
    
    
  • 在Controller中添加判断对象年龄并抛出异常测试方法/src/main/java/com/study/demo/controller/HelloController.java

    package com.study.demo.controller;
    
    import com.study.demo.domain.Girl;
    import com.study.demo.domain.Result;
    import com.study.demo.repository.GirlRepository;
    import com.study.demo.service.GirlService;
    import com.study.demo.utils.ResultUtil;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.context.properties.bind.BindResult;
    import org.springframework.validation.BindingResult;
    import org.springframework.web.bind.annotation.*;
    
    import javax.validation.Valid;
    import java.util.List;
    import java.util.Optional;
    
    @RestController
    public class HelloController {
    
        public final static Logger logger = LoggerFactory.getLogger(HelloController.class);
    
        @Autowired
        private GirlRepository girlRepository;
        @Autowired
        public GirlService girlService;
    
        //查询所有女生列表
        @GetMapping(value="/girls")
        public List<Girl> girlList(){
            logger.info("执行到GirlList");
            return girlRepository.findAll();
        }
    
        //新增女生
        @PostMapping(value="/girls")
        public Result girlAdd(@Valid Girl girl, BindingResult bindingResult){//@Valid注解表示要验证的对象为girl,验证结果会返回到参数bindingResult对象中
            //通过对象的hasErrors()方法获得是否发生错误
            if(bindingResult.hasErrors()){
                return ResultUtil.error(1,bindingResult.getFieldError().getDefaultMessage());
            }
            girl.setCupSize(girl.getCupSize());
            girl.setAge(girl.getAge());
            return ResultUtil.success(girlRepository.save(girl));//方法返回的就是添加的对象
        }
    
        //查询一个女生
        @GetMapping(value="/girls/{id}")
        public Optional<Girl> girlFindOne(@PathVariable("id")Integer id){
            Optional<Girl> girl = girlRepository.findById(id);
            return girl;
        }
    
        //更新
        @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 void girlDelete(@PathVariable("id")Integer id){
            girlRepository.deleteById(id);
        }
    
        //通过年龄查询女生列表
        @GetMapping(value="/girls/age/{age}")
        public List<Girl>girlListByAge(@PathVariable("age")Integer age){
            return girlRepository.findByAge(age);
        }
    
        //添加两条数据
        @PostMapping(value="/girls/two")
        public void girlTwo(){
            girlService.insertTwo();
        }
    
        //判断对象年龄并抛出异常测试方法
        @GetMapping(value = "girls/getAge/{id}")
        public void getAge(@PathVariable("id")Integer id) throws Exception{
            girlService.getAge(id);
        }
    }
    
  • 在service中添加对应处理方法,针对不同情况的age内容抛出不同错误码的异常/src/main/java/com/study/demo/service/GirlService.java

    package com.study.demo.service;
    import com.study.demo.domain.Girl;
    import com.study.demo.enums.ResultEnum;
    import com.study.demo.exception.GirlException;
    import com.study.demo.repository.GirlRepository;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import java.util.Optional;
    @Service
    public class GirlService {
        @Autowired
        private GirlRepository girlRepository;
        //添加@Transactional事务注解
        @Transactional
        public void insertTwo(){
            Girl girlA = new Girl();
            girlA.setCupSize("A");
            girlA.setAge(18);
            girlRepository.save(girlA);
    
            Girl girlB = new Girl();
            girlB.setCupSize("Fffffff");
            girlB.setAge(19);
            girlRepository.save(girlB);
        }
        public void getAge(Integer id) throws GirlException{
            Optional<Girl> girl = girlRepository.findById(id);
            Integer age = girl.get().getAge();
            if(age<10){
                //返回你还在上小学吧——使用枚举的形式获得
                throw new GirlException(ResultEnum.PRIMARY_SCHOOL);
            }else if(age>=10&&age<16){
                //返回你可能在上初中
                throw new GirlException(ResultEnum.MIDDLE_SCHOOL);
            }
        }
    }
    
  • 定义枚举类型,将错误编号与错误信息统一管理/src/main/java/com/study/demo/enums/ResultEnum.java

    package com.study.demo.enums;
    //使用枚举将错误编号与错误信息统一管理
    public enum ResultEnum {
        UNKOWN_ERROR(-1,"未知错误"),
        SUCCESS(0,"成功"),
        PRIMARY_SCHOOL(100,"你可能在上小学~"),
        MIDDLE_SCHOOL(101,"你可能在上初中~")
        ;
        private Integer code;
        private String msg;
        //枚举的使用基本上不用set方法,而使用构造方法
        ResultEnum(Integer code, String msg) {
            this.code = code;
            this.msg = msg;
        }
        public Integer getCode() {
            return code;
        }
        public String getMsg() {
            return msg;
        }
    }
    

四.单元测试

1.对service进行测试方式一:创建/src/test/java/com/study/demo/GirlServiceTest.java文件编写findOneTest方法进行测试

  • 在GirlService中编写需要测试的方法

    package com.study.demo.service;
    import com.study.demo.domain.Girl;
    import com.study.demo.enums.ResultEnum;
    import com.study.demo.exception.GirlException;
    import com.study.demo.repository.GirlRepository;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import java.util.Optional;
    @Service
    public class GirlService {
        @Autowired
        private GirlRepository girlRepository;
        //添加@Transactional事务注解
        @Transactional
        public void insertTwo(){
            Girl girlA = new Girl();
            girlA.setCupSize("A");
            girlA.setAge(18);
            girlRepository.save(girlA);
    
            Girl girlB = new Girl();
            girlB.setCupSize("Fffffff");
            girlB.setAge(19);
            girlRepository.save(girlB);
        }
        public void getAge(Integer id) throws GirlException{
            Optional<Girl> girl = girlRepository.findById(id);
            Integer age = girl.get().getAge();
            if(age<10){
                //返回你还在上小学吧
                throw new GirlException(ResultEnum.PRIMARY_SCHOOL);
            }else if(age>=10&&age<16){
                //返回你可能在上初中
                throw new GirlException(ResultEnum.MIDDLE_SCHOOL);
            }
        }
        /**
         * 通过Id查询一个女生的信息
         */
        public Optional<Girl> findOne(Integer id){
            return girlRepository.findById(id);
        }
    }
    
    
  • 编写GirlServiceTest方法进行测试(右击Run on findOneTest进行执行)

    package com.study.demo;
    import com.study.demo.domain.Girl;
    import com.study.demo.service.GirlService;
    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;
    import java.util.Optional;
    //表示当前是在测试环节里面跑的,底层使用JUnit测试工具
    @RunWith(SpringRunner.class)
    //将启动整个Spring的工程
    @SpringBootTest
    public class GirlServiceTest {
        @Autowired
        private GirlService girlService;
        @Test
        public void findOneTest(){
            Optional<Girl> girl = girlService.findOne(4);
            //断言的方式判断,要求参数都是Object
            Assert.assertEquals(new Integer(16),girl.get().getAge());
        }
    }
    

2.对service进行测试方法二:利用IDEA生成对应的测试方法

  • 在对应需要测试的方法上右击 -> Go To -> Test ->Create new Test -> 选择要测试的方法 -> OK
  • 系统会自动在test包下创建对应包即测试文件

3.对controller进行测试

  • 使用IDEA执行右击 -> Go To -> Test ->Create new Test -> 选择要测试的方法 -> OK生成对应的测试文件

  • 在测试文件类上不仅要添加@RunWith(SpringRunner.class)和@SpringBootTest注解,还要添加@AutoConfigurationMockMvc注解标注是controller的测试类,并使用MockMvc对象的perform方法进行相关测试内容的设置

    package com.study.demo.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;
    import static org.junit.Assert.*;
    @RunWith(SpringRunner.class)
    @SpringBootTest
    @AutoConfigureMockMvc
    public class HelloControllerTest {
        @Autowired
        private MockMvc mvc;
        @Test
        public void girlList() throws Exception{
            mvc.perform(MockMvcRequestBuilders.get("/girls"))//Get方法,请求地址为/girls
                    .andExpect(MockMvcResultMatchers.status().isOk())//表示期望返回的状态码为200
                    .andExpect(MockMvcResultMatchers.content().string("abc"));//表示期望返回的是abc字符串
        }
    }
    

4.执行mvn clean package进行打包的时候会自动执行对应的单元测试

  • 如果单元测试用例测试不通过则提示报错信息,打包失败
  • 如果单元测试用例测试通过则没有错误信息,打包成功
  • 当我们打包时希望不执行单元测试时,需要使用命令mvn clean package -Dmaven.test.skip=true跳过单元测试

你可能感兴趣的:(JAVA相关)