表单验证
在领域模型girl中(也就是有getset的类中)声明如下: @Min(value = 18, message = "未成年少女禁止入门")
private Integer age;
在控制器的验证方法中加入:
@PostMapping(value = "/girls")
public Result girlAdd(@Valid Girl girl, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return ResultUtil.error(1, bindingResult.getFieldError().getDefaultMessage());
}
girl.setCupSize(girl.getCupSize());
girl.setAge(girl.getAge());
return ResultUtil.success(girlRepository.save(girl));
}
@valid用于验证参数的正确性,BindingResult对象用来显示错误信息!
使用AOP处理请求
AOP是一种编程范式与语言无关,是一种程序设计思想。
面向切面(AOP)Aspect Oriented Programming
面向对象(OOP)Object Oriented Programming
面向过程(POP)Procedure Oriented Programming
面向过程到面向对象 换个角度看世界,换个姿势处理问题 将通用逻辑从业务逻辑中分离出来
使用方法:
引入依赖 starter-aop
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
创建aspect包(放处理文件,包名随意,自己明白就好)
然后在包内新建java类 在类前加上@Aspect @Component注解
然后在类中方法前加上以下代码
@Before("execution(public * com.hx.girl.GirlController.*(..))")
拦截GirlController中的所有方法,在方法运行前运行被Before注释的代码
execution()是最常用的切点函数,其语法如下所示:
整个表达式可以分为五个部分:
1、execution(): 表达式主体。
2、第一个*号:表示返回类型,*号表示所有的类型。
3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
4、第二个*号:表示类名,*号表示所有的类。
5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
@Pointcut 注解中填写的内容与 @Before @After 两注解是一样的
为了避免代码重复书写,定义一个公用方法,@Pointcut注解声明切入点
@Before @After 两注解直接复用该方法切入点
@Aspect
@Component
public class HttpAspect {
private final static Logger logger= LoggerFactory.getLogger(HttpAspect.class); //Logger引用org.slf4j 是Spring自带的日志框架
@Pointcut("execution(public * com.example.project.web.CustomerContorller.*(..))")
public void log(){
}
@Before("log()")
public void doBefore(){
logger.info("run before");
}
@After("log()")
public void doAfter(){
logger.info("run after");
}
}
用于记录http请求的信息的代码:
// 请求参数
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
// 请求对象
javax.servlet.http.HttpServletRequest request = attributes.getRequest();
// url
request.getRequestURL();
// method
request.getMethod();
// ip
request.getRemoteAddr();
// doBefore(JoinPoint joinPoint)
joinPoint.getSignature().getDeclaringTypeName() // 类名
joinPoint.getSignature().getName() // 类方法名
// 参数
joinPoint.getArgs()
//获取返回的内容
@AfterReturning(returning = "object", pointcut = "log()")
public void doAfterReturning(Object object){
logger.info("response={}",object.toString()); //添加toString()方法用于详细打印
}
写代码时发现重复时,即时优化。
写一个工具类,ResultUtil,成功时,传入data返回;失败时传入失败码和错误信息,返回
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);
return result;
}
}
写ExceptionHandle类来捕获异常,同时使用ResultUtil的方法使数据格式统一。
@ControllerAdvice
public class ExceptionHandle{
@ExceptionHandle(value = Exception.class)
@ResponseBody
public Result handle(Exception e){
return ResultUtil.error(100,e.getMessage());
}
}
当需要对不同错误设置不同error code时,Exception就无法满足,于是就需要自定义一个Exception
spring只对RuntimeException才会进行事务回滚。
public class GirlException extends RuntimeException{
private Integer code;
public GirlException(Integer code, String message){
super(message);
this.code = code;
}
...getset方法
}
@ControllerAdvice
public class ExceptionHandle{
@ExceptionHandle(value = Exception.class)
@ResponseBody
public Result handle(Exception e){
if(e instanceof GirlException){
GirlException ge = (GirlException) e;
return ResultUtil.error(ge.getCode(),ge.getMessage());
}else{
return ResultUtil.error(-1,"未知错误");
}
}
}
当抛的异常属于未知错误时,就会不知道具体错误信息,这时候就可以用日志将其打印记录下来。
@ControllerAdvice
public class ExceptionHandle{
private final static Logger logger= LoggerFactory.getLogger(ExceptionHandle.class);
@ExceptionHandle(value = Exception.class)
@ResponseBody
public Result handle(Exception e){
if(e instanceof GirlException){
GirlException ge = (GirlException) e;
return ResultUtil.error(ge.getCode(),ge.getMessage());
}else{
logger.error("[系统异常]{}",e);
return ResultUtil.error(-1,"未知错误");
}
}
}
ResultEnum 统一管理异常码和异常信息
public enum ResultEnum{
UNKOWN_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;
}
...get方法
}
GirlException(resultEnum) 构造方法传枚举就可以了
易读,便于管理
总结一下:
1、对外统一的Result 一些公司的工作前端做前端,后端做后端,所以后端需要提供统一格式的输出,才好让前端调用
2、抛出异常,统一处理,包装成Result 这样即便有异常也不会影响前端工程师的使用
3、因为原始的异常只有message,我们还需要异常码,所以要有一个自定义的异常类,方便个性化设置
4、统一处理时,从自己定义的异常中取出异常码和信息,用ResultUtil进行处理,得到Result
单元测试
测试GirlService 在test包中创建测试类 如果是用Idea编辑可以用右键GO TO ->Test 然后选择要测试的方法进行快速创建测试类@RunWith(SpringRunner.class)
@SpringBootTest
public class GrilServiceTest {
@Autowired
private GrilService grilService;
@Test
public void findOneTest(){
Girl girl = girlService.findOneTest(3);
Assert.assertEquals(new Integer(13),gril.getAge());
}
}
可以右键代码空白区域,选择Run "GrilServiceTest"测试整个类
或者右键要测试的方法,选择Run "findOneTest()"测试这个方法
测试controller 如果还用上面的那个方式测试就无法测试url的请求
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class GrilController {
@Autowired
private MockMvc mvc;
@Test
public void girlList() throws Exception {
mvc.perform(
MockMvcRequestBuilders.get("/girls")) //请求的url 如果请求是post,就改为.post("/girls"))
.andExpect(MockMvcResultMatchers.status().isOk()) //期望返回状态码是200
.andExpect(MockMvcResultMatchers.content().string("hi")); //期望返回内容是hi
}
}
在使用项目打包时,就会进行单元测试
mvn clean package
打包项目时跳过单元测试:
mvn clean package -Dmaven.test.skip=true