AOP(Aspect Oriented Programming)是一种面向切面的编程思想。面向切面编程是将程序抽象成各个切面,即解剖对象的内部,将那些影响了多个类的公共行为抽取到一个可重用模块里,减少系统的重复代码,降低模块间的耦合度,增强代码的可操作性和可维护性。
AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理、增强处理。
AOP 的初衷是,保证开发者不修改源代码的前提下,为系统中的业务组件添加某种通用功能。AOP 的本质是由 AOP 框架修改业务组件的源代码,达到增强功能的目的。按照 AOP 框架修改源代码的时机,可以将其分为两类:
静态 AOP 实现:AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
动态 AOP 实现:AOP 框架在运行阶段动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP。
通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
连接点(Join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
切点(PointCut): 可以插入增强处理的连接点。
切面(Aspect): 切面是通知和切点的结合。
引入(Introduction):允许我们向现有的类添加新的方法或者属性。
织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的代理对象。
在spring中尝试使用AspectJ其他指示器时,将会抛出IllegalArgumentException异常。当我们查看上面展示的spring支持的指示器时,注意只有execution指示器是唯一的执行匹配,而其他的指示器都是用于限制匹配的。这说明execution指示器是我们在编写切点定义时最主要使用的指示器,在此基础上,我们使用其他指示器来限制所匹配的切点。
下面的切点表达式表示当Instrument的play方法执行时会触发通知。
execution(* com.alibaba.spring.aop.Instrument.play(..))
--execution表示在方法执行时触发
--*表示返回任意类型
--com.alibaba.spring.aop.Instrument表示方法所属的类型
--play表示切点切入的方法名称
--..表示使用任意参数
我们使用execution指示器选择Instrument的play方法,方法表达式以 *
号开始,标识我们不关心方法的返回值类型。然后我们指定了全限定类名和方法名。对于方法参数列表,我们使用 ..
标识切点选择任意的play方法,无论该方法的入参是什么。多个匹配之间我们可以使用链接符 &&
、||
、!
来表示 “且”、“或”、“非”的关系。但是在使用 XML 文件配置时,这些符号有特殊的含义,所以我们使用 “and”、“or”、“not”来表示。举例:
限定该切点仅匹配的包是com.alibaba.spring.aop
,可以使用execution(* com.alibaba.spring.aop.IBuy.buy(..)) && within(com.alibaba.spring.aop.*)
在切点中选择 bean,可以使用execution(*com.alibaba.spring.aop.IBuy.buy(..)) && bean(girl)
使用 @Pointcut
注解声明切点表达式,然后使用表达式,代码如下:
package com.alibaba.spring.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class BuyAspectJ {
@Pointcut("execution(* com.alibaba.spring.aop.IBuy.buy(..))")
public void point(){}
@Before("point()")
public void hehe() {
System.out.println("before ...");
}
@After("point()")
public void haha() {
System.out.println("After ...");
}
@AfterReturning("point()")
public void xixi() {
System.out.println("AfterReturning ...");
}
@Around("point()")
public void xxx(ProceedingJoinPoint pj) {
try {
System.out.println("Around aaa ...");
pj.proceed();
System.out.println("Around bbb ...");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
通过args声明参数表达式,然后使用参数表达式,代码如下:
package com.alibaba.spring.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class BuyAspectJ {
@Pointcut("execution(String com.alibaba.spring.aop.IBuy.buy(double)) && args(price) && bean(girl)")
public void gif(double price) {
}
@Around("gif(price)")
public String hehe(ProceedingJoinPoint pj, double price){
try {
pj.proceed();
if (price > 68) {
System.out.println("女孩买衣服超过了68元,赠送一双袜子");
return "衣服和袜子";
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return "衣服";
}
}
在配置文件中,我们用注解 @EnableAspectJAutoProxy() 启用Spring AOP 的时候,通过proxyTargetClass的赋值来决定Spring AOP动态代理机制。proxyTargetClass为false时,是通过jdk基于接口方式进行织入,这时候代理生成的是一个接口对象;proxyTargetClass 为 true时,则会使用 cglib 的动态代理方式,这时候代理生成的是一个继承代理对象, 这种方式的缺点是拓展类的方法被final
修饰时,无法进行织入。代码如下:
package com.alibaba.spring;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}
对于频繁重复使用的切点表达式,我们也可以声明成切点。配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="boy" class="com.alibaba.spring.aop.Boy"></bean>
<bean id="girl" class="com.alibaba.spring.aop.Girl"></bean>
<bean id="buyAspectJ" class="com.alibaba.spring.aop.BuyAspectJ"></bean>
<aop:config proxy-target-class="true">
<aop:pointcut id="apoint" expression="execution(* com.alibaba.spring.aop.IBuy.buy(..))"/>
<aop:aspect id="qiemian" ref="buyAspectJ">
<aop:before pointcut-ref="apoint" method="hehe"/>
<aop:after pointcut-ref="apoint" method="haha"/>
<aop:after-returning pointcut-ref="apoint" method="xixi"/>
<aop:around pointcut-ref="apoint" method="xxx"/>
</aop:aspect>
</aop:config>
</beans>
通过args声明参数表达式,然后使用参数表达式,配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="boy" class="com.alibaba.spring.aop.Boy"></bean>
<bean id="girl" class="com.alibaba.spring.aop.Girl"></bean>
<bean id="buyAspectJ" class="com.alibaba.spring.aop.BuyAspectJ"></bean>
<aop:config proxy-target-class="true">
<aop:pointcut id="apoint" expression="execution(String com.alibaba.spring.aop.IBuy.buy(double)) and args(price) and bean(girl)"/>
<aop:aspect id="qiemian" ref="buyAspectJ">
<aop:around pointcut-ref="apoint" method="hehe"/>
</aop:aspect>
</aop:config>
</beans>
CGlib 代理方式:
<aop:config proxy-target-class="true"> </aop:config>
JDK 代理方式:
<aop:config proxy-target-class="false"> </aop:config>
静态代理,代理类和被代理类实现了同样的接口,代理类同时持有被代理类的引用,这样,当我们需要调用被代理类的方法时,可以通过调用代理类的方法来做到。
定义一个接口
package com.alibaba.spring.aop;
public interface IWork {
void meeting();
int evaluate(String name);
}
然后定义Leader类
package com.alibaba.spring.aop;
import java.util.Random;
public class Leader implements IWork {
@Override
public void meeting() {
System.out.println("领导早上要组织会议");
}
@Override
public int evaluate(String name) {
int score = new Random(System.currentTimeMillis()).nextInt(20) + 80;
System.out.println(String.format("领导给%s的考评为%s分", name, score));
return score;
}
}
Secretary类
package com.alibaba.spring.aop;
public class Secretary implements IWork {
private Leader mLeader;
public Secretary(Leader mLeader) {
this.mLeader = mLeader;
}
@Override
public void meeting() {
System.out.println("秘书先给老板准备材料");
mLeader.metting();
}
@Override
public int evaluate(String name) {
return mLeader.evaluate(name);
}
}
测试类
package com.alibaba.spring.aop;
public class TestApp {
public static void main(String[] args) {
Leader leader = new Leader();
Secretary secretary = new Secretary(leader);
secretary.meeting();
secretary.evaluate("Joy");
}
}
执行结果
动态代理根据实现方式的不同可以分为 JDK 动态代理和 CGlib 动态代理。JDK 动态代理是利用反射机制生成一个实现代理接口的类,在调用具体方法前调用InvokeHandler来处理。CGlib 动态代理是利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。区别是JDK代理只能对实现接口的类生成代理;CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。
首先,定一个类实现 InvocationHandler
接口,并实现 invoke 方法:
package com.alibaba.spring.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class WorkInvocationHandler implements InvocationHandler {
private Object object;
public WorkInvocationHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("object: " + object.getClass().getSimpleName());
System.out.println("proxy: " + proxy.getClass().getSimpleName());
if ("meeting".equals(method.getName())) {
System.out.println("代理先准备会议材料...");
return method.invoke(object, args);
} else if ("evaluate".equals(method.getName())) {
if(args[0] instanceof String) {
if ("James".equals(args[0])) {
System.out.println("James 犯过错误,所以考评分数较低...");
return 70;
}
}
return method.invoke(object, args);
}
return null;
}
}
然后通过 Proxy.newProxyInstance() 方法创建代理对象:
package com.alibaba.spring.aop;
import java.lang.reflect.Proxy;
public class TestApp {
public static void main(String[] args) {
Leader leader = new Leader();
IWork proxy = (IWork) Proxy.newProxyInstance(Leader.class.getClassLoader(),
new Class[]{IWork.class}, new WorkInvocationHandler(leader));
proxy.meeting();
proxy.evaluate("Joy");
proxy.evaluate("James");
}
}
输出结果:
首先在build.gradle 文件添加 cglib 依赖:
dependencies {
// 引入 cglib 库
compile 'cglib:cglib:3.1'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
cglib 针对类进行代理,我们先创建一个类实现 MethodInterceptor接口:
package com.alibaba.spring.aop;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class LeaderMethodInterceptor implements MethodInterceptor {
/**
*
* @param obj 表示增强的对象,即实现这个接口类的一个对象
* @param method 表示要被拦截的方法
* @param args 表示要被拦截方法的参数
* @param proxy 表示要触发父类的方法对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if ("meeting".equals(method.getName())) {
System.out.println("代理先准备会议材料...");
return methodProxy.invokeSuper(o, objects);
} else if ("evaluate".equals(method.getName())) {
if(objects[0] instanceof String) {
if ("James".equals(objects[0])) {
System.out.println("James 犯过错误,所以考评分数较低...");
return 70;
}
}
return methodProxy.invokeSuper(o, objects);
}
return null;
}
}
测试代码:
package com.alibaba.spring.aop;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
import java.lang.reflect.Proxy;
public class TestApp {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer(); // 通过CGLIB动态代理获取代理对象的过程
enhancer.setSuperclass(Leader.class); // 设置enhancer对象的父类
enhancer.setCallback(new LeaderMethodInterceptor()); // 设置enhancer的回调对象
Leader proxy= (Leader)enhancer.create(); // 创建代理对象
// 通过代理对象调用目标方法
proxy.meeting();
proxy.evaluate("Joy");
proxy.evaluate("James");
}
}
结果如下:
自定义DigestAopLog日志注解
package com.alibaba.alsc.activity.common.util.aspect.annotated;
import com.alibaba.alsc.activity.common.util.aspect.ExtraDigestInfoGenerator;
import com.alibaba.alsc.activity.common.util.aspect.NoGen;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DigestAopLog {
/**
* 日志标记
*
* @return
*/
String tag() default "DEFAULT";
/**
* 日志名
*
* @return
*/
String logFileName();
/**
* 外部方法名
*
* @return
*/
String aopCode() default "";
/**
* 超时警告时间
*
* @return
*/
int timeoutWarn() default 30000;
/**
* 是否打印入参
*
* @return
*/
boolean printParams() default false;
Class<? extends ExtraDigestInfoGenerator> extraDigestInfoGenerator() default NoGen.class;
}
自定义DigestAopLog日志注解
package com.alibaba.alsc.activity.common.util.aspect.annotated;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DetailAopLog {
/**
* 日志标记
*
* @return
*/
String tag() default "DEFAULT";
/**
* 日志名
*
* @return
*/
String logFileName();
/**
* 外部方法名
*
* @return
*/
String aopCode() default "";
/**
* 是否打印结果
*
* @return
*/
boolean printResult() default true;
/**
* 超时警告时间
*
* @return
*/
int timeoutWarn() default 30000;
/**
* true需要抛出异常
*/
boolean needThrow() default true;
/**
* 日志等级 INFO,DEBUG
*
* @return
*/
String logLevel() default "INFO";
}
统一日志处理器
package com.alibaba.alsc.activity.common.util.aspect;
import com.alibaba.alsc.activity.common.util.LoadTestUtils;
import com.alibaba.alsc.activity.common.util.UUIDUtil;
import com.alibaba.alsc.activity.common.util.aspect.annotated.DetailAopLog;
import com.alibaba.alsc.activity.common.util.aspect.annotated.DigestAopLog;
import com.alibaba.alsc.activity.common.util.log.LoggerNames;
import com.alibaba.alsc.activity.common.util.log.LoggerTages;
import com.alibaba.alsc.activity.common.util.log.LoggerUtils;
import com.alibaba.fastjson.JSON;
import com.taobao.eagleeye.EagleEye;
import com.taobao.hsf.util.RequestCtxUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
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.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Component
@Aspect
@Order(300)
public class AopLogAspect {
public static Logger monitorLog = LoggerFactory.getLogger(LoggerNames.C_MONITOR);
private static final Map<Method, ExtraDigestInfoGenerator> methodExtraDigestInfoGeneratorMap =
new ConcurrentHashMap<>();
@Pointcut("@annotation(com.alibaba.alsc.activity.common.util.aspect.annotated.DigestAopLog)")
public void digestAopLog() {
}
@Pointcut("@annotation(com.alibaba.alsc.activity.common.util.aspect.annotated.DetailAopLog)")
public void detailAopLog() {
}
@Around("digestAopLog()")
public Object aroundDigestAopLog(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = null;
long startTimes = System.nanoTime();
String logStr = "";
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
DigestAopLog detailAspectLog = method.getAnnotation(DigestAopLog.class);
// 如果facade入口已经有entryPoint,则不再重复打印摘要
if ("FACADE-C".equals(detailAspectLog.tag()) && EagleEye.getUserData("entryPoint") != null) {
return joinPoint.proceed();
}
boolean hasException = false;
boolean hasMDCRequestId = true;
try {
//获取mdcid
String mdcRequestId = MDC.get(LoggerTages.MDC_REQUEST_ID);
if (StringUtils.isBlank(mdcRequestId)) {
hasMDCRequestId = false;
MDC.put(LoggerTages.MDC_REQUEST_ID, UUIDUtil.generateUUID());
}
logStr = startLog(detailAspectLog.tag(), joinPoint, detailAspectLog.aopCode(), detailAspectLog.printParams());
Object[] args = joinPoint.getArgs();
result = joinPoint.proceed(args);
return result;
} catch (Throwable ex) {
hasException = true;
throw ex;
} finally {
if (hasException) {
logStr += "|||EXCEPTION";
LoggerUtils.info(monitorLog, "|errorStatics|" + detailAspectLog.aopCode() + "|");
} else {
logStr += "|||OK";
}
logStr += "|||" + endLog(result, startTimes, false, detailAspectLog.timeoutWarn(),
detailAspectLog.extraDigestInfoGenerator(), joinPoint.getArgs(),
detailAspectLog.logFileName(), null, method);
LoggerUtils.info(getLogger(detailAspectLog.logFileName()), logStr);
//如果没有MDC则说明是最外层的facade日志,因此要移除
if (!hasMDCRequestId) {
MDC.remove(LoggerTages.MDC_REQUEST_ID);
}
}
}
@Around("detailAopLog()")
public Object aroundDetailAopLog(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = null;
long startTimes = System.nanoTime();
String logStr = "";
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
DetailAopLog detailAspectLog = method.getAnnotation(DetailAopLog.class);
boolean hasMDCRequestId = true;
try {
//获取mdcid
String mdcRequestId = MDC.get(LoggerTages.MDC_REQUEST_ID);
if (StringUtils.isBlank(mdcRequestId)) {
hasMDCRequestId = false;
MDC.put(LoggerTages.MDC_REQUEST_ID, UUIDUtil.generateUUID());
}
logStr = startLog(detailAspectLog.tag(), joinPoint, detailAspectLog.aopCode(), true);
Object[] args = joinPoint.getArgs();
result = joinPoint.proceed(args);
return result;
} catch (Throwable ex) {
logStr += "|||" + exceptionLog(ex.getMessage());
if (detailAspectLog.needThrow()) {
throw ex;
} else {
LoggerUtils.error(ex, LoggerLoop.getLogger(LoggerLoop.COMMON_ERROR), "");
}
return result;
} finally {
logStr += "|||" + endLog(result,
startTimes,
printResult(detailAspectLog.printResult()),
detailAspectLog.timeoutWarn(), null, joinPoint.getArgs(),
detailAspectLog.logFileName(), detailAspectLog.logLevel(), method);
LoggerUtils.info(getLogger(detailAspectLog.logFileName()), logStr);
//如果没有MDC则说明是最外层的facade日志,因此要移除
if (!hasMDCRequestId) {
MDC.remove(LoggerTages.MDC_REQUEST_ID);
}
}
}
private boolean printResult(boolean print) {
if (LoadTestUtils.isLoadTestMode()) {
// 压测且开关不打印
return false;
}
return print;
}
private Logger getLogger(String logFileName) {
return LoggerLoop.getLogger(logFileName);
}
/**
* @param tag
* @param joinPoint
* @param digestIdentificationCode
* @return
*/
private String startLog(String tag, ProceedingJoinPoint joinPoint, String digestIdentificationCode, boolean printParams) {
StringBuilder logInfo = new StringBuilder(256);
logInfo.append(ProcessType.START.getTypeName());
logInfo.append(" Tag:(").append(tag)
.append(") FromSys:(").append(RequestCtxUtil.getAppNameOfClient());
if (StringUtils.isNotEmpty(digestIdentificationCode)) {
logInfo.append(") Method:(").append(digestIdentificationCode);
} else {
logInfo.append(") Method:(").append(joinPoint.getSignature().getDeclaringTypeName())
.append(".").append(joinPoint.getSignature().getName());
}
if (printParams) {
logInfo.append(") Params:(").append(JSON.toJSONString(joinPoint.getArgs()))
.append(")");
} else {
logInfo.append(") ");
}
return logInfo.toString();
}
private String exceptionLog(Object result) {
StringBuilder logInfo = new StringBuilder(256);
logInfo.append(ProcessType.EXCEPTION.getTypeName());
appendResult(logInfo, result);
return logInfo.toString();
}
private boolean printResultEnabled(String logFileName, String logLevel) {
Logger logger = getLogger(logFileName);
if ((logLevel == null || "INFO".equals(logLevel)) && logger.isInfoEnabled()) {
return true;
}
if ("DEBUG".equals(logLevel) && logger.isDebugEnabled()) {
return true;
}
return false;
}
private String endLog(Object result, long startTimes, boolean printResult, int timeoutWarn,
Class<? extends ExtraDigestInfoGenerator> genClass, Object[] args,
String logFileName, String logLevel, Method method) {
StringBuilder logInfo = new StringBuilder(256);
logInfo.append(ProcessType.END.getTypeName());
if (printResult && printResultEnabled(logFileName, logLevel)) {
appendResult(logInfo, result);
}
long millisecond = (System.nanoTime() - startTimes) / 1000000;
if (millisecond <= 0) {
millisecond = 1;
}
logInfo.append(" elapsedTime:(").append(millisecond).append("ms)");
if (timeoutWarn <= millisecond) {
logInfo.append(" timeoutWarn:(").append(timeoutWarn).append("ms)");
}
if (genClass != null && NoGen.class != genClass) {
try {
ExtraDigestInfoGenerator extraDigestInfoGenerator = methodExtraDigestInfoGeneratorMap.get(method);
if (extraDigestInfoGenerator == null) {
extraDigestInfoGenerator = genClass.newInstance();
methodExtraDigestInfoGeneratorMap.put(method, extraDigestInfoGenerator);
}
List<Object> generate = extraDigestInfoGenerator.generate(args, result);
if (CollectionUtils.isNotEmpty(generate)) {
String extDigestStr = generate.stream().map(g -> g == null ? "-" : g.toString()).collect(Collectors.joining("|||"));
logInfo.append("|||").append(extDigestStr).append("|||");
}
} catch (Throwable t) {
LoggerUtils.error(t, monitorLog, "extraDigestInfoGenerator error");
}
}
return logInfo.toString();
}
private void appendResult(StringBuilder logInfo, Object result) {
logInfo.append(" result:(");
if (result == null) {
logInfo.append("null");
} else {
try {
logInfo.append(JSON.toJSONString(result));
} catch (Exception ex) {
try {
logInfo.append(result.toString());
} catch (Exception ex1) {
logInfo.append("SerializeFailed");
}
}
}
logInfo.append(")");
}
private enum ProcessType {
START("TraceStart"),
EXCEPTION("TraceException"),
END("TraceEnd");
private String typeName;
ProcessType(String typeName) {
this.typeName = typeName;
}
public String getTypeName() {
return this.typeName;
}
}
}
日志输出
TraceStart Tag: (INTEGRATION) FromSys: (kangaroo - server) Method: (alsc - dmp.mapping) Params: ([{
"extInfo": {},
"targets": ["ELE_UID", "ALSC_UID"],
"uType": "HAVANA_ID",
"uid": "927062106"
}]) || | TraceEnd result: ({
"extInfos": {},
"success": true,
"targets": {
"ALSC_UID": "2204471881625",
"ELE_UID": "142802603"
}
}) elapsedTime: (5 ms)[LoadTest: false]
自定义注解
package com.alibaba.alsc.activity.common.util.aspect.annotated;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface SentinelAop {
/**
* 限流资源名称, 作为限流簇点资源名,不填默认为interfaceName#methodName
*
* @return
*/
String resourceName() default "";
/**
* 限流参数名称,方法可按照填入的参数名进行限流,只能对请求对象的第一个参数对象中的第一层参数进行限流,限流参数需要为基本类型
*
* @return
*/
String[] limitParams();
/**
* 是否需要抛出限流异常,不抛出异常默认返回下游为空
* 如需返回特定错误码需要在com.alibaba.alsc.activity.c.aspect.SentinelAspect识别特定的返回类型
* 目前已适配返回值类型AlscResponse、BizResult,异常码SENTINEL_LIMIT
*
* @return
*/
boolean needThrowException() default true;
}
限流处理器
package com.alibaba.alsc.activity.c.aspect;
import com.ali.unit.rule.util.lang.CollectionUtils;
import com.alibaba.alsc.activity.c.service.dto.AlscResponse;
import com.alibaba.alsc.activity.c.service.enums.ErrorCodeEnum;
import com.alibaba.alsc.activity.common.util.ClassUtil;
import com.alibaba.alsc.activity.common.util.aspect.LoggerLoop;
import com.alibaba.alsc.activity.common.util.aspect.annotated.SentinelAop;
import com.alibaba.alsc.activity.common.util.log.LoggerUtils;
import com.alibaba.common.lang.StringUtil;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.sea.common.recommend.response.BizResult;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.MapUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeanUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
@Component
@Aspect
@Order(2)
public class SentinelAspect {
/**
* 限流切面
*
* @param proceedingJoinPoint
* @throws Throwable
*/
@Around(value = "@annotation(sentinelAop)")
public Object doExecute(ProceedingJoinPoint proceedingJoinPoint, SentinelAop sentinelAop) throws Throwable {
Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
String className = ClassUtil.getMethodInterfaceClassName(method);
String methodName = method.getName();
Class<?> returnType = method.getReturnType();
if (sentinelAop == null || sentinelAop.limitParams().length <= 0) {
return proceedingJoinPoint.proceed();
}
String resourceName = StringUtil.isBlank(sentinelAop.resourceName()) ?
className + "#" + methodName : sentinelAop.resourceName();
List<String> sentinelTag = Lists.newArrayList();
Object[] args = proceedingJoinPoint.getArgs();
if (args != null && args.length > 0 && args[0] != null
&& !BeanUtils.isSimpleProperty(args[0].getClass()) && !(args[0] instanceof Collection)) {
JSONObject argJson = null;
try {
argJson = (JSONObject) JSONObject.toJSON(args[0]);
} catch (Exception e) {
//忽略异常,对于不能正常解析的参数不打点
}
if (argJson == null) {
return proceedingJoinPoint.proceed();
}
for (String limitParam : sentinelAop.limitParams()) {
Object limitParamValue = MapUtils.getObject(argJson, limitParam);
sentinelTag.add(limitParamValue == null ? null : limitParamValue.toString());
}
}
Entry entry = null;
try {
if (CollectionUtils.isNotEmpty(sentinelTag)) {
entry = SphU.entry(resourceName, EntryType.IN, 1, sentinelTag.toArray());
}
return proceedingJoinPoint.proceed();
} catch (BlockException be) {
if (sentinelAop.needThrowException()) {
throw be;
}
LoggerUtils.error(be, LoggerLoop.getLogger(LoggerLoop.COMMON_ERROR), "");
return buildLimitResult(returnType);
} finally {
// 注意:exit 的时候也一定要带上对应的参数,否则可能会有统计错误。
if (entry != null) {
entry.exit(1, sentinelTag);
}
}
}
private Object buildLimitResult(Class<?> returnType) {
try {
if (BizResult.class.isAssignableFrom(returnType)) {
return new BizResult(ErrorCodeEnum.SENTINEL_LIMIT.getKey(), ErrorCodeEnum.SENTINEL_LIMIT.getDescription());
} else if (AlscResponse.class.isAssignableFrom(returnType)) {
return new AlscResponse(false,
ErrorCodeEnum.SENTINEL_LIMIT.getKey(), ErrorCodeEnum.SENTINEL_LIMIT.getDescription());
}
} catch (Exception e) {
LoggerUtils.error(e, LoggerLoop.getLogger(LoggerLoop.COMMON_ERROR), "");
}
return null;
}
}
使用示例
/**
*limitParams 唯一标识字段
*
*
**/
@SentinelAop(limitParams = {"sceneCode", "optionName"})
public Response methodName(Request request) {
}