什么是面向切面编程(AOP)
在软件开发中,散布于应用中多处的功能被称为横切关注点(比如日志、安全和事务管理)。通常来讲,这些横切关注点从概念上是与应用的业务逻辑相分离的(但是往往会直接嵌入到应用的业务逻辑之中)。把这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所要解决的问题。
简而言之,横切关注点可以被描述为影响应用多处的功能,AOP能帮助我们模块化横切关注点(切面)。在使用面向切面编程时,我们仍然在一个地方定义通用功能,但是可以通过声明的方式定义这个功能要以何种方式在何处应用,而无需修改受影响的业务逻辑类。
在公司项目中,我们常常需要使用AOP去实现一些功能需求,这里我为大家分享一下在实际项目下具体AOP该如何去使用。
(1)日志输出
AOP最常用的一种方式,日志管理,这里我们可以先定义一个日志切面类,apiLog(),annoationLog()为切点,before()能够在请求执行前打印请求日志。
@Aspect
@Order(1)
public class LogAspect {
private static Logger logger = LoggerFactory.getLogger(LogAspect.class);
@Pointcut("execution (public * com.dtyunxi.yundt.cube.center.trade.biz.apiimpl..*.*(..))")
public void apiLog() {
}
@Pointcut("@annotation(com.trade.center.voucher.svr.config.mock.Mock))")
public void annoationLog() {
}
@Before(value = "annoationLog() || apiLog()")
public void before(JoinPoint joinPoint){
System.out.println("方法执行前执行......before");
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
logger.info("<=====================================================");
logger.info("请求来源: =》" + request.getRemoteAddr());
logger.info("请求URL:" + request.getRequestURL().toString());
logger.info("请求方式:" + request.getMethod());
logger.info("响应方法:" + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
logger.info("请求参数:" + Arrays.toString(joinPoint.getArgs()));
logger.info("------------------------------------------------------");
}
}
(2)接口拦截
项目需求:由于系统由多方公司联合开发,其他公司的相关接口可能偶尔网络不通,或者处于调试阶段,导致我方开发人员由于外部接口错误从而无法相关模块的开发,这里就可以使用Aop+自定义注解去实现接口拦截,从而返回正确响应参数。首先,我们可以自定义一个注解,这里的jsonFilePath用来指定文件(该文件包含正确的返回信息)的存放位置
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Mock {
String jsonFilePath();
}
其次,我们可以使用AOP定义一个切面,jsonMockable属性可以设置是否开启拦截,这里默认
开启,有需要的话也可以关闭,pointCut()指定该切面的作用域,这里是用于注解,around()为处理接口拦截的具体业务
@Aspect
@Component
public class MockAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
@Value("${json.mock.enable:true}")
private Boolean jsonMockEnable;
@Pointcut("@annotation(com.trade.center.voucher.svr.config.mock.Mock)")
public void pointCut() {
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object result;
try {
MethodSignature methodSignature = getMethodSignature(joinPoint);
Method method = methodSignature.getMethod();
Mock mock = AnnotationUtils.findAnnotation(method, Mock.class);
Objects.requireNonNull(mock);
// String[] paramNames = methodSignature.getParameterNames();
Object[] paramValues = joinPoint.getArgs();
if (!jsonMockEnable) {
result = joinPoint.proceed(paramValues);
} else {
// 取json文件数据
ClassPathResource pathResource = new ClassPathResource(mock.jsonFilePath());
String jsonStr = IoUtil.read(pathResource.getStream(), StandardCharsets.UTF_8);
Class<?> returnType = methodSignature.getReturnType();
result = JSON.parseObject(jsonStr, returnType);
}
} catch (Throwable e) {
logger.error(e.getMessage());
throw e;
}
return result;
}
private MethodSignature getMethodSignature(ProceedingJoinPoint joinPoint) {
return (MethodSignature) joinPoint.getSignature();
}
}
在需要实现接口拦截的方法上添加@Mock注解并指定json路径,这样就会被我们定义的切面处理
@PostMapping("/fuxunpay/cash")
@Mock(jsonFilePath = "mock/getMemberBaseInfo.json")
public RestResponse<FxResponse<FuXunGiftCashResp>> cash(@RequestBody FuXunGiftCashReq req) {
return new RestResponse<>(fuXunPayService.cash(req));
}