5.@AfterReturning
切入点返回结果之后执行,也就是都前置后置环绕都执行完了,这个就执行了
/**
* 执行完请求可以做的
* @param result
* @throws Throwable
*/
@AfterReturning(returning ="result", pointcut ="cutOffPoint()")
publicvoid doAfterReturning(Object result)throwsThrowable{
logger.info("大家好,我是@AfterReturning,他们都秀完了,该我上场了");
}
执行结果
应用场景可以用来在订单支付完成之后就行二次的结果验证,重要参数的二次校验,防止在方法执行中的时候参数被修改等等
6.@AfterThrowing
这个是在切入执行报错的时候执行的
// 声明错误e时指定的抛错类型法必会抛出指定类型的异常
// 此处将e的类型声明为Throwable,对抛出的异常不加限制
@AfterThrowing(throwing ="e",pointcut ="cutOffPoint()")
publicvoid doAfterReturning(Throwable e){
logger.info("大家好,我是@AfterThrowing,他们犯的错误,我来背锅");
logger.info("错误信息"+e.getMessage());
}
在其他切入内容中随意整个错误出来,制造一个环境。
下面是@AfterThrowing的执行结果
7.AOP用在全局异常处理
定义切入点拦截ResultBean或者PageResultBean
@Pointcut(value ="execution(public com.example.beans.PageResultBean *(..)))")
publicvoid handlerPageResultBeanMethod(){
}
@Pointcut(value ="execution(public com.example.beans.ResultBean *(..)))")
publicvoid handlerResultBeanMethod(){
}
下面是AopController.java
package com.example.aop;
import com.example.beans.PageResultBean;
import com.example.beans.ResultBean;
import com.example.entity.UnloginException;
import com.example.exception.CheckException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* 使用@Aspect注解将此类定义为切面类
* 根据晓风轻著的ControllerAOP所修改
* 晓风轻大佬(很大的佬哥了):https://xwjie.github.io/
*/
@Aspect
@Component
publicclassAopController{
privatestaticfinalLogger logger =LoggerFactory.getLogger(AopController.class);
ThreadLocal<ResultBean> resultBeanThreadLocal =newThreadLocal<>();
ThreadLocal<PageResultBean<?>> pageResultBeanThreadLocal =newThreadLocal<>();
ThreadLocal<Long> start =newThreadLocal<>();
/**
* 定义一个切点
*/
@Pointcut(value ="execution(public com.example.beans.PageResultBean *(..)))")
publicvoid handlerPageResultBeanMethod(){
}
@Pointcut(value ="execution(public com.example.beans.ResultBean *(..)))")
publicvoid handlerResultBeanMethod(){
}
@Around("handlerPageResultBeanMethod()")
publicObject handlerPageResultBeanMethod(ProceedingJoinPoint pjp){
start.set(System.currentTimeMillis());
try{
pageResultBeanThreadLocal.set((PageResultBean<?>)pjp.proceed());
logger.info(pjp.getSignature()+" 方法执行耗时:"+(System.currentTimeMillis()- start.get()));
}catch(Throwable e){
ResultBean<?> resultBean = handlerException(pjp , e);
pageResultBeanThreadLocal.set(newPageResultBean<>().setMsg(resultBean.getMsg()).setCode(resultBean.getCode()));
}
return pageResultBeanThreadLocal.get();
}
@Around("handlerResultBeanMethod()")
publicObject handlerResultBeanMethod(ProceedingJoinPoint pjp){
start.set(System.currentTimeMillis());
try{
resultBeanThreadLocal.set((ResultBean<?>)pjp.proceed());
logger.info(pjp.getSignature()+" 方法执行耗时:"+(System.currentTimeMillis()- start.get()));
}catch(Throwable e){
resultBeanThreadLocal.set(handlerException(pjp , e));
}
return resultBeanThreadLocal.get();
}
/**
* 封装异常信息,注意区分已知异常(自己抛出的)和未知异常
*/
privateResultBean<?> handlerException(ProceedingJoinPoint pjp,Throwable e){
ResultBean<?> result =newPageResultBean();
logger.error(pjp.getSignature()+" error ", e);
// 已知异常
if(e instanceofCheckException){
result.setMsg(e.getLocalizedMessage());
result.setCode(ResultBean.FAIL);
}elseif(e instanceofUnloginException){
result.setMsg("Unlogin");
result.setCode(ResultBean.NO_LOGIN);
}else{
result.setMsg(e.toString());
result.setCode(ResultBean.FAIL);
}
return result;
}
}
用上面的环绕通知可以对所有返回ResultBean或者PageResultBean的方法进行切入,这样子就不用在业务层去捕捉错误了,只需要去打印自己的info日志。
看下面一段代码
@Transactional
@Override
publicint insertSelective(Area record){
record.setAddress("test");
record.setPostalcode(88888);
record.setType(3);
int i=0;
try{
i = areaMapper.insertSelective(record);
}catch(Exception e){
logger.error("AreaServiceImpl insertSelective error:"+e.getMessage());
}
return i;
}
假如上面的插入操作失败出错了? 你认为会回滚吗?
答案是:不会。
为什么?
因为你把错误捕捉了,事物没检测到异常就不会回滚。
那么怎么才能回滚呢?
在catch里加throw new RuntimeException().
可是那么多业务方法每个设计修改的操作都加,代码繁琐,怎么进行处理呢?
在这里用到上面的AOP切入处理,错误不用管,直接抛,抛到控制层进行处理,这样的话,接口调用的时候,出错了,接口不会什么都不返回,而是会返回给你错误代码,以及错误信息,便于开发人员查错。
8.以上用的是log4j2的日志处理
先移除springboot自带的log日志处理
在build.gradle中增加
configurations {
providedRuntime
// 去除SpringBoot自带的日志
all*.exclude group:'org.springframework.boot',module:'spring-boot-starter-logging'
}
ext {
springBootVersion ='2.0.1.RELEASE'
}
dependencies {
compile "org.springframework.boot:spring-boot-starter-log4j2:${springBootVersion}"
}
然后在application.yml中增加
#显示mysql执行日志
logging:
level:
com:
example:
dao: debug
config: classpath:log4j2-spring.xml
log4j2-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configurationstatus="INFO"monitorInterval="30">
<!--先定义所有的appender-->
<appenders>
<!--这个输出控制台的配置-->
<consolename="Console"target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayoutpattern="%highlight{[ %p ] [%-d{yyyy-MM-dd HH:mm:ss}] [ LOGID:%X{logid} ] [%l] %m%n}"/>
</console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
<Filename="Test"fileName="logs/test.log"append="false">
<PatternLayoutpattern="%highlight{[ %p ] %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] [%l] %m%n}"/>
</File>
<RollingFilename="RollingFileInfo"fileName="logs/log.log"filePattern="logs/info.log.%d{yyyy-MM-dd}">
<!-- 只接受level=INFO以上的日志 -->
<ThresholdFilterlevel="info"onMatch="ACCEPT"onMismatch="DENY"/>
<PatternLayoutpattern="%highlight{[ %p ] [%-d{yyyy-MM-dd HH:mm:ss}] [ LOGID:%X{logid} ] [%l] %m%n}"/>
<Policies>
<TimeBasedTriggeringPolicymodulate="true"interval="1"/>
<SizeBasedTriggeringPolicy/>
</Policies>
</RollingFile>
<RollingFilename="RollingFileError"fileName="logs/error.log"filePattern="logs/error.log.%d{yyyy-MM-dd}">
<!-- 只接受level=WARN以上的日志 -->
<Filters>
<ThresholdFilterlevel="warn"onMatch="ACCEPT"onMismatch="DENY"/>
</Filters>
<PatternLayoutpattern="%highlight{[ %p ] %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] [%l] %m%n}"/>
<Policies>
<TimeBasedTriggeringPolicymodulate="true"interval="1"/>
<SizeBasedTriggeringPolicy/>
</Policies>
</RollingFile>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<loggername="org.springframework"level="INFO"></logger>
<loggername="org.mybatis"level="INFO"></logger>
<rootlevel="all">
<appender-refref="Console"/>
<appender-refref="Test"/>
<appender-refref="RollingFileInfo"/>
<appender-refref="RollingFileError"/>
</root>
</loggers>
</configuration>
之后在你要打印日志的类中增加
privatestaticfinalLogger logger =LoggerFactory.getLogger(你的类名.class);
publicstaticvoid main(String[] args){
logger.error("error级别日志");
logger.warn("warning级别日志");
logger.info("info级别日志");
}
有了日志后就很方便了,在你的方法接收对象时打印下,然后执行了逻辑之后打印下, 出错之后很明确了,就会很少去Debug的,养成多打日志的好习惯,多打印一点info级别的日志,用来在开发环境使用,在上线的时候把打印的最低级别设置为warning,这样你的info级别日志也不会影响到项目的重要Bug的打印
写这个博客的时候我也在同时跑着这个项目,有时候会出现一些错误,例如jar包版本,业务层引用无效,AOP设置不生效等等,也同时在排查解决,如果你遇到了同样的错误,可以去我的GitHub联系我,如小弟有时间或许也能帮到你,谢谢
Github地址:https://github.com/cuifuan