zoukankan      html  css  js  c++  java
  • SpringBoot2.x整合Email并利用AOP做一个项目异常通知功能

    因为不知aop能干嘛,因此用aop做个小功能,再结合最近学的springboot-Email做了个系统异常自动邮件通知的功能,

    感觉满满的成就感。

    AOP不懂的可以看上一篇https://www.cnblogs.com/zgq7/p/11310142.html

    spring-email不懂的看上一篇https://www.cnblogs.com/zgq7/p/11314895.html

    先看看这个功能的总体规划图:

     

    因此需要思考的是:

    1:如何捕获异常?

    总不能在每个会发生异常的地方写 throw 或者 try-catch 语句吧?因此利用AOP进行统一捕获并进行下一步处理。

    2:如何将异常信息发送到开发者(用户)邮箱?

    这就需要用到javaMail技术了,而我在maven仓库搜寻时看到了springboot-Email,因此去自发了解了这个开发流程。

    本来这里打算使用原生的JavaMail的,但是springboot集成了就没用了。

    原生的可参考这里https://www.cnblogs.com/LUA123/p/5575134.html

    下面开始我的设计思路的实现流程。

    1:添加相关springboot-mail以及springboot-aop 相关依赖

            <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
                <version>2.1.6.RELEASE</version>
            </dependency>
            <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-mail -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-mail</artifactId>
                <version>2.1.6.RELEASE</version>
            </dependency>

    2:构造一个本地线程池,以防高并发。 

    package com.dev.config;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.DisposableBean;
    import org.springframework.beans.factory.InitializingBean;
    
    import java.lang.reflect.Method;
    import java.util.concurrent.*;
    
    /**
     * Created on 2019-08-05 14:00.
     *
     * @author zgq7
     */
    public class LocalThreadPool implements InitializingBean, DisposableBean {
    
        public static final String PACKAGE_BEAN_NAME = "localThreadPool";
    
        private static final Logger logger = LoggerFactory.getLogger(LocalThreadPool.class);
    
        /**
         * capacity 队列容量
         **/
        private final BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(5000);
    
        private final RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
    
        private final ThreadFactory defaultThreadFactory = Executors.defaultThreadFactory();
        private final ThreadFactory privilegedThreadFactory = Executors.privilegedThreadFactory();
    
        /**
         * corePoolSize             核心线程数量
         * maximumPoolSize          线程池允许最大线程池数量
         * keepAliveTime            当线程数超过本地线程核心数同时又小于设置的最大线程数,需要重新创建一个新线程时需要等待的时间
         * TimeUnit.MILLISECONDS    时间单位,这里我设置的是豪秒
         * workQueue                一个队列:当一个task被执行前进行使用
         * threadFactory            用于创建新线程的线程工厂
         * handler                  一个处理器:当线程被锁、或者队列的容量达到上限时 被调用
         **/
        public final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 3000, TimeUnit.MILLISECONDS
                , workQueue, defaultThreadFactory, handler);
    
        /**
         * 本地线程池被销毁后的回调方法
         **/
        @Override
        public void destroy() throws Exception {
            logger.info("指令->[本地线程池已销毁]");
        }
    
        /**
         * 本地线程池成功初始化的回调犯法
         **/
        @Override
        public void afterPropertiesSet() throws Exception {
            logger.info("指令->[本地线程池已成功初始化]");
        }
    
    }

    3:邮件工具类请参考我上一篇博客:https://www.cnblogs.com/zgq7/p/11314895.html

    4:编写一个切面类,用于全局捕捉程序产生的异常

    package com.dev.config.aop;
    
    import com.dev.config.LocalThreadPool;
    import com.dev.model.email.EmailModel;
    import com.dev.utils.email.MailSendUtils;
    import com.dev.utils.exception.ExceptionCodes;
    import com.dev.utils.exception.ServiceException;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.annotation.Order;
    
    import java.util.Arrays;
    
    /**
     * Created on 2019-07-31 9:41.
     *
     * @author zgq7
     */
    @Aspect
    @Order(2)
    public class RuntimeExceptionAspectJ {
        @Autowired
        private MailSendUtils mailSendUtils;
    
        @Autowired
        private LocalThreadPool localThreadPool;
    
        private final Logger log = LoggerFactory.getLogger(this.getClass());
    
        //@Pointcut("execution(public * com.dev..*(..))")
        @Pointcut("execution(public * com.dev.controller.TestController.*(..))")
        private void runtimeExceptionAspect() {
        }
    
        /**
         * 切面报错
         **/
        @AfterThrowing(value = "runtimeExceptionAspect()", throwing = "exception")
        public void afterThrowing(Throwable exception) {
            Class klass = exception.getClass();
            log.error("occured a [{}] , msg : [{}]", klass.getSimpleName(), ExceptionCodes.getMsgByKlass(klass));
            EmailModel emailModel = new EmailModel();
            emailModel.setEmailTheme("测试");
            emailModel.setRecieverName("测试");
            emailModel.setEmailContent(exception.toString() + ":
    " + Arrays.toString(exception.getStackTrace()));
            emailModel.setRecieverEmailAddress("xxx@qq.com");
    
            localThreadPool.threadPoolExecutor.getThreadFactory().newThread(() -> mailSendUtils.sendEmailAsSysExceptionHtml(emailModel)).start();
            throw new ServiceException(ExceptionCodes.getCodeByKlass(klass), ExceptionCodes.getMsgByKlass(klass));
        }
    
    }

    4.1:为了更方便的区分异常类型,我在程序内部设计了这个枚举(各位看官看看就好)

    package com.dev.utils.exception;
    
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.net.ProtocolException;
    import java.sql.SQLException;
    
    /**
     * Created on 2019-07-31 16:01.
     *
     * @author zgq7
     * @apiNote 自定义的code - XxxException.class
     */
    public enum ExceptionCodes {
        //RuntimeException 类型异常
        RUNTIME_EXCEPTION(100, "运行时异常", RuntimeException.class),
        NULL_POINT_EXCEPTION(101, "空指针异常", NullPointerException.class),
        ARITHMETIC_EXCEPTION(102, "数学错误,被0除", ArithmeticException.class),
        INDEX_OUT_OF_BOUNDS_EXCEPTION(103, "当某对象的索引超出范围时抛出异常", IndexOutOfBoundsException.class),
        ARRAY_INDEX_OUT_OF_EXCEPTION(103001, "数组下标越界", ArrayIndexOutOfBoundsException.class),
        CLASS_CAST_EXCEPTION(104, "强制转换异常", ClassCastException.class),
        ILLEGAL_ARGUMENT_EXCEPTION(105, "非法转换", IllegalArgumentException.class),
        NUMBER_FORMAT_EXCEPTION(105001, "字符串转换为数字异常类", NumberFormatException.class),
        PROTOCOL_EXCEPTION(106, "网络协议有错误", ProtocolException.class),
    
        //IOException 类型异常
        IO_EXCEPTION(200, "IO 流异常", IOException.class),
        FILE_NOT_FOUND_EXCEPTION(201, "文件找不到", FileNotFoundException.class),
    
        //数据库 sql 操作异常
        SQL_EXCEPTION(300, "操作数据库异常", SQLException.class),
    
        //其他相关异常
        REFLECTIVE_OPERATION_EXCEPTION(400, "", ReflectiveOperationException.class),
        ILLEGAL_ACESS_EXCEPTION(401, "访问某类被拒绝时抛出的异常", IllegalAccessException.class);
    
    
        private int code;
    
        private String msg;
    
        private Class<Exception> klass;
    
        ExceptionCodes(int code, String msg, Class klass) {
            this.code = code;
            this.msg = msg;
            this.klass = klass;
        }
    
        public int getCode() {
            return this.code;
        }
    
        public String getMsg() {
            return this.msg;
        }
    
        public Class<Exception> getKlass() {
            return this.klass;
        }
    
        /**
         * 通过异常码获取对应异常类
         **/
        public static Class<Exception> getKlassByCode(int code) {
            for (ExceptionCodes exceptionCodes : ExceptionCodes.values()) {
                if (exceptionCodes.getCode() == code)
                    return exceptionCodes.getKlass();
            }
            return null;
        }
    
        /**
         * 根据异常码获取对应异常信息
         **/
        public static String getMsgByCode(int code) {
            for (ExceptionCodes exceptionCodes : ExceptionCodes.values()) {
                if (exceptionCodes.getCode() == code)
                    return exceptionCodes.getMsg();
            }
            return null;
        }
    
        /**
         * 根据异常类获取异常码
         **/
        public static int getCodeByKlass(Class<? extends Exception> klass) {
            for (ExceptionCodes exceptionCodes : ExceptionCodes.values()) {
                if (exceptionCodes.getKlass() == klass)
                    return exceptionCodes.getCode();
            }
            return 3000;
        }
    
        /**
         * 根据异常类获取异常信息
         **/
        public static String getMsgByKlass(Class<? extends Exception> klass) {
            for (ExceptionCodes exceptionCodes : ExceptionCodes.values()) {
                if (exceptionCodes.getKlass() == klass)
                    return exceptionCodes.getMsg();
            }
            return null;
        }
    
    
    }
    异常枚举

    5:注册相关bean

    package com.dev.config;
    
    import com.dev.config.aop.BaseAop;
    import com.dev.config.aop.RuntimeExceptionAspectJ;
    import com.dev.filter.BaseFilter;
    import com.dev.utils.email.MailSendUtils;
    import org.springframework.boot.autoconfigure.AutoConfigureAfter;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    import org.springframework.core.annotation.Order;
    import org.springframework.mail.javamail.JavaMailSender;
    import org.springframework.mail.javamail.JavaMailSenderImpl;
    
    import java.util.*;
    
    /**
     * Created by zgq7 on 2019/6/6.
     * 注册一些bean进入ioc
     *
     * @EnableAspectJAutoProxy 开启aop代理
     */
    @Configuration
    @EnableAspectJAutoProxy
    public class BeanRegistryCenterConfig {/**
         * 邮箱工具类 bean 注册
         **/
        @Bean
        public MailSendUtils mailSendUtils() {
            return new MailSendUtils();
        }
    
        /**
         * 本地线程 bean 注册
         **/
        @Bean(name = LocalThreadPool.PACKAGE_BEAN_NAME)
        public LocalThreadPool localThreadPool() {
            return new LocalThreadPool();
        }/**
         * 异常捕获类 RuntimeExceptionAspectJ bean 注册
         **/
        @Bean
        public RuntimeExceptionAspectJ runtimeExceptionAspectJ() {
            return new RuntimeExceptionAspectJ();
        }
    
    }

    6:controller中制造一个runtimeException类型的异常,如下

    package com.dev.controller;
    
    import com.dev.controller.bases.BaseController;
    import com.dev.service.AopiService;
    import com.dev.utils.exception.ServiceException;
    import com.google.common.collect.ImmutableMap;
    import okhttp3.*;
    import org.springframework.web.bind.annotation.*;
    
    import javax.annotation.Resource;
    import java.io.*;
    import java.util.Collections;
    import java.util.Map;
    
    /**
     * Created by zgq7 on 2019/6/6.
     */
    @RestController
    @RequestMapping(value = "/dev")
    public class TestController extends BaseController {
    
        @Resource(name = AopiService.PACKAGE_BEAN_NAME)
        private AopiService aopiService;
    
        @GetMapping(value = "")
        public Map<Object, Object> get() {
            if (1 == 1)
                throw new NullPointerException();
            return ImmutableMap.of("code", aopiService.getAopList());
        }
    
    }

    7:运行项目进行测试

    7.1:先看启动日志

     项目成功启动,本地线程池也初始化成功!!!

     7.2:使用postMan进行测试

     

    7.3:邮箱接收到的邮件

    总结:做到这一步,程序在遇到异常时,便可主动给开发者发报错信息了,若线上出了什么小异常,可以直接查看。

    如有不理解的地方请下方留言,喜欢的点个赞。

    本文项目地址:https://github.com/zgq7/devloper-mine

     

  • 相关阅读:
    JAVA的反射理解
    网络编程-小结
    JAVA多线程的总结
    Mysql基本语句的总结
    IO流
    JAVA集合介绍
    时间复杂度
    JAVA面向对象-多态的理解
    求A的B次方
    最大公约数
  • 原文地址:https://www.cnblogs.com/zgq7/p/11310776.html
Copyright © 2011-2022 走看看