因为不知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() + ":\n" + 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

 

SpringBoot2.x整合Email并利用AOP做一个项目异常通知功能的更多相关文章

  1. 今天做一个项目的时候,要在一个编辑的jsp页面的textarea标签设置value属性,结果发现他没有value属性,但是是编辑页面又必须要回显要修改的内容,所以在参考了w3cschool之后很轻松的解决了这个问题。

    今天做一个项目的时候,要在一个编辑的jsp页面的textarea标签设置value属性,结果发现他没有value属性,但是是编辑页面又必须要回显要修改的内容,所以在参考了w3cschool之后很轻松的 ...

  2. SpringBoot31 整合SpringJDBC、整合MyBatis、利用AOP实现多数据源

    一.整合SpringJDBC 1  JDBC JDBC(Java Data Base Connectivity,Java 数据库连接)是一种用于执行 SQL 语句的 Java API,可以为多种关系数 ...

  3. 利用AOP切面打印项目中每个接口的运行情况

    1.前言 AOP切面技术,大家应该都听知道,Spring框架的主要功能之一. AOP切面的用途很广,其中一个常见的用途就是打印接口方法的运行日志和运行时间. 日志对于一个项目很是重要,不仅有助于调错, ...

  4. 用小程序做一个类似于苹果AssistiveTouch功能

    一.首先我先介绍一下,我们要做一个什么样的项目功能 项目功能就是一个音频点击播放,当点击为播放的状态时,一个音频的动图出现,而且是可以跟随着手指的滑动而滑动,而且,在滑动动图的时候,当前下的页面是不可 ...

  5. iOS 利用UICollectionView做一个无限循环广告栏

    一.效果图 左右丝滑滑动,并且有缩放动画. 二.分析和思路 1. 为什么选择用UICollectionView去做上面的效果? 首先无限效果永远是表现出来的,而不是程序里面创建了无数个view,如何做 ...

  6. 利用dhtmlxGrid做的表格排序的功能。

    dhtmlxGrid支持tree和grid. grid之间.grid内部进行拖拽, 如在grid内部进行拖拽,可以增加一行:在grid之间拖拽,第一个grid的记录删除,第二个grid增加一行记录.

  7. 利用jmeter做一个简单的性能测试并进行参数化设置

    1.新增一个线程组,并在下面添加基本原件,包括:监听器.http请求默认值和一个事务控制器 在http请求默认值中填写 ip 地址和端口号,协议类型默认为http 2.添加代理服务器,以便之后进行录制 ...

  8. 利用Django做一个简单的分页页面

    views代码: from django.shortcuts import render from django.conf import settings from booktest.models i ...

  9. java aop做一个接口耗时的计算

    看代码: @Aspect @Component public class TimeCostAspect { private static Logger logger = LoggerFactory.g ...

随机推荐

  1. PHP-mysql存储照片的两种方式

    PHP-mysql存储照片的两种方式 方式一:把图片数据存储在数据库中(二进制) 数据库代码: CREATE TABLE `photo` (    `id` int(10) unsigned NOT ...

  2. CCF-最优配餐(BFS)

    最优配餐   问题描述 栋栋最近开了一家餐饮连锁店,提供外卖服务.随着连锁店越来越多,怎么合理的给客户送餐成为了一个急需解决的问题.栋栋的连锁店所在的区域可以看成是一个n×n的方格图(如下图所示),方 ...

  3. 关于RabbitMQ的简单理解

    说明:想要理解RabbitMQ,需要先理解MQ是什么?能做什么?然后根据基础知识去理解RabbitMQ是什么.提供了什么功能. 一.MQ的简单理解 1. 什么是MQ? 消息队列(Message Que ...

  4. 【xml】Button背景色无法修改

    由于新版本的主题问题,导致Android Studio的Button背景无法修改,一直呈现亮紫色. 解决方法:将app/res/values目录下的themes代码加上.Bridge即可 修改前: & ...

  5. docker(3)快速搭建centos7-python3.6环境

    前言 当我们在一台电脑上搭建了python3.6的环境,下次换台电脑,又得重新搭建一次,设置环境变量等操作. 好不容易安装好,一会提示pip不是内部或外部命令,一会又提示pip: command no ...

  6. H - Oil Skimming (挖石油)

    题意大概是,海上漂浮着一些符号为#的石油,你要去搜集他们,但是你的勺子呢能且只能挖到两个单元的石油.问你最多能挖多少勺.注意 不能挖到纯净的海水,不然石油会被纯净的海水稀释的. 二分匹配,计算出里边有 ...

  7. python爬取酷我音乐

    我去!!!我之后一定按照搜索方式下载歌曲~~~~~~~~~ 1.首先打开我们本次主讲链接:http://www.kuwo.cn/  2.刚开始我就随便点了一个地方,然后开始在后台找歌曲的链接地址.但是 ...

  8. Codeforces Round #479 (Div. 3) C. Less or Equal (排序,贪心)

    题意:有一个长度为\(n\)的序列,要求在\([1,10^9]\)中找一个\(x\),使得序列中恰好\(k\)个数满足\(\le x\).如果找不到\(x\),输出\(-1\). 题解:先对这个序列排 ...

  9. 煎蛋网爬虫之JS逆向解析img路径

    图片使用js onload事件加载 <p><img src="//img.jandan.net/img/blank.gif" onload="janda ...

  10. 14. 从0学ARM-exynos4412-看门狗裸机程序编写

    看门狗 一.概念 看门狗的简称是WDT(Watch Dog Timer),exynos4412scp中的看门狗定时器(WDT)是一种定时装置. 1. 工作原理 由(一般需要客户编写)软件读写定时器相关 ...