AspectJ基本用法

本文详细介绍了AspectJ的基本使用方法,包括JoinPoints的概念及其类型、Pointcuts的定义与使用、Advice的种类及应用实例,并展示了如何通过自定义Pointcuts实现更精确的代码切入。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考:深入理解Android之AOP

AOP虽然是方法论,但就好像OOP中的Java一样,一些先行者也开发了一套语言来支持AOP。目前用得比较火的就是AspectJ了,它是一种几乎和Java完全一样的语言,而且完全兼容Java(AspectJ应该就是一种扩展Java,但它不是像Groovy[1]那样的拓展。)。当然,除了使用AspectJ特殊的语言外,AspectJ还支持原生的Java,只要加上对应的AspectJ注解就好。所以,使用AspectJ有两种方法:

  • 完全使用AspectJ的语言。这语言一点也不难,和Java几乎一样,也能在AspectJ中调用Java的任何类库。AspectJ只是多了一些关键词罢了。
  • 或者使用纯Java语言开发,然后使用AspectJ注解,简称@AspectJ

AspectJ的配置可以参考另一篇文章 Android中使用AspectJ

Join Points介绍

Join Points是AspectJ中的一个关键概念。Join Points可以看作是程序运行时的一个执行点,比如:一个函数的调用可以看作是个Join Points,如Log.e()这个函数,e()可以看作是个Join Points,而调运e()的函数也可以认为是一个Join Points;设置一个变量,或者读取一个变量也可以是个Join Points;for循环也可以看作是Join Points。

理论上说,一个程序中很多地方都可以被看做是Join Points,但是AspectJ中,只有下面所示的几种执行点被认为是Join Points:

Join Points说明示例
method call函数调用比如调用Log.e(),这是一处JPoint
method execution函数执行比如Log.e()的执行内部,是一处Join Points。注意它和method call的区别。method call是调用某个函数的地方。而execution是某个函数执行的内部。
constructor call构造函数调用和method call类似
constructor execution构造函数执行和method execution类似
field get获取某个变量比如读取DemoActivity.debug成员
field set设置某个变量比如设置DemoActivity.debug成员
pre-initializationObject在构造函数中做得一些工作。
initializationObject在构造函数中做得工作
static initialization类初始化比如类的static{}
handler异常处理比如try catch(xxx)中,对应catch内的执行
advice execution这个是AspectJ的内容,稍后再说

这里列出了AspectJ所认可的JoinPoints的类型。实际上,也就是你想把新的代码插在程序的哪个地方,是插在构造方法中,还是插在某个方法调用前,或者是插在某个方法中,这个地方就是Join Points,当然,不是所有地方都能给你插的,只有能插的地方,才叫Join Points。

Pointcuts介绍

一个程序会有多个Join Points,即使同一个函数,也还分为call和execution类型的Join Points,但并不是所有的Join Points都是我们关心的,Pointcuts就是提供一种使得开发者能够选择自己需要的JoinPoints的方法。

Advice

Advice就是我们插入的代码以何种方式插入,有Before还有After、Around。

看个例子

@Before("execution(* android.app.Activity.on**(..))")
public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
}

这里会分成几个部分,我们依次来看:

  • @Before:Advice,也就是具体的插入点
  • execution:处理Join Point的类型,例如call、execution
  • (* android.app.Activity.on**(..)):这个是最重要的表达式,第一个*表示返回值,*表示返回值为任意类型,后面这个就是典型的包名路径,其中可以包含 * 来进行通配,几个 * 没区别。同时,这里可以通过&&、||、!来进行条件组合。()代表这个方法的参数,你可以指定类型,例如android.os.Bundle,或者(..)这样来代表任意类型、任意个数的参数。
  • public void onActivityMethodBefore:实际切入的代码。

Before和After其实还是很好理解的,也就是在Pointcuts之前和之后,插入代码,那么Around呢,从字面含义上来讲,也就是在方法前后各插入代码,是的,他包含了Before和After的全部功能,代码如下:

@Around("execution(* com.xys.aspectjxdemo.MainActivity.testAOP())")
public void onActivityMethodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    String key = proceedingJoinPoint.getSignature().toString();
    Log.d(TAG, "onActivityMethodAroundFirst: " + key);
    proceedingJoinPoint.proceed();
    Log.d(TAG, "onActivityMethodAroundSecond: " + key);
}

其中,proceedingJoinPoint.proceed()代表执行原始的方法,在这之前、之后,都可以进行各种逻辑处理。

自定义Pointcuts

自定义Pointcuts可以让我们更加精确的切入一个或多个指定的切入点。

首先我们要定义一个注解类

@Retention(RetentionPolicy.CLASS)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface DebugTrace {
}

在需要插入代码的地方加入这个注解。如在MainActivity中加入,

public class MainActivity extends AppCompatActivity {
    final String TAG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        logTest();
    }

    @DebugTrace
    public void logTest() {
        Log.e(TAG, "log test");
    }
}

最后,创建切入代码

@Pointcut("execution(@com.kun.aspectjtest.aspect.DebugTrace * *..*.*(..))")
public void DebugTraceMethod() {}

@Before("DebugTraceMethod()")
public void beforeDebugTraceMethod(JoinPoint joinPoint) throws Throwable {
    String key = joinPoint.getSignature().toString();
    Log.e(TAG, "beforeDebugTraceMethod: " + key);
}

log如下

这里写图片描述

在AspectJ的切入点表达式中,我们前面都是使用的execution,实际上,还有一种类型——call,那么这两种语法有什么区别呢,对于Call来说:

