【Springboot学习 | 6】Lombok优雅的编码+Aop异常统一管理

一、Lombok优雅的编码

1.1 配置Lombok

  1. IDEA安装lombok插件
    步骤:file-》setting-》Plugins-》Marketplace-》搜索lombok-》intall-》重启IDEA
    在这里插入图片描述
    2.允许enable注解处理器
    步骤:在Settings设置页面,我们点击Build,Execution,Deployment-->选择Compiler-->选中Annotation Processors,然后在右侧勾选Enable annotation processing即可。
    在这里插入图片描述
    3.安装的lombok和Maven一样是一个插件处理器,需要在pom中声明调用

    查看最新版本:https://mvnrepository.com/artifact/org.projectlombok/lombok/

    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.8</version>
        <scope>provided</scope>
    </dependency>
    

1.2 常用Lombok方法

1.2.1 @Getter@Setter

不使用lombok的实体类:
在这里插入图片描述
@Getter@Setter简化get和set函数:

import lombok.Getter;
import lombok.Setter;

@Entity
@Table(name="t_user")
@Getter
@Setter
public class UserEntity implements Serializable{
    @Id
    @GeneratedValue
    @Column(name="t_id")
    private Long id;

    @Column(name = "t_name")
    private String name;

    @Column(name = "t_age")
    private int age;

    @Column(name = "t_address")
    private String address;

    @Column(name = "t_password")
    private Long password;
}

test函数:

    @Test
    public void testLombok()
    {
        //测试Getter/Setter
        UserEntity user = new UserEntity();
        user.setName("测试lombok");
        user.setAge(10);
        user.setAddress("测试地址");

        System.out.println(user.getName()+"  " + user.getAge() +"  "+user.getAddress());
    }

运行结果:测试lombok 10 测试地址,getter/setter没毛病

1.2.3 @ToString()

实体类

