现如今有许多个可用的 AOP 库,使用这些库需要能够回答以下问题:
是否与现有的或新的应用程序兼容?
在哪里可以使用 AOP ?
如何迅速与应用程序集成?
性能开销是多少?
在本文中,我们将回答这些问题并介绍 Spring AOP 和 AspectJ ——两个最受欢迎的 AOP 框架。
AOP 概念
在我们开始之前,让我们对术语和核心概念进行快速复习:
Aspect - 分散在应用程序中的多个位置的标准代码/功能,通常与实际的业务逻辑(例如事务管理)不同。 每个切面都侧重于具体的交叉切割功能。
Joinpoint – 它是在执行方法、构造函数调用或字段分配等过程时特定的点。
Advice – 在具体 Joinpoint 切面执行通知。
Pointcut –与 Joinpoint 匹配的正则表达式。 每次任何 Joinpoint 与 Pointcut 匹配时,将执行与该 Pointcut 相关联的指定的通知。
Weaving –把切面应用到目标对象来创建新的 advised 对象的过程。
Spring AOP 和 AspectJ
现在,我们通过一些数据来讨论 Spring AOP 和 AspectJ ,例如性能、目标、织入、内部结构、连接点和易用性。
目标
简单来说,Spring AOP 和 AspectJ 有不同的目标。
Spring AOP 旨在为 Spring IoC 提供一个简单的 AOP 实现,以解决程序员面临的最常见的问题。 它不是一个完整的 AOP 解决方案 - 它只能应用于由 Spring 容器管理的 Bean 。
另一方面, AspectJ 是原始的 AOP 技术,旨在提供完整的 AOP 解决方案。 它比 Spring AOP 更强大,但也更复杂。 还值得注意的是, AspectJ 可以跨所有域对象应用。
织入
AspectJ 和 Spring AOP 都使用了不同类型的会影响他们在性能和易用性方面行为的 Weaving。
AspectJ 使用三种不同类型的 Weaving :
Compile-time Weaving:AspectJ 编译器将我们切面的源代码和应用程序作为输入,生成已经织入的类文件作为输出。
Post-compile Weaving:这也称为二进制 Weaving。它与我们的切面用于编写现有的类文件和 JAR 文件。
Load-time Weaving:这与前面的二进制织入完全一样,区别在于类加载器将类文件加载到 JVM 时,Weaving 被推迟。
有关 AspectJ 本身的更多详细信息,请转到http://www.baeldung.com/rest-with-spring-course#certification-class。
AspectJ 使用编译时和类载入时 Weaving,Spring AOP 使用运行时 Weaving。
使用运行时织入,在使用目标对象的代理程序执行应用程序期间使用 JDK 动态代理或 CGLIB 代理(下文将讨论)进行织入:
———————————————
内部结构与应用
Spring AOP 是一个基于代理的 AOP 框架。 这意味着为了实现目标对象的切面,它将创建该对象的代理对象。 这是通过以下两种方法之一实现的:
1、JDK 动态代理 : Spring AOP 的首选方式。 只要目标对象实现了一个接口,那么将使用 JDK 动态代理。
2、CGLIB 代理 : 如果目标对象未实现接口,则可以使用 CGLIB 代理。
我们可以从官方文档中了解更多关于 Spring AOP 代理机制的信息。
另一方面,AspectJ 在运行时没有做任何事情,因为类是直接用切面编译的。
和 Spring AOP 不同,它不需要任何设计模式。 为了编写代码的切面,它将其编译器称为 AspectJ 编译器(ajc),通过它来编译我们的程序,然后通过提供一个小的(<100K)运行时库来运行它。
Joinpoints
从上面的内容来看,我们发现 Spring AOP 是基于代理模式的。 因此,它需要对目标 Java 类进行子类化,并相应地应用交叉问题。
但它有一个限制, 我们不能跨越 “final” 的类交叉关注(或方面),因为它们不能被覆盖,因此会导致运行时异常。
这同样适用于 Static 和 Final 方法。 Spring 切面无法应用它们,因为它们不能被覆盖。 因此 Spring AOP 因为这些限制,只支持方法执行连接点。
然而,AspectJ 在运行之前将交叉切合的问题直接编写到实际的代码中。与 Spring AOP 不同,它不需要对目标对象进行子类化,因此也支持许多其他连接点。 以下是支持的 Joinpoint 的摘要:
————————————————
值得注意的是,在 Spring AOP 中,切面不适用于在同一类中调用的方法。
这显然是因为当我们在同一个类中调用一个方法时,我们不会调用 Spring AOP 提供的代理方法。 如果我们需要这个功能,那么我们必须在不同的 bean 中定义一个单独的方法,或者使用 AspectJ。
易用性
Spring AOP 显然更简单,因为在我们的构建过程之间不会引入任何额外的编译器或者 weaver 。 它使用运行时织入,因此它与我们通常的构建过程无缝集成。 虽然看起来很简单,但它只适用于由 Spring 管理的 bean 。
然而,要使用 AspectJ ,我们需要引入 AspectJ 编译器(ajc)并重新打包所有的库(除非我们切换到后期编译或加载时织入)。
这当然比前者更复杂 - 因为它引入了 AspectJ Java 工具(其中包括一个编译器(ajc),一个调试器(ajdb),一个文档生成器(ajdoc),一个程序结构浏览器(ajbrowser)),我们 需要与我们的IDE或构建工具集成。
性能
就性能而言,编译时织入比运行时织入快得多。 Spring AOP 是一个基于代理的框架,因此在应用程序启动时创建代理。 此外,每个切面还有一些方法调用,这会对性能产生负面影响。
另一方面, AspectJ 在应用程序执行之前将这些切面编入主代码,因此与 Spring AOP 不同,没有额外的运行时开销。
由于这些原因,基准测试表明 AspectJ 比 Spring AOP 快8到35倍。
比较
本快速表总结了 Spring AOP 和 AspectJ 之间的主要区别:
————————————————
选择正确的框架
如果我们分析本节中提出的所有论据,我们将开始明白,一个框架并不比另一个框架更好。
简单地说,选择在很大程度上取决于我们的需求:
框架:如果应用程序不使用 Spring 框架,那么我们别无选择,只能放弃使用 Spring AOP 的想法,因为它无法管理任何超出 Spring 容器范围的东西。但是,如果我们的应用程序是完全使用 Spring 框架创建的,那么我们可以使用 Spring AOP 来直接学习和应用。
灵活性:由于连接点支持有限,Spring AOP 不是一个完整的 AOP 解决方案,而是解决了程序员面临的最常见的问题。所以如果我们想深入挖掘和利用 AOP 的最大功能,并希望得到广泛的可用连接点的支持,那么就选 AspectJ。
性能:如果我们使用有限的切面,那么就有微不足道的性能差异。但有时候,一个应用程序有几万个切面。在这种情况下,我们不想使用运行时织入,因此最好选择 AspectJ 。已知 AspectJ 比 Spring AOP 快8到35倍。
两者兼有:这两个框架都是完全兼容的。我们可以随时利用 Spring AOP ,并且仍然使用 AspectJ 获得前者不支持的连接点的支持。
结论
在本文中,我们分析了 Spring AOP 和 AspectJ 在几个关键领域。
我们比较了 AOP 的两种方法,灵活性以及它们如何轻松地适应我们的应用程序。
环绕通知 ProceedingJoinPoint 执行proceed方法的作用是让目标方法执行,这也是环绕通知和前置、后置通知方法的一个最大区别。
简单理解,环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的.
例子: 基于Aspect的性能日志切面
导包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<exclusions>
<exclusion>
<artifactId>log4j-api</artifactId>
<groupId>org.apache.logging.log4j</groupId>
</exclusion>
</exclusions>
</dependency>
spring-boot-starter-aop里面有aspectj包
切面自动配置
LogAspect.java
/*
* Copyright 2019 Wicrenet, Inc. All rights reserved.
*/
package com.xy.pay.main.aspect;
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.springframework.stereotype.Component;
import java.lang.reflect.Modifier;
/**
* 【$end$】
*
* @author YJX
* Created on 2019-05-23 20:03
*/
@Aspect
@Component
public class LogAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
/**
* 此处的切点是注解的方式,也可以用包名的方式达到相同的效果
* '@Pointcut("execution(com.xy.pay.main.service.*.impl.*.*(..))")'
*/
@Pointcut("@annotation(com.xy.pay.main.aspect.PerformanceLog)")
public void performanceLog() {
}
/**
* 环绕增强,相当于MethodInterceptor
*/
@Around("performanceLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
PerformanceLog performanceLog = signature.getMethod().getAnnotation(PerformanceLog.class);
Object res;
//开始时间
long time = System.currentTimeMillis();
try {
// 获取请求参数
// Object[] args = point.getArgs();
// 执行方法
res = joinPoint.proceed();
// 耗时(毫秒)
time = System.currentTimeMillis() - time;
System.out.println("目标方法名为:" + joinPoint.getSignature().getName());
// System.out.println("目标方法所属类的简单类名:" + joinPoint.getSignature().getDeclaringType().getSimpleName());
System.out.println("目标方法所属类的类名:" + joinPoint.getSignature().getDeclaringTypeName());
System.out.println("目标方法声明类型:" + Modifier.toString(joinPoint.getSignature().getModifiers()));
if (time > performanceLog.maxTime()) {
logger.error("{}:{}", performanceLog.title(), time);
}
} finally {
try {
//方法执行完成后增加日志
// addOperationLog(joinPoint,res,time);
} catch (Exception e) {
logger.error("切面日志失败{}", e);
}
}
return res;
}
// @Before("operationLog()")
// public void doBeforeAdvice(JoinPoint joinPoint){
// System.out.println("进入方法前执行.....");
//
// }
//
// /**
// * 处理完请求,返回内容
// * @param ret
// */
// @AfterReturning(returning = "ret", pointcut = "operationLog()")
// public void doAfterReturning(Object ret) {
// System.out.println("方法的返回值 : " + ret);
// }
//
// /**
// * 后置异常通知
// */
// @AfterThrowing("operationLog()")
// public void throwss(JoinPoint jp){
// System.out.println("方法异常时执行.....");
// }
//
//
// /**
// * 后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
// */
// @After("operationLog()")
// public void after(JoinPoint jp){
// System.out.println("方法最后执行.....");
// }
}
PerformanceLog.java
/*
* Copyright 2019 Wicrenet, Inc. All rights reserved.
*/
package com.xy.pay.main.aspect;
import java.lang.annotation.*;
/**
* 【 性能日志 】
*
* @author YJX
* Created on 2019-05-23 20:06
*/
@Documented
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PerformanceLog {
/** 标题 */
String title() default "";
/** 最长时间 */
long maxTime() default 1;
}
//修改入参处理例子
切面类
package com.xy.pay.api.controller.demo.aspect;
import com.xy.common.msg.Message;
import com.xy.common.msg.Msg;
import com.xy.common.util.SystemUtils;
import com.xy.common.util.UniqueKeyUtils;
import com.xy.pay.api.amqp.event.RequestEndEvent;
import com.xy.pay.api.amqp.event.RequestStartEvent;
import com.xy.pay.api.controller.core.ApiCommander;
import com.xy.pay.api.controller.trade.model.wap.ApiWapPayModel;
import com.xy.pay.core.structure.plugin.event.TradeEventPubEngine;
import com.xy.pay.dao.core.dict.JointProviderEnum;
import com.xy.pay.dao.risk.dict.TradeTraceErrorTypeEnum;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* WapPayDemoController#createWapOrder 切面
*
* @date 2019/1/23 16:00
*/
@Profile({"dev", "test"})
@Aspect
@Component
public class CreateWapOrderAspect {
@Autowired
private Environment environment;
@Autowired
private TradeEventPubEngine tradeEventPubEngine;
//@AfterReturning(value = "execution(* com.xy.pay.api.controller.trade.ApiTradeHandler.cancel(..)) && args(cancelModel,context)",
// argNames = "cancelModel,context,result", returning = "result")
//public void afterReturningAspect(ApiTradeCancelModel cancelModel, ApiHandlerContext context, Msg<TradeCancelResult> result) {
//
//}
@Around(value = "execution(* com.xy.pay.api.controller.demo.WapPayDemoController.createWapOrder(..)) && args(model,serial,request)", argNames = "invocation,model,serial,request")
public Object exec(ProceedingJoinPoint invocation, ApiWapPayModel model, String serial, HttpServletRequest request) throws Throwable {
String requestId = UniqueKeyUtils.uuid();
long begin = System.currentTimeMillis();
Object responseData = null;
if (model != null) {
model.setOrderNo(UniqueKeyUtils.uuid());
if (this.environment.acceptsProfiles("test")) {
model.setNotifyUrl("http://47.101.156.198:8881/payApi/test/demo/notice");
}
}
//请求开始发布
this.requestBegin(model, requestId);
try {
//参数验证
if (StringUtils.isBlank(serial)) {
responseData = Message.getError("设备号为空");
return responseData;
}
if (model == null) {
responseData = Message.getError("参数为空");
return responseData;
}
Msg msg = model.verify();
if (msg.hasError()) {
responseData = msg;
return responseData;
}
//事件处理发布准备
this.tradeEventPubEngine.pubPrepare(requestId);
responseData = invocation.proceed(new Object[]{model, serial, request});
return responseData;
} catch (Throwable throwable) {
responseData = Message.getError(throwable.getMessage());
return responseData;
} finally {
this.tradeEventPubEngine.pubComplete();
int consumeTime = (int) (System.currentTimeMillis() - begin);
this.requestEnd(model, requestId, consumeTime, responseData);
}
}
/**
* 请求开始
*/
private void requestBegin(ApiWapPayModel model, String requestId) {
if (model == null || StringUtils.isBlank(model.getOrderNo())) {
return;
}
RequestStartEvent startEvent = new RequestStartEvent(model.getOrderNo(), ApiCommander.trade_wapPay.getCmd());
startEvent.setHost(SystemUtils.getIP().orElse("127.0.0.1"));
switch (model.getProvider()) {
case ALI:
startEvent.setProvider(JointProviderEnum.ALI);
break;
default:
startEvent.setProvider(JointProviderEnum.WEI_XIN);
}
startEvent.setRequestId(requestId);
startEvent.setRequestParam(model);
this.tradeEventPubEngine.pubPrepare(requestId);
this.tradeEventPubEngine.pub(startEvent);
}
/**
* 请求结束
*/
private void requestEnd(ApiWapPayModel model, String requestId, int consumeTime, Object responseData) {
if (model == null || StringUtils.isBlank(model.getOrderNo())) {
return;
}
Msg msg = (Msg) responseData;
RequestEndEvent endEvent = new RequestEndEvent(model.getOrderNo(), ApiCommander.trade_wapPay.getCmd());
endEvent.setApplicationInfo(null);
endEvent.setConsumeTime(consumeTime);
if (responseData != null) {
if (msg.hasError()) {
endEvent.setErrorType(TradeTraceErrorTypeEnum.EXCEPTION);
}
}
endEvent.setHost(SystemUtils.getIP().orElse("127.0.0.1"));
switch (model.getProvider()) {
case ALI:
endEvent.setProvider(JointProviderEnum.ALI);
break;
default:
endEvent.setProvider(JointProviderEnum.WEI_XIN);
}
endEvent.setRequestId(requestId);
endEvent.setResponseData(responseData);
this.tradeEventPubEngine.pubPrepare(requestId);
this.tradeEventPubEngine.pub(endEvent);
}
}
业务类
package com.xy.pay.api.controller.demo;
import com.xy.common.msg.Message;
import com.xy.pay.api.controller.core.ApiCommander;
import com.xy.pay.api.controller.core.ApiHandlerContext;
import com.xy.pay.api.controller.trade.TradeWapPayHandler;
import com.xy.pay.api.controller.trade.model.wap.ApiWapPayModel;
import com.xy.pay.api.controller.trade.model.wap.ApiWeiXinWapPay;
import com.xy.pay.core.service.TerminalService;
import com.xy.pay.core.service.AppInfoService;
import com.xy.pay.dao.core.entity.joint.AppInfo;
import com.xy.pay.dao.core.entity.joint.Terminal;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.Optional;
/**
* 【手机网站案例】 只在测试与开发中使用
*
* @date 2019/1/18 10:55
*/
@Profile({"dev", "test"})
@RequestMapping("test/demo")
@Controller
public class WapPayDemoController {
private final static Logger LOGGER = LoggerFactory.getLogger(WapPayDemoController.class);
@Autowired
private TradeWapPayHandler tradeWapPayHandler;
@Autowired
private Environment environment;
@Autowired
private TerminalService terminalService;
@Autowired
private AppInfoService appInfoService;
/**
* 手机网站案例
*
* @return
*/
@GetMapping("wapPayDemoView")
public ModelAndView aliWapPayDemoView() {
ModelAndView mv = new ModelAndView("demo/wapPayDemo");
mv.addObject("env", environment.getActiveProfiles()[0]);
return mv;
}
/**
* 创建订单
*
* @return
*/
@ResponseBody
@PostMapping("createWapOrder")
public Object createWapOrder(ApiWapPayModel model, String serial, HttpServletRequest request) {
LOGGER.debug("创建订单:{}", model);
//根据设备号获取设备
Optional<Terminal> terminalOptional = this.terminalService.getBySerial(serial, 42L);
if (!terminalOptional.isPresent()) {
return Message.getError("设备号不存在");
}
Optional<AppInfo> appInfoOptional = this.appInfoService.getByTerminal(terminalOptional.get().getId(), model.getProvider());
if (!appInfoOptional.isPresent()) {
return Message.getError("应用号不存在");
}
ApiHandlerContext context = new ApiHandlerContext();
context.setAppId(6L);
context.setAppInfo(appInfoOptional.get());
context.setBrand(appInfoOptional.get().getBrand());
context.setCommander(ApiCommander.trade_wapPay);
context.setSerial(terminalOptional.get());
context.setStore(terminalOptional.get().getStore());
context.setProvider(appInfoOptional.get().getProvider());
appInfoOptional.get().setStore(terminalOptional.get().getStore());
appInfoOptional.get().setBrand(appInfoOptional.get().getBrand());
//微信必须参数,用户客户端ip
ApiWeiXinWapPay apiWeiXinWapPay = new ApiWeiXinWapPay();
apiWeiXinWapPay.setWapUrl(model.getWeiXinWapPay().getWapUrl());
apiWeiXinWapPay.setWapName(model.getWeiXinWapPay().getWapName());
apiWeiXinWapPay.setIpAddress(StringUtils.defaultString(model.getWeiXinWapPay().getIpAddress(), getIpAddress(request)));
model.setWeiXinWapPay(apiWeiXinWapPay);
return this.tradeWapPayHandler.execute(model, context);
}
private String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}