說明
初次了解AOP就自己基于AspectJ實(shí)現(xiàn)了一個(gè)打印日志的功能狰贯,后來才發(fā)現(xiàn)Spring也有相關(guān)的接口,想用的話還是使用Spring提供的接口吧,我的這套代碼還是有很多不足之處葫掉,比如對每個(gè)切點(diǎn)添加操作都要直接修改原來的代碼目尖,還是不夠優(yōu)雅。
簡述
平常我們打印日志的時(shí)候需要在每一個(gè)地方使用logger打印勇垛,aop提供了一種面向切面的方式脖母,不需要在每一個(gè)地方都寫一行代碼,而是通過配置切面在我們需要執(zhí)行的函數(shù)前后獲得切點(diǎn)闲孤,在切點(diǎn)處直接執(zhí)行相應(yīng)的方法谆级。話不多說,讓我們一起來使用吧讼积。
使用簡述
本次使用的項(xiàng)目是基于Spring MVC的web項(xiàng)目肥照,項(xiàng)目必須依賴spring,同時(shí)本項(xiàng)目基于注解配置了aop勤众,當(dāng)然你也可以xml配置aop舆绎。
Maven配置
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
相關(guān)代碼
AopConfiguration是核心,使用方式見ControllerAopConfigurationImpl文件
/**
* 控制切面的自定義操作们颜,
* 使用方式如下(基于注解):
* 1. 重寫pointCut方法并定義切點(diǎn)
* 2. 重寫對應(yīng)方法方法進(jìn)入執(zhí)行內(nèi)容
* 使用方式如下(基于xml):
* 1. 繼承該類
* 2. 配置切點(diǎn)
* 3. 配置wrap方法為對應(yīng)的before吕朵、after等
*/
@SuppressWarnings("unused")
public abstract class AopConfiguration implements ILoggerInfoHandler, IAopConfiguration {
// 不發(fā)出警告的程序最大執(zhí)行時(shí)間,單位ms
private long timeThreshold;
// 日志打印的標(biāo)簽
private String tag;
public AopConfiguration() {
timeThreshold = getTimeThreshold();
tag = getTag();
}
private Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);
protected Gson gson = new Gson();
/**
* 需要重寫并定義切點(diǎn)
*/
protected abstract void pointCutMethod();
@Data
private static class AopResult {
private String explain;
private String type;
private Integer totalSize;
private Integer totalLength;
private List<Object> subList;
}
/**
* 包裹before函數(shù)
*/
// @Before("pointCutMethod()")
public final void wrapBefore(JoinPoint joinPoint) {
String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
Object[] args = joinPoint.getArgs();
doBefore(joinPoint, parameterNames, args);
}
/**
* 包裹afterReturn函數(shù)
*/
// @AfterReturning(value = "pointCutMethod()", returning = "returnValue")
public final Object wrapAfterReturn(JoinPoint joinPoint, Object returnValue) {
return doAfterReturn(joinPoint, returnValue);
}
/**
* 包裹afterThrow函數(shù)
*/
// @AfterThrowing(value = "pointCutMethod()", throwing = "exception")
public final void wrapAfterThrow(JoinPoint joinPoint, Exception exception) throws Exception {
doAfterThrow(joinPoint, exception);
}
/**
* 包裹around函數(shù)
* @return
*/
// @Around(value = "pointCutMethod() && @annotation(methodLog)", argNames = "joinPoint")
@Around(value = "pointCutMethod()", argNames = "joinPoint")
private Object wrapAround(ProceedingJoinPoint joinPoint) throws Throwable {
String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
Object[] args = joinPoint.getArgs();
return doAround(joinPoint, parameterNames, args);
}
private void doBefore(JoinPoint joinPoint, String[] parameterNames, Object[] args) {
before(joinPoint, parameterNames, args);
String classAndMethodName = joinPoint.getSignature().toShortString();
String logContent = "before " + tag + " execute: " + classAndMethodName + "\t" +
gson.toJson(parameterNames) + " --> " + gson.toJson(args);
log(logContent);
}
private Object doAfterReturn(JoinPoint joinPoint, final Object returnValue) {
afterReturn(joinPoint, returnValue);
String classAndMethodName = joinPoint.getSignature().toShortString();
String logContent = "after " + tag + " execute: " + classAndMethodName + "\t"
+ "result --> " + gson.toJson(getResultToAopResult(returnValue));
log(logContent);
return returnValue;
}
private void doAfterThrow(JoinPoint joinPoint, Exception exception) throws Exception {
afterThrow(joinPoint, exception);
String classAndMethodName = joinPoint.getSignature().toShortString();
String logContent = "after " + tag + " execute: " + classAndMethodName + "\t"
+ "throw --> " + gson.toJson(exception.getMessage());
log(logContent);
throw exception;
}
private Object doAround(ProceedingJoinPoint joinPoint, String[] parameterNames, Object[] args) throws Throwable {
String classAndMethodName = joinPoint.getSignature().toShortString();
Object returnValue = null;
// 執(zhí)行切點(diǎn)
try {
doBefore(joinPoint, parameterNames, args);
long start = System.currentTimeMillis();
returnValue = joinPoint.proceed(args);
long duration = System.currentTimeMillis() - start;
if (duration > timeThreshold) {
warn( classAndMethodName + " execute time is too long: " + " --> " + duration + " ms");
} else {
log(classAndMethodName + " execute time: " + " --> " + duration + " ms");
}
doAfterReturn(joinPoint, returnValue);
} catch (Exception e) {
doAfterThrow(joinPoint, e);
afterThrow(joinPoint, e);
throw e;
}
return joinPoint.proceed(args);
}
/**
* 如果結(jié)果是非常長的list窥突,就要截取一部分打印到日志
* @param resultValue
* @return
*/
@SuppressWarnings("unchecked")
protected Object getResultToAopResult(final Object resultValue) {
// 如果結(jié)果太長默認(rèn)只取三條
final int maxSize = 3;
final int maxLength = 300;
AopResult aopResult = new AopResult();
if (resultValue instanceof Collection) {
Collection<Object> collection = (Collection<Object>) resultValue;
int length = gson.toJson(collection).length();
if (collection.size() > maxSize && length > maxLength) {
// 如果結(jié)果的長度大于maxSize努溃,并且字符串長度大于maxLength
// 就取出其中的maxSize條數(shù)據(jù)打印在日志
aopResult.setType(resultValue.getClass().getSimpleName());
aopResult.setExplain("截取" + maxSize + "條結(jié)果展示!");
aopResult.setTotalSize(collection.size());
aopResult.setTotalLength(length);
aopResult.setSubList(Arrays.asList(collection.toArray()).subList(0, maxSize));
return aopResult;
}
}
return resultValue;
}
protected void log(String content) {
logger.info(content);
}
protected void warn(String content) {
logger.warn(content);
}
protected void error(String content) {
logger.error(content);
}
}
ControllerAopConfigurationImpl是一個(gè)打印日志的示例阻问,您只需要修改@Pointcut里面的內(nèi)容梧税,如下文中的實(shí)例com.ninggc.template.springbootfastdemo.web.controller.*..*(..)
是指controller包下的所有類的所有方法蛇数,將其修改為自己的包即可甸鸟。
/**
* 控制controller的函數(shù)的入口和出口處打印日志
*/
@Component
@Aspect
public class ControllerAopConfigurationImpl extends AopConfiguration {
@Pointcut("execution(* com.ninggc.template.springbootfastdemo.web.controller.*..*(..))")
@Override
protected void pointCutMethod() { }
@Override
public String getTag() {
return "controller";
}
@Override
public void before(JoinPoint joinPoint, String[] parameterNames, Object[] args) {
}
@Override
public void afterReturn(JoinPoint joinPoint, Object returnValue) {
}
@Override
public void afterThrow(JoinPoint joinPoint, Exception exception) throws Exception {
}
}
其他的需要的文件
public interface IAopConfiguration {
void before(JoinPoint joinPoint, String[] parameterNames, Object[] args);
void afterReturn(JoinPoint joinPoint, final Object returnValue);
void afterThrow(JoinPoint joinPoint, Exception exception) throws Exception;
}
public interface ILoggerInfoHandler {
// 自定義輸出日志的標(biāo)簽
String getTag();
// 自定義不發(fā)出警告的程序最大執(zhí)行時(shí)間,單位ms蘑志,默認(rèn)未300
default Long getTimeThreshold() {
return 500L;
}
}
以上四個(gè)文件是全部的文件煌茬,以下是具體的代碼流程描述
AopConfiguration使用詳述
//第一類方法 wrap*方法
wrapBefore
wrapAfterReturn
wrapAfterThrow
wrapAround
//以上四個(gè)wrap*的方法是相應(yīng)的切點(diǎn)處執(zhí)行的方法斥铺,在這個(gè)實(shí)例中只在wrapAround上注解了@around,沒有涉及到其他三個(gè)方法
//第二類方法 do*方法
doBefore
doAfterReturn
doAfterThrow
doAround
//wrap*方法調(diào)用了do*方法坛善,do*方法是我打印日志的格式晾蜘,內(nèi)部調(diào)用了更具體的方法邻眷,見第三類方法
//第三類方法,這是繼承類可以改寫的方法剔交,方法功能如方法名所示
public interface IAopConfiguration {
void before(JoinPoint joinPoint, String[] parameterNames, Object[] args);
void afterReturn(JoinPoint joinPoint, final Object returnValue);
void afterThrow(JoinPoint joinPoint, Exception exception) throws Exception;
}
//打印日志的具體流程如下所示
private Object doAround(ProceedingJoinPoint joinPoint, String[] parameterNames, Object[] args) throws Throwable {
String classAndMethodName = joinPoint.getSignature().toShortString();
Object returnValue = null;
// 執(zhí)行切點(diǎn)
try {
doBefore(joinPoint, parameterNames, args); //內(nèi)部調(diào)用了before
long start = System.currentTimeMillis();
returnValue = joinPoint.proceed(args);
long duration = System.currentTimeMillis() - start;
if (duration > timeThreshold) {
warn( classAndMethodName + " execute time is too long: " + " --> " + duration + " ms");
} else {
log(classAndMethodName + " execute time: " + " --> " + duration + " ms");
}
doAfterReturn(joinPoint, returnValue); //內(nèi)部調(diào)用了afterReturn
} catch (Exception e) {
doAfterThrow(joinPoint, e); //內(nèi)部調(diào)用了afterThrow
throw e;
}
return joinPoint.proceed(args);
}