发表于: 2018-03-05 15:03:46
1 563
今天完成的事情
1.使用AOP处理请求
优化昨天的代码:
@Aspect
@Component
public class HttpAspect {
//这个slf4j的logger底层原理是logback,spring自带的框架
private final static Logger logger = LoggerFactory.getLogger(HttpAspect.class);
//====================================下面是新的方法,首先定义一个公用的log方法,
// 加上pointcut注解 表示切的是哪个点,现在是切的controller类
@Pointcut("execution(public * com.imooc.controller.GirlController.*(..))")
public void log(){
}
@Before("log()")
public void doBefore(JoinPoint joinPoint){
logger.info("111111111");}
@After("log()")
public void doAfter(){
logger.info("2222222222");}
}
这个还是很很好理解的 在方法之前之后分别做打印输出,这里需要注意的事情是logger的包 引入的是sfg4j的,因为这个是spring自带的框架,支持事件回滚,用起来比较舒服。
那么在aop这里的需求是这样的,来记录访问的url,方法,类名。ip地址,以及参数,以及最后再记录一下返回值。
@Aspect
@Component
public class HttpAspect {
//这个slf4j的logger底层原理是logback,spring自带的框架
private final static Logger logger = LoggerFactory.getLogger(HttpAspect.class);
//====================================下面是新的方法,首先定义一个公用的log方法,
// 加上pointcut注解 表示切的是哪个点,现在是切的controller类
@Pointcut("execution(public * com.imooc.controller.GirlController.*(..))")
public void log(){
}
@Before("log()")
public void doBefore(JoinPoint joinPoint){
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//这里的HttpServletRequest使用的是javax包下的
HttpServletRequest request = attributes.getRequest();
logger.info("url={}",request.getRequestURL());
logger.info("method={}",request.getMethod());
logger.info("ip={}",request.getRemoteAddr());
logger.info("class_method={}",joinPoint.getSignature().getDeclaringTypeName()+"."+
joinPoint.getSignature().getName());
logger.info("args={}",joinPoint.getArgs());
}
@After("log()")
public void doAfter(){
logger.info("2222222222");
}
//接下来是拿取返回值做一个日志记录
@AfterReturning(returning = "object",pointcut = "log()")
public void doAfterReturning(Object object){
logger.info("response={}",object.toString());
}
}
这里用object的话就是为了应对返回集合等的情况。
看一下运行效果吧:
这个切面呢,用起来就是舒服,想要的东西都有了。
2.进行统一异常的处理
首先来设计一种异常:
实体类里面写上这个
@Entity
public class Girl {
@Id
@GeneratedValue
private Integer id;
@NotBlank(message = "罩杯 这个字段必传")
private String cupSize;
@Min(value = 18,message = "未成年少女禁止入内")
private Integer age;
@NotNull(message = "金额必传")
private Integer money;
解释一下@NotBlank 这个东西主要用在String上面
@NotNull 用在基本类型上
这样的话呢,就是定义好了不传这两个字段或者属性的时候就会在控制台出现异常
上个图吧:
这是postman的测试:
输出结果是符合我们的预期的,这里其实在上述条件下输出不是这样的,应该是只有一句话“罩杯...”
而我做了一些处理,让他们格式统一
这是插入成功时候的状态,为了统一格式,加了一些东西才打到现在的效果,接下来就说怎么弄得:
我们希望弄成三部分 code msg 以及data,首先我们新建一个类:Result:
public class Result<T> {
private Integer code;
private String msg;
private T date;
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 getDate() {
return date;
}
public void setDate(T date) {
this.date = date;
}
}
这个类里面就有我们想要的三个部分的数据
然后对应的controller本来是这样写:我们让它返回一个Result类型的数据
但是这些代码都非常难受,因为它重复了很多,我们来进行一下优化:
做一个工具类:ResultUtil:
public class ResultUtil {
public static Result success(Object object){
Result result = new Result();
result.setCode(0);
result.setMsg("ojbk");
result.setDate(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;
}
}
那么最后的controller:
@PostMapping(value = "/girls")
//考虑一下参数很多的时候写这么多param很难受,所以改成对象
public Result<Girl> 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));
}
相比上面已经清晰了很多了
就是这样了,来简单总结一下每一步的作用
首先我们向设定一个特定的格式,也就是那个Result类,然后在controller里面对它的实例化对象赋值并返回到页面,其实本来这样就行了,但是能优化就优化,我们又写了Util类来对进行对Result的实例化对象赋值并返回的操作,简化controller,同时设定了三个方法,这就叫做封装好了。然后在新的controller里面直接调用Util的方法来完成我们的目的,就很舒服
这样的话就会看到一开始我的截图那样的页面了。
3.进行统一处理异常
首先我们在service里面写上逻辑:
这里若果用其他方法将会很痛苦,比如这里判断一次加个识别码,再去controller里面判断识别码来进行相关操作...想想都觉得麻烦对不对,所以我们这里直接抛出异常
那么异常链的传递就会传到controller里面,但是我不想在controller里面处理这个异常,于是再次抛出
@GetMapping(value = "/girls/getAge/{id}")
public void getAge(@PathVariable("id") Integer id)throws Exception{
girlService.getAge(id);
}
然后直接运行:查询一个小于12岁的数据:
可以看到已经输出了异常信息,与此对应的,控制台也会抛出异常。
而且这个时候我们要注意到数据的格式又变了,不是我们设定好的三段式了,这种情况就是因为我们没有对异常进行处理,直接往外抛的话就会这样
所以接下来就是对异常进行捕获处理了,首先新建一个ExceptionHandle类
在里面写一个捕获异常的方法,这里简单解释一下这几个注解的含义:
@ControllerAdvice是控制器增强的注解,配合@ExceptionHandler,可以处理全局异常
@ExceptionHandler单独使用的时候只能在当前controller里面来处理异常,其参数就是想要捕获的类
@ResponsBody在这里的话呢是为了返回一个Json数据到页面。
然后调用ResultUtil里面的方法,运行之后的效果:
可以看到格式回来了,美滋滋
此时控制台也不会报错了。
但是要注意到这个地方
这个异常抛出的视乎不能加参数了,而handler里面状态吗只有一个100,当然是每个异常对应一个code了,那么该怎么办呢?
我们自己来写一个自己的Exception:
这里需要注意的就是继承的类,选择这个因为支持事物回滚,这里的参数呢,继承来的有message,只需自己写一个code进去就好,接下来:
我么把抛出的异常改成我们自己的异常,然后加上code
还没结束,我们捕捉的异常是不是自己写的这个异常呢?
加个判断,如果是的话呢,就返回自己的code和message,不是的话就先暂时标记。
@ExceptionHandler(value = Exception.class)
@ResponseBody//表示返回json格式数据
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, "未知错误");
}
}
这样的话呢,每个异常信息对应着不同的code码了。舒服!
但是还没完,我们自己定义的异常中的错误码和message少了还好,要是多了的话,都写在那个里面,管理维护起来也很麻烦,于是我们现在要统一管理起来,这里用到的是枚举:
首先新建一个类:将所有错误码写到里面:
public enum ResultEnum {
UNKONW_ERROR(-1,"未知错误"),
SUCCESS(0,"成功"),
PRIMARY_SCHOOL(100,"你可能在上小学"),
MIDDEL_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;
}
}
呐,都放好了,然后我们修改service:
if (age<=10){
//这里用的自己写的错误码,时间久了或者异常多了不方便记忆和管理查找
throw new GirlException(ResultEnum.PRIMARY_SCHOOL);
}else if (age>10 && age<16){
throw new GirlException(ResultEnum.MIDDEL_SCHOOL);
}else if(age>=16){
throw new GirlException(ResultEnum.SUCCESS);
}
}
然后可以看到这里抛出的异常的参数是ResultEnum的数据,所以修改GIRLException的构造方法,数据对不上:报错如下:
public class GirlException extends RuntimeException {//这里不继承Exception是因为不支持事物回滚
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;
}
}
这样的话后期文虎真的实在舒服,进去枚举就行了,都在这了,很舒服。
4.单元测试
首先我们来测试service,在service里面写一个要测试的方法:
public Girl findOne(Integer id){
return girlRepository.findOne(id);
}
然后新建测试类:
@RunWith(SpringRunner.class)//这个注解表示我们要在测试环境里面跑了
@SpringBootTest//表示将启动整个spring的工程
public class GirlServiceTest {
@Autowired
private GirlService girlService;
@Test
public void findOneTest(){
Girl girl = girlService.findOne(20);
Assert.assertEquals(new Integer(19),girl.getAge());//这里要求是两个对象
}
}
这里使用了断言,看一下运行结果:
测试通过,那我们把断言那的数据改成不对的把19改成20,看看会怎么样呢:
很清楚,对不对。
然后是对API的测试
与上面不一样的地方是:
多了一个注解,情切测试方法略有不同
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc//对API进行测试使用这个东西
public class GirlControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void girlList() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/girls")).andExpect(MockMvcResultMatchers.status().isOk());
}
}
接下来是测试效果:
接下来试试测个错的:
可以发现它是通过对比状态吗来进行判断的。
第一次玩儿这个东西,好厉害
跳过单元测试
今天的收获
以上
今天遇到的问题
都解决了
明天计划的事情
搞复盘
评论