Call(Before)
Pointcut{
    Pointcut Method
}
Call(After)

对于Execution来说:

Pointcut{
  execution(Before)
    Pointcut Method
  execution(After)
}

withincode

这个语法通常来进行一些切入点条件的过滤,作更加精确的切入控制。如下

public class MainActivity extends AppCompatActivity {
    final String TAG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        aspectJ1();
        aspectJ2();
        aspectJ3();
    }

    public void aspectJTest() {
        Log.e(TAG, "execute aspectJTest");
    }

    public void aspectJ1(){
        aspectJTest();
    }

    public void aspectJ2(){
        aspectJTest();
    }

    public void aspectJ3(){
        aspectJTest();
    }
}

aspectJ1(),aspectJ2(),aspectJ3()都调用了aspectJTest方法,但只想在aspectJ2调用aspectJTest时插入代码,这个时候就需要使用到Pointcut和withincode组合的方式,来精确定位切入点。

@Pointcut("(call(* *..aspectJTest()))&&withincode(* *..aspectJ2())")
public void invokeAspectJTestInAspectJ2() {
}

@Before("invokeAspectJTestInAspectJ2()")
public void beforeInvokeaspectJTestInAspectJ2(JoinPoint joinPoint) throws Throwable {
    Log.e(TAG, "method:" + getMethodName(joinPoint).getName());
}

private MethodSignature getMethodName(JoinPoint joinPoint) {
    if (joinPoint == null) return null;
    return (MethodSignature) joinPoint.getSignature();
}

log如下

04-02 23:44:40.681 12107-12107/ E/MainActivity: execute aspectJTest
04-02 23:44:40.681 12107-12107/ E/AspectTest: method:aspectJTest
04-02 23:44:40.681 12107-12107/ E/MainActivity: execute aspectJTest
04-02 23:44:40.681 12107-12107/ E/MainActivity: execute aspectJTest

以上就是Aspecj的基本使用方法

<think>嗯,用户想了解AspectJ的使用方法。AspectJ是一个面向切面编程的框架,我需要先介绍它的基本概念,然后分步骤讲解使用方法。首先,可能需要提到AspectJ的安装和配置,然后是如何编写切面,定义切点和通知类型。还有,用户可能想知道如何与项目集成,比如在Maven或Gradle中的配置。另外,编译时织入和加载时织入的区别也很重要,应该分别说明。需要注意用户可能对AOP概念不熟悉,所以例子要简单明了。还要检查是否有遗漏的步骤,比如注解驱动和XML配置的区别,以及常见的应用场景,比如日志、事务管理等。确保步骤清晰,语法正确,用中文表达,避免技术术语过于复杂。</think>AspectJ 是一个面向切面编程(AOP)的扩展工具,可用于Java项目中实现横切关注点的模块化。以下是分步骤的使用方法说明: --- ### 一、环境配置 1. **安装AspectJ编译器** - 下载地址: https://www.eclipse.org/aspectj/downloads.php - 使用命令行安装: ```bash java -jar aspectj-x.x.x.jar ``` 2. **项目依赖** - Maven项目添加依赖: ```xml <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.7</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency> ``` --- ### 二、核心概念与语法 #### 1. **切面(Aspect)** - 用 `.aj` 文件或 `@Aspect` 注解定义: ```java @Aspect public class LoggingAspect { // 切点和通知在此定义 } ``` #### 2. **切点(Pointcut)** - 语法: `execution([修饰符] 返回类型 类路径.方法名(参数))` - 示例: ```java @Pointcut("execution(* com.example.service.*.*(..))") public void serviceMethods() {} ``` #### 3. **通知(Advice)** - **@Before**: 方法执行前触发 ```java @Before("serviceMethods()") public void beforeAdvice() { System.out.println("方法即将执行"); } ``` - **@After**: 方法执行后触发 - **@Around**: 包裹目标方法 ```java @Around("serviceMethods()") public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable { System.out.println("方法执行前"); Object result = pjp.proceed(); System.out.println("方法执行后"); return result; } ``` --- ### 三、编译与织入 1. **编译时织入(CTW)** - 使用AspectJ编译器 `ajc` 编译代码: ```bash ajc -source 1.8 -target 1.8 -cp aspectjrt.jar YourAspect.aj YourClass.java ``` 2. **加载时织入(LTW)** - 在 `META-INF/aop.xml` 中配置切面: ```xml <aspectj> <aspects> <aspect name="com.example.LoggingAspect"/> </aspects> </aspectj> ``` - JVM启动参数添加: ```bash -javaagent:path/to/aspectjweaver.jar ``` --- ### 四、示例场景 #### 记录方法执行时间 ```java @Aspect public class PerformanceAspect { @Around("execution(* com.example.service.*.*(..))") public Object logTime(ProceedingJoinPoint pjp) throws Throwable { long start = System.currentTimeMillis(); Object result = pjp.proceed(); long duration = System.currentTimeMillis() - start; System.out.println(pjp.getSignature() + " 耗时 " + duration + "ms"); return result; } } ``` --- ### 五、常见问题 1. **织入失败检查** - 确认依赖版本一致 - 检查切点表达式是否匹配目标方法 2. **与Spring集成** - 启用 `@EnableAspectJAutoProxy` - 使用 `<aop:aspectj-autoproxy/>` 配置 --- 通过以上步骤,可以快速在Java项目中应用AspectJ实现日志、事务、性能监控等横切逻辑的统一管理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值