@Getter
@Setter
@ToString
public class UserEntity implements Serializable{
...

测试

System.out.println(user.toString());

运行结果:UserEntity(id=null, name=测试lombok, age=10, address=测试地址, password=null)

Lombok自动创建的toString方法会将所有的属性都包含并且调用后可以输出。

1.2.3 @AllArgsConstructor@NoArgsConstructor

实体类:

@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class UserEntity implements Serializable{
...
  1. @AllArgsConstructor
    UserEntity user = new UserEntity(1l,"Lombok",20,"构造lombok",123456l);
    2.@NoArgsConstructor
    和1.2.1中写法相同UserEntity user = new UserEntity();.....
    @Test
    public void testLombok2()
    {
        //测试AllArgsConstructor
        UserEntity user = new UserEntity(1l,"Lombok",20,"构造lombok",123456l);
        //id:1l,password:123456l是因为Long类型写法
        System.out.println(user.getName()+"  " + user.getAge() +"  "+user.getAddress());
        System.out.println(user.toString());
    }

测试结果:

测试lombok  10  测试地址
UserEntity(id=1, name=Lombok, age=20, address=构造lombok, password=123456)

1.2.3 @Data

使用@Data注解就可以涵盖@ToString、@Getter、@Setter方法,当然我们使用构造函数时还是需要单独添加注解,下面我们修改实体类添加@Data注解代码如下所示

import lombok.*;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserEntity implements Serializable{
...

重复以上测试均无误。所以以后用
@Data @AllArgsConstructor @NoArgsConstructor
就ok了!
常用注解:

#注解用法作用
1@Setter注解在类或字段注解在类时为所有字段生成setter方法,注解在字段上时只为该字段生成setter方法。
2@Getter使用方法同上区别在于生成的是getter方法。
3@ToString注解在类添加toString方法。
4@EqualsAndHashCode注解在类生成hashCode和equals方法。
5@NoArgsConstructor注解在类生成无参的构造方法。
6@RequiredArgsConstructor注解在类为类中需要特殊处理的字段生成构造方法,比如final和被@NonNull
7@AllArgsConstructor注解在类生成包含类中所有字段的构造方法。
8@Data注解在类生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。
9@Slf4j注解在类生成log变量,严格意义来说是常量。private static final Logger log = LoggerFactory.getLogger(UserController.class);

1.2.4 @Slf4j

日志输出,替代System.out.println();而且可以保存在日志中。

import lombok.extern.slf4j.Slf4j;
@Slf4j
....
		log.info(user.toString());

在这里插入图片描述
结果:

2019-08-14 11:40:42.923  INFO 31844 --- [           main] c.s.three.ThreeApplicationTests          : UserEntity(id=1, name=Lombok, age=20, address=构造lombok, password=123456)

二、Aop异常统一管理

2.1 为什么要用Aop?

  1. Aop认识
    AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。在日常开发当中经常用来记录日志,方法跟踪、事务,权限等。

  2. 三种默认
    Springboot默认error:

    -》启动该应用,访问一个不存在的URL。
    在这里插入图片描述
    控制台打印出:2019-08-14 13:38:08.949 WARN 28636 --- [nio-8090-exec-9] o.s.web.servlet.PageNotFound : No mapping for POST /abc

    默认异常原理:
    Spring Boot提供了一个默认的映射:/error,当处理中抛出异常之后,会转到该请求中处理,并且该请求有一个全局的错误页面用来展示异常内容。
    ~
    对应引入的spring-boot-autoconfigure-2.1.7.RELEASE.jar包中,有这样一个错误控制层函数BasicErrorController,其中有返回html和json的方法:
    在这里插入图片描述
    在这里插入图片描述

    -》用postman测试:响应的json如下
    在这里插入图片描述

  3. 统一异常处理:

    • 场景1 返回HTML页面(比较简单,参考链接
    • 场景2 返回JSON页面 ?(我们只想要一个json返回结果,而不是跳转到硬生生的error页面,选择这种方式)
      原版:data
      在这里插入图片描述
      加强版Result结果:
      在这里插入图片描述

2.2 Aop配置

2.2.1 文档说明

查看springboot Guide官方文档针对异常处理部分Error Handling解释

快速链接:Spring Boot-2.1.7 Reference Guide(29.1.11 Error Handling)
在这里插入图片描述

结论:

  • 我们可以通过@ControllerAdvice (控制增强)+ @ExceptionHandler进行统一异常处理,
  • ExceptionHandler注解可以设置全局处理控制里的异常类型来拦截要处理的异常。 例如:@ExceptionHandler(value = Exception.class)

2.2.2 Aop引入

项目目录:
在这里插入图片描述

  1. pom引入Aop依赖

    		<!--AOP主要依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
    
  2. 首先定义响应状态码枚举类ResultCode.java,返回json,其中包含

    • code:错误码,0表示没有异常信息。
    • message:异常提示信息
    • date:无异常返回具体内容。

    因为是枚举类,所以class类型为Enum:
    在这里插入图片描述

    package com.springboot.three.aop;
    
    public enum ResultCode {
        SUCCESS(200),//成功
        FAIL(400),//失败
        UNAUTHORIZED(401),//未认证(签名错误)
        NOT_FOUND(404),//接口不存在
        INTERNAL_SERVER_ERROR(500);//服务器内部错误
    
        private final int code;
    
        ResultCode(int code) {
            this.code = code;
        }
    
        public int code() {
            return code;
        }
    }
    
    
  3. 定义 响应类Result.java

    package com.springboot.three.aop;
    
    import com.fasterxml.jackson.annotation.JsonInclude;
    
    /**
     * 响应类
     */
    public class Result {
        // 状态响应码
        private int code;
    
        // 响应结果 成功/失败
        private boolean success;
    
        // 响应信息
        private String message;
    
        // 响应数据
        @JsonInclude(JsonInclude.Include.NON_NULL)
        private Object data;
    
        public Result setCode(ResultCode resultCode) {
            this.code = resultCode.code();
            return this;
        }
    
        public int getCode() {
            return code;
        }
    
        public boolean isSuccess() {
            return success;
        }
    
        public Result setSuccess(boolean success) {
            this.success = success;
            return this;
        }
    
        public String getMessage() {
            return message;
        }
    
        public Result setMessage(String message) {
            this.message = message;
            return this;
        }
    
        public Object getData() {
            return data;
        }
    
        public Result setData(Object data) {
            this.data = data;
            return this;
        }
    }
    
  4. 对响应结果封装,做预处理ResultGenerator.java

    package com.springboot.three.aop;
    
    /**
     * 响应结果,预处理
     */
    public class ResultGenerator {
        private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";
        /**
         * 成功响应
         * code:200
         * 无参返回data:null
         * 成功信息:message:SUCCESS
         * @return
         */
    
        public static Result genSuccessResult() {
            return new Result()
                    .setCode(ResultCode.SUCCESS)
                    .setSuccess(true)
                    .setMessage(DEFAULT_SUCCESS_MESSAGE);
        }
    
        /**
         * 成功响应
         * code:200
         * 有参返回data:实体类或集合
         * 成功信息:message:SUCCESS
         * @param data
         * @return
         */
    
        public static Result genSuccessResult(Object data) {
            return new Result()
                    .setCode(ResultCode.SUCCESS)
                    .setSuccess(true)
                    .setMessage(DEFAULT_SUCCESS_MESSAGE)
                    .setData(data);
        }
    
        /**
         * 错误响应
         * code:400等
         * 无参返回data:null
         * 错误信息message:"/by zero"等
         * @param message
         * @return
         */
    
        public static Result genFailResult(String message) {
            return new Result()
                    .setCode(ResultCode.FAIL)
                    .setSuccess(false)
                    .setMessage(message);
        }
    }
    
  5. 全局异常处理类(这是监控error)
    ExceptionControllerAdvice.java

    package com.springboot.three.aop;
    
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    
    import javax.servlet.http.HttpServletRequest;
    
    /**
     * 异常处理类
     * restful 风格处理异常信息
     */
    @RestControllerAdvice
    public class ExceptionControllerAdvice {
        /**
         * 全局异常捕捉处理
         * @param req
         * @param e
         * @return
         */
    
        @ExceptionHandler(Exception.class)
        public Result jsonErrorHandler(HttpServletRequest req, Exception e){
            return ResultGenerator.genFailResult(e.getMessage());
        }
    }
    
    

    aop配置部分参考于:SpringBoot2.X (十六): SpringBoot 全局异常配置
    仅仅多加了一些注释。

2.3 返回值测试

  1. Controller中:一个简单的测试

    【get类型,成功:只返回成功data对象,不返回code,message;失败:返回错误码和提示】

    @GetMapping(value="/abc")
        public Object testException(){
            int i = 5/0;//分母不为0,会报错400
            return i;
        }
    

    get请求:http://127.0.0.1:8090/user/abc
    结果:会报错400,message报错:分母不为0
    在这里插入图片描述

    -》修改为post类型,重启测试:(post要加上条件查询)

        @RequestMapping(value="/abc",method = RequestMethod.POST)
        public Object testException(Integer num){
            int i = 5/num;//分母不为0,会报错400
            return i;
        }
    

    post请求:http://127.0.0.1:8090/user/abc?num=0:报错400
    http://127.0.0.1:8090/user/abc?num=1:仅显示5/1结果==5
    在这里插入图片描述 在这里插入图片描述

  2. 【Post类型,成功:不返回成功data对象,只返回code,message等;失败:返回错误码和提示】

        //aop测试:【post类型,成功:有返回值,失败:无返回值】
        @RequestMapping(value ="/aop1",method = RequestMethod.POST)
        public Result aopTest2(Integer num,Result result,Exception e) {
            int i = 5/num;//分母不为0,会报错400
            return ResultGenerator.genSuccessResult();
        }
    

    post请求:http://127.0.0.1:8090/user/aop1?num=0http://127.0.0.1:8090/user/aop1?num=1
    结果:Result结果
    在这里插入图片描述 在这里插入图片描述

  3. 【Post类型,成功:返回成功data对象,返回code,message等;失败:返回错误码和提示】

        //aop测试:【post类型,全返回】
        @RequestMapping(value ="/aop",method = RequestMethod.POST)
        public Result aopTest(String name, Long password,Exception e) {
            UserEntity userEntity= userJPA.findByNameAndPassword(name,password);
            if(userEntity!=null){
                return ResultGenerator.genSuccessResult(userEntity);
            }else {
                return ResultGenerator.genFailResult(e.getMessage());
            }
        }
    

    post请求:http://127.0.0.1:8090/user/aop?name=test&password=123456(数据库有的,查询到了)
    http://127.0.0.1:8090/user/aop?name=test&password=123(数据库没有的,查询不到)
    结果:Result结果
    在这里插入图片描述 在这里插入图片描述
    -》这是data返回集合:同理,调用ResultGenerator.genSuccessResult(userlist);就行

        @RequestMapping(value = "/aop2", method = RequestMethod.GET)
        public Result aopList3(Exception e) {
            List<UserEntity> userlist = userJPA.findAll();
            if(userlist!=null){
                return ResultGenerator.genSuccessResult(userlist);
            }else {
                return ResultGenerator.genFailResult(e.getMessage());
            }
        }
    

    get请求:http://127.0.0.1:8090/user/aop2
    结果:data可以存放list
    在这里插入图片描述
    大致就是上面这些了!

2.4 log日志配置

新建TestAspect.java,封装日志信息
在这里插入图片描述
test示例:

package com.springboot.three.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Slf4j
public class TestAspect {

    //com.springboot.three.controller 包中所有的类的所有方法切面
    //@Pointcut("execution(public * com.springboot.three.controller.*.*(..))")

    //只针对 MessageController 类切面
    //@Pointcut("execution(public * com.springboot.three.controller.MessageController.*(..))")

    //统一切点,对com.springboot.three.controller及其子包中所有的类的所有方法切面
    @Pointcut("execution(public * com.springboot.three.controller..*.*(..))")
    public void Pointcut() {
    }

    //前置通知
    @Before("Pointcut()")
    public void beforeMethod(JoinPoint joinPoint){
        log.info("调用了前置通知");

    }

    //@After: 后置通知
    @After("Pointcut()")
    public void afterMethod(JoinPoint joinPoint){
        log.info("调用了后置通知");
    }
    //@AfterRunning: 返回通知 rsult为返回内容
    @AfterReturning(value="Pointcut()",returning="result")
    public void afterReturningMethod(JoinPoint joinPoint,Object result){
        log.info("调用了返回通知");
    }
    //@AfterThrowing: 异常通知
    @AfterThrowing(value="Pointcut()",throwing="e")
    public void afterReturningMethod(JoinPoint joinPoint, Exception e){
        log.info("调用了异常通知");
    }

    //@Around:环绕通知
    @Around("Pointcut()")
    public Object Around(ProceedingJoinPoint pjp) throws Throwable {
        log.info("around执行方法之前");
        Object object = pjp.proceed();
        log.info("around执行方法之后--返回值:" +object);
        return object;
    }

}

请求http://localhost:8090/send_message?name=aop&password=1111
在这里插入图片描述
后端:
在这里插入图片描述
aspect类内部执行顺序:
在这里插入图片描述
完善:

参考:使用AOP打印日志和效率监听(记录请求参数和返回结果和方法运行总时间)

package com.springboot.three.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Aspect
@Component
@Slf4j
public class TestAspect {

    //com.springboot.three.controller 包中所有的类的所有方法切面
    //@Pointcut("execution(public * com.springboot.three.controller.*.*(..))")

    //只针对 MessageController 类切面
    //@Pointcut("execution(public * com.springboot.three.controller.MessageController.*(..))")

    //统一切点,对com.springboot.three.controller及其子包中所有的类的所有方法切面
    @Pointcut("execution(public * com.springboot.three.controller..*.*(..))")
    public void Pointcut() {
    }
    //@Around:环绕通知
    @Around("Pointcut()")
    public Object Around(ProceedingJoinPoint pjp) throws Throwable {
        Map<String,Object> data = new HashMap<>();
        //获取目标类名称
        String clazzName = pjp.getTarget().getClass().getName();
        //获取目标类方法名称
        String methodName = pjp.getSignature().getName();

        //记录类名称
        data.put("clazzName",clazzName);
        //记录对应方法名称
        data.put("methodName",methodName);
        //记录请求参数
        data.put("params",pjp.getArgs());
        //开始调用时间
        // 计时并调用目标函数
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();
        Long time = System.currentTimeMillis() - start;

        //记录返回参数
        data.put("result",result);

        //设置消耗总时间
        data.put("consumeTime",time);
        System.out.println(data);
        return result;

    }

}

请求:http://127.0.0.1:8090/user/abc?num=1
运行效果:

{result=5, consumeTime=0, methodName=testException, params=[Ljava.lang.Object;@71747c2, clazzName=com.springboot.three.controller.UserController}

我觉得不太满意,用HashMap存储,可视化太不良好了,下面是给出的一个优化:
在这里插入图片描述
完整:TestAspect.java

package com.springboot.three.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

@Aspect
@Component
@Slf4j
public class TestAspect {

    //com.springboot.three.controller 包中所有的类的所有方法切面
    //@Pointcut("execution(public * com.springboot.three.controller.*.*(..))")

    //只针对 MessageController 类切面
    //@Pointcut("execution(public * com.springboot.three.controller.MessageController.*(..))")

    //统一切点,对com.springboot.three.controller及其子包中所有的类的所有方法切面
    @Pointcut("execution(public * com.springboot.three.controller..*.*(..))")
    public void Pointcut() {
    }
    //@Around:环绕通知
    @Around("Pointcut()")
    public Object Around(ProceedingJoinPoint pjp) throws Throwable {
        log.info("我是环绕通知!!!");
        log.info("方法 :"+pjp.getSignature().getName());
        //AOP代理类的名字
        log.info("方法所在包 :"+pjp.getSignature().getDeclaringTypeName());

        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        String[] strings = methodSignature.getParameterNames();
        log.info("参数名 :"+ Arrays.toString(strings));
        log.info("参数值ARGS : " + Arrays.toString(pjp.getArgs()));

        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest req = attributes.getRequest();
        // 记录下请求内容
        log.info("请求URL : " + req.getRequestURL().toString());
        log.info("HTTP_METHOD : " + req.getMethod());
        log.info("IP : " + req.getRemoteAddr());
        log.info("CLASS_METHOD : " + pjp.getSignature().getDeclaringTypeName() + "." + pjp.getSignature().getName());
        
        //开始调用时间
        // 计时并调用目标函数
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();
        Long time = System.currentTimeMillis() - start;
        //记录返回参数
        log.info("请求返回结果result: "+result);
        //设置消耗总时间
        log.info("环绕通知完毕!!!");
        log.info("请求总耗时time:"+time);
        return result;
    }

}

三、项目源码

https://github.com/cungudafa/SpringBoot/tree/master/springboot学习Day7
??????????????

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值