AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ通过在源代码中插入横切关注点(cross-cutting concern),实现了在不侵入代码结构的情况下对系统进行横向切分的能力。它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
文章目录
一、AspectJ特性和优势
- 成熟稳定:AspectJ自2001年发展至今,已经是一个非常成熟和稳定的框架。通常在使用时,不需要过多担心插入的字节码正确性相关的问题。
- 灵活性:AspectJ允许在多个位置插入自定义的代码,如方法调用的位置、方法体内部、读写变量的位置、静态代码块内部以及异常处理位置的前后。它还可以直接将原位置的代码替换为自定义的代码。
- 模块化:切面(Aspect)是AspectJ中的一个关键概念,它是一个模块化的单元,封装了横切关注点的逻辑和行为。这使得代码更加模块化,提高了代码的可维护性和可重用性。
- 引入新接口和实现:AspectJ的引入(Introduction)功能允许开发者为现有的类添加新的接口和实现,从而扩展类的功能。
然而,AspectJ也存在一些潜在的问题,例如性能问题。AspectJ在实现AOP时,会包装一些特定的类,并不会直接将Trace函数插入到代码中,而是经过一系列的封装。这可能导致生成的字节码较大,对原函数的性能产生一定的影响。特别是在对应用中的所有函数都进行插桩时,性能影响可能会更加显著。
AspectJ的两种实现方式:
- 使用XML的配置文件:配置全局事务。
- 使用注解:我们在项目中要做AOP功能,一般都使用注解, AspectJ有5个注解。
1、切面、切入点和通知
在AspectJ中,切面、切入点和通知是核心概念,它们共同构成了面向切面编程(AOP)的基础。
- 切面(Aspect):切面是从不同的角度来看待同一个事物。在AspectJ中,切面是一个模块化的单元,它封装了与
横切关注点
相关的行为。切面负责定义切点(即哪些连接点会被拦截)和通知(即拦截到连接点后执行什么逻辑)。
通过切面,开发者可以将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,使得代码更加模块化、可维护。 - 切点(PointCut):
切点用于指定哪些连接点(JoinPoint)应该被切面所影响。
连接点是程序中可能被拦截的点,例如方法调用、异常抛出等。切点表达式用于匹配这些连接点,从而确定哪些方法或操作应该被切面的通知所影响。 - 通知(Advice):
通知是切面的具体逻辑实现,由切面负责执行。
通知定义了当与切点匹配的连接点被拦截时应该执行的操作。AspectJ定义了多种通知类型,如前置通知(在方法执行前执行)、后置通知(在方法正常返回后执行)、异常通知(在方法抛出异常时执行)等。通知使得开发者能够在不修改业务逻辑代码的情况下,向业务逻辑中添加额外的行为。
在AspectJ框架中使用注解表示的。也可以使用xml配置文件中的标签。
2、AspectJ中常用的注解
- @Aspect:这个注解用于标识一个Java类为切面类,这样AspectJ框架就能识别并处理这个类中的通知、切点等定义。
- @Pointcut:用于定义一个切点,即指定哪些连接点(JoinPoint)应该被拦截。切点表达式描述了哪些方法或操作应该被切面的通知所影响。
- @Before:前置通知注解,表示在目标方法执行之前执行特定的通知逻辑。
- @After:后置通知注解,表示在目标方法执行之后(无论方法执行是否成功)执行特定的通知逻辑。
- @AfterReturning:返回通知注解,表示在目标方法正常执行完毕后(即方法没有抛出异常)执行特定的通知逻辑。
- @AfterThrowing:异常通知注解,表示在目标方法抛出异常时执行特定的通知逻辑。
- @Around:环绕通知注解,它是最强大的通知类型,允许在目标方法执行前后插入自定义的逻辑,甚至可以控制目标方法的执行(例如,阻止其执行或多次执行)。
- @Order:这个注解用于定义切面的优先级,当多个切面都匹配同一个连接点时,可以根据优先级来决定哪个切面的通知先执行。
这些注解在AspectJ中扮演着重要的角色,它们使得开发者能够以一种声明式的方式定义切面的行为,从而简化横切关注点的处理。通过使用这些注解,开发者可以将日志记录、事务管理、权限控制等横切关注点从业务逻辑中分离出来,提高代码的可维护性和可重用性。
2.1、@Before :前置通知注解
属性:value,是切入点表达式,表示切面的功能执行的位置。
位置:方法的上面。
特点:在目标方法之前先执行的,不会改变目标方法执行的结果,不会影响目标方法的执行。
2.2、@AfterReturning :后置通知注解
后置通知定义方法,方法是实现切面功能的
方法的定义要求:
- 公共方法public
- 方法没有返回值
- 方法自定义名称
- 方法有参数,推荐Object
属性:
- value,是切入点表达式,表示切面的功能执行的位置。
- returning,自定义的变量,表示目标方法的返回值的,自定义变量名必须和通知方法的形参名一样。
位置:方法定义的上面
特点:
- 在目标方法之后执行的,能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能。
- 可以修改这个返回值。
- 后置通知的执行类似参数传递:传值、传引用。
2.3、@Around :环绕通知注解
环绕通知经常做事务,在目标方法之前开启事务,执行目标方法,在目标方法之后提交事务。
环绕通知方法的定义格式:
- public
- 必须有一个返回值
- 方法名称自定义
- 方法有参数,固定的参数ProceedingJoinPoint
属性:value,是切入点表达式。
位置:在方法的定义上面
特点:
- 他是功能最强的通知,在目标方法的前和后都能增强功能。
- 控制目标方法是否被调用执行。
- 修改原来的目标方法的执行结果,影响最后的调用结果。
- 环绕通知,等同于JDK动态代理,
InvocationHandler
接口,ProceedingJoinPoint
就等同于Method
。
2.4、@AfterThrowing :异常通知注解
方法的定义要求:
- 公共方法public
- 方法没有返回值
- 方法自定义名称
- 方法有一个exception,如果还有就是JoinPoint
属性:
- value,切入点表达式
- throwing自定义的变量,表示目标方法抛出的异常对象
- 变量名必须和方法的参数名一样。
特点:
- 在目标方法抛出异常时执行的
- 可以做异常的监控程序,监控目标方法执行时是不是有异常,如果有异常,可以发送邮件,短信进行通知。
异常通知类似于try…catch
try{
SomeServiceImpl.doSecond(..)
}catch{
myAfterThrowing(e)
}
2.5、@After 最终通知
一般是做资源清除工作的。
方法的定义要求:
- 公共方法public
- 方法没有返回值
- 方法自定义名称
- 方法没有参数,如果有就是JoinPoint。
属性:value,切入点表达式。
位置:在方法的上面。
特点:总是会执行,在目标方法之后执行的。
try{
SomeServiceImpl.doThird(..)
}catch(Exception e){
}finally{
myAfter()
}
2.6、@Pointcut:定义和管理切入点
如果你的项目中有多个切入点表达式是重复的,可以复用的,可以使用@Pointcut。
属性:value切入点表达式。
位置:在自定义的方法上面。
特点:当使用@Pointcut定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名。其它的通知中,vaLue属性就可以使用这个方法名称,代替切入点表达式了。
3、切入点表达式
切入点表达式表示切面执行的位置。
execution(public * *(..))
- 指定切入点为:任意公共方法。
execution(* set*(..))
- 指定切入点为:任何一个以
set
开始的方法。 execution(* com.xyz.service.*.*(..))
- 指定切入点为:定义在 service 包里的任意类的任意方法。
execution(* com.xyz.service..*.*(..))
- 指定切入点为:定义在 service 包或者子包里的任意类的任意方法。
..
出现在类名中时,后面必须跟*
,表示包、子包下的所有类。 execution(* *..service.*.*(..))
- 指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
4、使用AspectJ框架实现AOP
使用AOP:目的是给已经存在的一些类和方法,增加额外的功能。前提是不改变原来的类的代码。
使用AspectJ实现AOP的基本步骤:
1、新建Maven项目。
2、加入依赖:Spring依赖,AspectJ依赖,JUnit单元测试。
3、创建目标类:接口和他的实现类。要做的是给类中的方法增加功能。
4、创建切面类:普通类,在类的上面加入@Aspect注解。在类中定义方法,方法就是切面要执行的功能代码在方法的上面加入AspectJ中的通知注解。
- 定义方法,方法是实现切面功能的。
- 方法的定义要求:公共方法public、方法没有返回值、方法自定义名称、方法可以有参数,也可以没有参数。
- 如果有参数,参数不是自定义的,@Before有需要指定切入点表达式
execution()
。
5、创建Spring的配置文件:声明对象,把对象交给容器统一管理声明对象。你可以使用注解或者xml配置文件 <bean>
。
- 声明目标对象。
- 声明切面类对象。
- 声明AspectJ框架中的自动代理生成器标签。
自动代理生成器:用来完成代理对象的自动创建功能的。
6、创建测试类,从Spring容器中获取目标对象(实际就是代理对象)。通过代理执行方法,实现AOP的功能增强。
三,使用AspectJ实现AOP功能示例
使用AspectJ实现AOP功能,你需要首先确保你的项目中包含了AspectJ的相关依赖。你可以定义一个切面(Aspect),并在其中声明连接点(Join Point)和通知(Advice)。
以下示例展示了如何使用AspectJ在方法执行前后打印日志:
1、定义目标接口和实现类
首先,我们定义一个简单的接口和一个实现该接口的类。
// MyService.java
public interface MyService {
void doSomething();
}
// MyServiceImpl.java
public class MyServiceImpl implements MyService {
@Override
public void doSomething() {
System.out.println("Doing something...");
}
}
2、定义切面
接下来,我们定义一个切面,该切面会在MyService接口的doSomething方法执行前后打印日志。
// LoggingAspect.java
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
@Aspect
public class LoggingAspect {
@Before("execution(* com.example.MyService.doSomething(..))")
public void beforeDoSomething(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature());
}
@After("execution(* com.example.MyService.doSomething(..))")
public void afterDoSomething(JoinPoint joinPoint) {
System.out.println("After method: " + joinPoint.getSignature());
}
}
在上面的代码中,我们使用了 @Aspect
注解来声明这是一个切面。@Before
和 @After
注解分别定义了前置通知和后置通知,它们会在 MyService
接口的 doSomething
方法执行前后被调用。execution
表达式定义了连接点,即哪些方法会被这个切面所影响。
示例是基于AspectJ的注解风格。AspectJ还提供了XML配置风格和原生AspectJ语法,你可以根据自己的喜好和项目的需求来选择使用哪种方式。如果你使用的是Spring Boot,Spring Boot已经内置了对AspectJ的支持,你可以很容易地通过添加依赖和配置来使用AspectJ。