比较 Spring AOP 和 AspectJ 日志切面处理,修改入参处理

现如今有许多个可用的 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;
    }

}

更多例子

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值