我们在捕获到异常并对异常进行处理时可能会遇到如下一些问题:
有必要使用一个统一的异常处理机制 来进行某些异常处理的统一决策。比如对异常进行统一的日志记录,对散落在各处的某类异常进行统一处理等。
保障数据前后一致性,包括与外系统数据的一致性
将业务代码与补偿代码解耦
补偿框架易用性
使用Spring AOP对serivce层抛出的异常进行拦截,记录所有未处理的异常日志,并将所有未处理异常转换成统一自定义的系统异常,以便前端能够处理。下面的代码举例说明了如何使用Spring AOP将异常处理从正常的逻辑中分离出来,使得异常处理逻辑和正常的业务逻辑做到完全解耦。不同项目对于异常处理的要求不同,在具体项目中需要对异常处理代码根据项目需要进行修改,加以完善。
AOP配置
|
异常处理类
package com.za.exceptionhandler.demo.exception.aop;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.aop.ThrowsAdvice;
import com.za.exceptionhandler.demo.util.SpringContextUtil;
import com.za.exceptionhandler.router.support.Compensation;
import com.za.exceptionhandler.router.support.CompensationService;
public class ExceptionHandler implements ThrowsAdvice {
private Logger logger = Logger.getLogger(this.getClass().getName());
public void afterThrowing(Method method, Object[] args, Object target, Exception ex) throws Throwable {
logger.log(Level.INFO, args[0] + " 执行 " + method.getName() + "时有异常抛出..." + ex);
method=target.getClass().getMethod(method.getName(), method.getParameterTypes());
if (method.isAnnotationPresent(Compensation.class)) {
Compensation compensation = method.getAnnotation(Compensation.class);
CompensationService compensationService = (CompensationService) SpringContextUtil.getBean(compensation.beanName());
compensationService.process(compensation.beanName(), method, args, target, ex);
}
}
} |
补偿配置
package com.za.exceptionhandler.demo.service.impl; import java.util.concurrent.TimeoutException; import org.springframework.stereotype.Service; import com.za.exceptionhandler.demo.service.UserService; import com.za.exceptionhandler.router.support.Compensation; @Service public class UserServiceImpl implements UserService { @Compensation(beanName = "userAddCompensationService") public void addUser(String name) throws Exception { System.out.println("add user"); throw new Exception("Exception"); } @Compensation(beanName = "userUpdateCompensationService") public void updateUser(String name, String sex) throws Exception { System.out.println("update user"); throw new TimeoutException("超时。。。"); } } |
异常解析(暂未实现)
暂时没有复杂场景,如需对异常进行判断后,才可路由,可以先进行异常解析,将Exception中的堆栈进行解析,得到路由的key值
补偿路由
method=target.getClass().getMethod(method.getName(), method.getParameterTypes()); if (method.isAnnotationPresent(Compensation.class)) { Compensation compensation = method.getAnnotation(Compensation.class); CompensationService compensationService = (CompensationService) SpringContextUtil.getBean(compensation.beanName()); compensationService.process(compensation.beanName(), method, args, target, ex); } |
立即执行
package com.za.exceptionhandler.router.support; import com.za.exceptionhandler.router.support.CompensationService; import org.springframework.stereotype.Service; @Service("userAddCompensationService") public class UserAddCompensationServiceImpl implements CompensationService { @Override public void process(String key, Method method, Object[] args, Object target, Exception e) { System.out.println("新增补偿"); } } |
数据表结构
Field |
Type |
Null |
Default |
Comment |
---|---|---|---|---|
id |
bigint(20) |
NO |
(null) |
主键 |
Log_Type |
CHAR(1) |
NO |
(null) |
日志类型(0:一般代码异常;1:补偿代码异常) |
exception_name |
varchar(256) |
YES |
(null) |
异常名称 |
exception_position |
varchar(256) |
YES |
(null) |
异常位置 |
exception_detail |
text |
YES |
(null) |
异常详细信息 |
state |
char(1) |
NO |
(null) |
处理状态(0:待处理,1:处理中,2:已处理) |
routing_key |
varchar(128) |
YES |
(null) |
路由key |
compensation_class |
varchar(128) |
YES |
(null) |
补偿处理类 |
compensation_params |
text |
YES |
(null) |
补偿参数 |
compensation_detail |
text |
YES |
(null) |
补偿详细信息 |
creator |
varchar(128) |
YES |
(null) |
创建用户 |
modifier |
varchar(128) |
YES |
(null) |
更新用户 |
create_time |
datetime |
YES |
(null) |
创建时间 |
update_time |
datetime |
YES |
(null) |
更新时间 |
is_delete |
char(1) |
YES |
(null) |
是否删除 |
Ext1 |
varchar(256) |
YES |
(null) |
预留字段1 |
Ext2 |
varchar(1024) |
YES |
(null) |
预留字段2 |
发送mail通知
package com.za.exceptionhandler.demo.util; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Method; import java.util.Map; import java.util.Properties; import javax.activation.DataHandler; import javax.mail.BodyPart; import javax.mail.Message; import javax.mail.Multipart; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import javax.mail.internet.MimeUtility; import javax.mail.util.ByteArrayDataSource; import lombok.extern.slf4j.Slf4j; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.util.StringUtils; import com.alibaba.fastjson.JSON; @Slf4j public class MailUtil { private static JavaMailSenderImpl senderImpl = new JavaMailSenderImpl(); static{ // 设定mail server senderImpl.setHost("smtp.163.com"); senderImpl.setPort(25); senderImpl.setUsername("[email protected]"); // 根据自己的情况,设置username senderImpl.setPassword("xxxxxx"); // 根据自己的情况, 设置password senderImpl.setDefaultEncoding("utf-8"); Properties prop = new Properties(); prop.put("mail.smtp.auth", "true"); // 将这个参数设为true,让服务器进行认证,认证用户名和密码是否正确 prop.put("mail.smtp.timeout", "60000"); senderImpl.setJavaMailProperties(prop); } public static boolean send(String[] to, String from, String subject, String text) { try{ log.info("sent mail subject:" + subject + " from:" + from +" to:" + JSON.toJSONString(to)); // 建立邮件消息 SimpleMailMessage mailMessage = new SimpleMailMessage(); mailMessage.setTo(to); mailMessage.setFrom(from); mailMessage.setSubject(subject); mailMessage.setText(text); // 发送邮件 senderImpl.send(mailMessage); return true; }catch(Exception e){ log.error("sent mail subject:" + subject + " error",e); } return false; } public static boolean send(String[] to, String from, String subject, String text, Map |
可进行补偿情况的查询,或是需要人工处理的记录查询
补偿路由,补偿方式可动态配置