spring aop+queue+多线程轮询消费实现方法拦截后的异步处理

本文介绍了如何利用Spring AOP进行方法拦截,并结合队列和多线程实现异步处理。通过创建切点、定义队列消费者和服务类,以及处理未捕获异常,来实现日志记录和计费功能。

一、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!");
	}
}






评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值