一、spring aop的优缺点
优点:低侵入式设计,耦合性低,维护性高。
缺点:采用反射生成代理对象,性能上有瓶颈;织入增强处理都是同步的单一线程,总是在方法返回之前进行。
二、业务场景分析
对一般的业务场景,我们采用aop是可以的。但是如果我们的织入处理很复杂,而且与方法的返回无关。比如复杂日志的记录,这时候我们考虑异步方式去完成代理方法的增强处理。大型的系统,可以采用aop+MQ,自己维护一个消息队列系统,在对方法进行拦截后,只需发送消息到MQ系统,告诉另一个业务系统需要做什么。现在的业务场景是,下游客户调用我们API,我们API调用上游API,需要将调用情况记录数据库进行计费。这里,通过一个单例队列来存放“消息”,并开启一个线程轮询从队列取“消息”,再进行“消费”处理。
三、代码实践
这里采用AspectJ的方式,完成aop动态代理:
存放消息的队列:
/**
* 存放消息的队列
* 这里采用无界队列LinkedBlockingQueue,由于存的对象可能使上游计费对象,
* 也可能是下游计费对象,这里用泛型
* @author robert
*
* @param <T>
*/
@Component
public class ChargingQueue<T> {
private BlockingQueue<T> chargeQueue = new LinkedBlockingQueue<T>();
public void add(T t) {
chargeQueue.add(t);
}
public T poll() throws InterruptedException {
return chargeQueue.poll(1, TimeUnit.SECONDS);
}
}切点类:
/**
*
* @ClassName: ChargeAspect
* @Description: 切点类
* @author robert
* @date 2017-7-6
*
*/
@Aspect
@Component
public class ChargeAspect {
@Autowired
private ChargingQueue chargingQueue;
// 本地异常日志记录对象
private static final Logger logger = Logger.getLogger(ChargeAspect.class);
// 上游计费切点
@Pointcut("@annotation(com.roman.api.aop.async.charge.UpStreamChargingLog)")
public void upStreamCharging() {
}
// 下游计费切点
@Pointcut("@annotation(com.roman.api.aop.async.charge.DownStreamChargingLog)")
public void downStreamCharging() {
}
@Around("downStreamCharging()")
public String doAroundDown(ProceedingJoinPoint pjp) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
ApiDownCharging downLog = new ApiDownCharging();
Map<String, String> headMap = (Map<String, String>) request.getAttribute(Constants.headMap);
String appId = headMap.get(Constants.appId);
//这里由于下游唯一号只有本线程产生,故采用线程ID来保证唯一,不需要锁来控制,无并发量一说,当然,这里是单机的,分布式则不可取,需生成全局id
downLog.setUniquecode(DateUtil.getDateFormatPatter(new Date()) + Thread.currentThread().getId());
downLog.setApiUserid(appId);
downLog.setApiMsg(request.getServletPath());
downLog.setStartTime(new Date());
String upRes = "";
String repCode;
String repMsg;
try {
upRes = pjp.proceed()==null?"":(String)pjp.proceed();
repCode = ErrorBussnessExceptionEnums.INVOKE_SUCCESS.getErrorCode();
repMsg = upRes;
} catch (Throwable e) {
e.printStackTrace();
repCode = ErrorBussnessExceptionEnums.INVOKE_FAILED.getErrorCode();
repMsg = ErrorBussnessExceptionEnums.INVOKE_FAILED.getErrorMessage();
}
downLog.setEndTime(new Date());
downLog.setReturnCode(repCode);
downLog.setReturnMessage(repMsg);
chargingQueue.add(downLog);
return upRes;
}
@Around("upStreamCharging()")
public String doAroundUp(ProceedingJoinPoint pjp) {
ApiUpCharging upLog = new ApiUpCharging();
String uniquecode = DateUtil.getDateFormatPatter(new Date()) + Thread.currentThread().getId();
upLog.setUniquecode(uniquecode);
upLog.setStarttime(new Date());
String upRes = "";
String repCode;
String repMsg;
try {
upRes = (String) pjp.proceed();
repCode = ErrorBussnessExceptionEnums.INVOKE_SUCCESS.getErrorCode();
repMsg = upRes;
} catch (Throwable e) {
logger.info(e.getMessage());
repCode = ErrorBussnessExceptionEnums.INVOKE_FAILED.getErrorCode();
repMsg = ErrorBussnessExceptionEnums.INVOKE_FAILED.getErrorMessage();
}
upLog.setEndtime(new Date());
upLog.setReturnCode(repCode);
upLog.setReturnMessage(repMsg);
try {
upLog.setApiMsg(getUpStreamApiInfo(pjp));
} catch (Exception e) {
logger.info(e.getMessage());
upLog.setApiMsg("获取上游接口名出错,请查看uniquecode:" + uniquecode + "的出错日志");
}
chargingQueue.add(upLog);
return upRes;
}
/**
* 获取注解中对方法的描述信息 用于upStream层注解
*
* @param joinPoint
* 切点
* @return 方法描述
* @throws Exception
*/
public static String getUpStreamApiInfo(JoinPoint joinPoint) throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
String apiInfo = "";
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
apiInfo = method.getAnnotation(UpStreamChargingLog.class).apiInfo();
break;
}
}
}
return apiInfo;
}
}队列消费者:
/**
*
* @ClassName: ChargingQueueCosumer
* @Description:这里是一个单线程的轮询处理
* @author robert
* @date 2017-7-6
*
*/
@Component
public class ChargingQueueCosumer implements Runnable {
private static Logger logger = LoggerFactory.getLogger(ChargingQueueCosumer.class);
public static final int DEFAULT_BATCH_SIZE = 64;
@Autowired
private ChargingQueue chargingQueue;
@Autowired
private ChargingLogService chargingLogService;
private int batchSize = DEFAULT_BATCH_SIZE;
private boolean active = true;
private Thread thread;
//在servlet初始加载就开启轮询线程
@PostConstruct
public void init() {
thread = new Thread(this);
thread.start();
}
@PreDestroy
public void close() {
active = false;
}
@Override
public void run() {
Thread.currentThread().setUncaughtExceptionHandler(new ChargingExecuteFailedExceptionHandler());
while (active) {
execute();
}
}
public <T> void execute() {
List<T> chargingDtos = new ArrayList<T>();
try {
int size = 0;
//这里最多一次处理64条记录,多余的下次轮询处理
while (size < batchSize) {
T t = (T) chargingQueue.poll();
if (t == null) {
break;
}
chargingDtos.add(t);
size++;
}
} catch (Exception ex) {
logger.info(ex.getMessage(), ex);
}
if (!chargingDtos.isEmpty()) {
try {
chargingLogService.batchChargingLog(chargingDtos);
} catch (Exception e) {
logger.error(e.getMessage());
// 执行计费记录插入失败,暂且先将该队列对象再放进去
for (T failedT : chargingDtos) {
chargingQueue.add(failedT);
}
}
}
}
}ChargingLogService:
@Service
public class ChargingLogService<T> {
@Autowired
private ApiUpChargingMapper apiUpChargingMapper;
@Autowired
private ApiDownChargingMapper apiDownChargingMapper;
@Transactional
public void batchChargingLog(List<T> list) {
List<ApiUpCharging> apiUpChargingList = new ArrayList<ApiUpCharging>();
List<ApiDownCharging> apiDownChargingList = new ArrayList<ApiDownCharging>();
for (T t : list) {
if (t instanceof ApiUpCharging) {
apiUpChargingList.add((ApiUpCharging) t);
} else if (t instanceof ApiDownCharging) {
apiDownChargingList.add((ApiDownCharging) t);
}
}
if (!apiDownChargingList.isEmpty()) {
apiDownChargingMapper.insertBatchList(apiDownChargingList);
}
if (!apiUpChargingList.isEmpty()) {
apiUpChargingMapper.insertBatchList(apiUpChargingList);
}
}
}上下游计费注解定义:
/**
*
* @ClassName: DownStreamChargingLog
* @Description: 自定义注解 拦截下游计费记录
* @author robert
* @date 2017-7-6
*
*/
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DownStreamChargingLog {
}/**
*
* @ClassName: UpStreamChargingLog
* @Description: 自定义注解 拦截上游计费记录
* @author robert
* @date 2017-7-6
*
*/
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UpStreamChargingLog {
String apiInfo() default "";
}线程run方法抛出UncaughtException处理类
/**
* 线程run方法执行异常处理
*
* @author robert
*
*/
public class ChargingExecuteFailedExceptionHandler implements UncaughtExceptionHandler {
private static Logger logger = LoggerFactory.getLogger(ChargingExecuteFailedExceptionHandler.class);
@Override
public void uncaughtException(Thread t, Throwable e) {
// TODO这里暂且先记录输出日志
logger.info("系统执行计费日志记录出现了UncaughtException!");
}
}
本文介绍了如何利用Spring AOP进行方法拦截,并结合队列和多线程实现异步处理。通过创建切点、定义队列消费者和服务类,以及处理未捕获异常,来实现日志记录和计费功能。
253

被折叠的 条评论
为什么被折叠?



