面向对象编程(OOP)有一些弊端,当需要为多个不具备继承关系的对象引入一个公共的行为时,例如日志、安全监测等,我们需要在每个对象里添加公共的行为,这样程序中会有很多重复代码,不便于维护。这时候就需要面向切面编程( AOP)的支持。
最近在做 iot 的一个项目,将家里和小区的设备接入到云端的物联网项目。项目很大,已经服务化了,各个模块都抽成了独立的服务,服务之间通过HSF通讯。我主要负责 homeLink接口。拿到项目,发现项目中所有的异常都抛出去了,没有进行处理。难道别人调用的时候,需要catch异常?后来在调试一个接口的时候, 通过 debug 才发现:原来项目里使用aspect做切面,对异常进行统一处理.
code :
@Aspect
@Component
public class HsfAspect {
private static final Logger logger = xxx;
@Pointcut("execution(public * com.netease.iotx.service.hsf.provider..*.*(..))")
public void hsfLog() {}
@Around("hsfLog()")
public Object logFun(ProceedingJoinPoint joinPoint) {
Long startTime = System.currentTimeMillis();
Object result = null;
Throwable exception = null;
try {
result = joinPoint.proceed();
return result;
} catch (IotException e) {
result = new IotResult<>(e.getCode(), e.getMessage());
return result;
} catch (Throwable throwable) {
//error日志打印出堆栈,方便排查问题.
logger_error.error("UNEXPECTED ERROR: [INTERFACE : {}], [METHOD : {}], [ARGS : {}], EXCEPTION : ",
joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(),
joinPoint.getArgs(),
throwable);
exception = throwable;
return new IotResult<>(IotCodes.SERVER_ERROR);
} finally {
long timeSpend = System.currentTimeMillis() - startTime;
if (exception != null) {
logger_hsf.error("[INTERFACE : {}], [METHOD : {}], [ARGS : {}], [EXCEPTION : {}], [SPEND TIME : {}]",
joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(),
joinPoint.getArgs(),
exception.getMessage(),
timeSpend);
}
if (result != null) {
String resultdata = null;
if (result instanceof IoTxResult) {
Object data = ((IoTxResult)result).getData();
resultdata = (data != null) ? data.toString() : "";
}
logger.info("[INTERFACE : {}], [METHOD : {}], [ARGS : {}], [RESULT : {}], [RESULT_DATA : {}], [SPEND TIME : {}]",
joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(),
joinPoint.getArgs(),
result.toString(),
resultdata,
timeSpend);
}
}
}
}
Spring 使用的 AOP 需要在 xml 中配置<aop:aspectj-autoproxy/>
- @Aspect放在类头上,把这个类作为一个切面;
- @Pointcut放在方法头上,定义一个可被别的方法引用的切入点表达式;
- 使用通知;
要深入理解AOP, 需要理解以下相关术语:
1、目标对象(Target):包含连接点的对象。也被用来引用增强化或代理化对象。
2、代理(Proxy):AOP 框架创建的对象,包含增强。
3、连接点(Joinpoint):程序执行过程中明确的点,如方法的调用或特定的异常被抛出。指的是目标对象中的方法;
4、切点(Pointcut):指定一个通知将被引发的一系列连接点。AOP 框架必须允许开发者指定切入点:例如,使用正则表达式。切点用于选择连接点;
5、通知(Advice):通知也叫增强,许多AOP框架都是以拦截器做增强模型,维护一个“围绕”连接点的拦截器链。解决通知在何时调用,怎样调用的问题;
6、切面(Advisor):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事物管理是J2EE应用中横切关注点中一个很好的例子。切面一般是用 Advisor 或者 拦截器实现。切点和通知两个模块,切点解决了 where 问题,通知解决了 when 和 how 问题。切面把两者整合起来,就可以解决 对什么方法(where)在何时(when - 前置还是后置,或者环绕)执行什么样的横切逻辑(how)的三连发问题;
7、织入(Weaving):组装方面创建通知化对象。这可以在编译时完成(使用AspectJ编译器),也可以在运行时完成。Spring 通过实现后置处理器 BeanPostProcessor 接口,完成运行时织入。织入就是在切点的引导下,将通知逻辑插入到方法调用上,使得我们的通知逻辑在方法调用时得以执行;
AOP 通知类型包括:
1、Before Advice(前置增强):在一个连接点之前执行的增强,但这个增强不能阻止流程继续执行到连接点(除非它抛出一个异常)。
2、After Advice(后置增强,正常返回增强 ):在连接点正常完成后执行的增强,例如,如果一个正常返回,没有抛出异常。如果抛出异常则不会执行。
3、Around Advice(环绕增强):包围一个连接点的增强,如方法调用,是最强大的增强。在方法调用前后完成自定义的行为。它们负责选择继续执行连接点或直接返回它们自己的返回值或抛出异常来执行。
4、Throws Advice(抛出增强,异常返回增强):是最常用的增强类型。大部分是基于拦截器框架如Nanning或者JBoss4提供的Around增强。作用是,不管是否正常执行,都会返回增强中的内容。