Spring AspectJ

Spring 2.0 以后,Spring 新增了对 AspectJ 的支持。在新版本的 Spring 框架中,建议使用 AspectJ 方式开发 AOP。

AspectJ 是一个基于 Java 语言的 AOP 框架,它扩展了 Java 语言,提供了强大的 AOP 功能。

使用 AspectJ 需要导入以下 jar 包:

  • Aspectjrt.jar
  • Aspectjweaver.jar
  • Aspectj.jar

jar 包下载地址:https://www.eclipse.org/aspectj/downloads.php

使用 AspectJ 开发 AOP 通常有以下 2 种方式:

AspectJ包下载缓慢解决方法

打开 AspectJ 包下载页面,选择相应的版本,这里我们下载的为 1.9.5 稳定版本。

img

点击 aspectj-1.9.5.jar 进入下载页面,选择 Select another mirror,如下图。

img

根据自己所处地区选择下载,这里我们选择的是中国科学技术大学的下载地址。

img

下载完成后,解压该文件,需要导入的 jar 包在 files 文件夹的 lib 目录下。

AspectJ基于XML开发AOP

基于 XML 的声明式是指通过 Spring 配置文件的方式来定义切面、切入点及通知,而所有的切面和通知都必须定义在 aop:config 元素中。

在使用<aop:config>元素之前,我们需要先导入 Spring aop 命名空间,如下所示。

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
...
</beans>

定义切面<aop:aspect>

在 Spring 配置文件中,使用<aop:aspect>元素定义切面,该元素可以将定义好的 Bean 转换为切面 Bean,所以使用<aop:aspect>之前需要先定义一个普通的 Spring Bean。

1
2
3
4
5
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>

其中,id 用来定义该切面的唯一表示名称,ref 用于引用普通的 Spring Bean。

定义切入点<aop:pointcut>

<aop:pointcut> 用来定义一个切入点,当 <aop:pointcut> 元素作为 <aop:config> 元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;当 <aop:pointcut> 元素作为 <aop:aspect> 元素的子元素时,表示该切入点只对当前切面有效。

1
2
3
<aop:config>
<aop:pointcut id="myPointCut" expression="execution(* net.biancheng.service.*.*(..))"/>
</aop:config>

其中,id 用于指定切入点的唯一标识名称,execution 用于指定切入点关联的切入点表达式。

execution 格式为:

execution(modifiers-pattern returning-type-pattern declaring-type-pattern name-pattern(param-pattern)throws-pattern)

其中:

  • returning-type-pattern、name-pattern、param-pattern 是必须的,其它参数为可选项。
  • modifiers-pattern:指定修饰符,如 private、public。
  • returning-type-pattern:指定返回值类型,*表示可以为任何返回值。如果返回值为对象,则需指定全路径的类名。
  • declaring-type-pattern:指定方法的包名。
  • name-pattern:指定方法名,*代表所有,set* 代表以 set 开头的所有方法。
  • param-pattern:指定方法参数(声明的类型),(..)代表所有参数,(*)代表一个参数,(*,String)代表第一个参数可以为任何值,第二个为 String 类型的值。
  • throws-pattern:指定抛出的异常类型。

例如:execution(* net.biancheng.*.*(..)) 表示匹配 net.biancheng 包中任意类的任意方法。

定义通知

AspectJ 支持 5 种类型的 advice,如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
<aop:aspect id="myAspect" ref="aBean">
<!-- 前置通知 -->
<aop:before pointcut-ref="myPointCut" method="..."/>
<!-- 后置通知 -->
<aop:after-returning pointcut-ref="myPointCut" method="..."/>
<!-- 环绕通知 -->
<aop:around pointcut-ref="myPointCut" method="..."/>
<!-- 异常通知 -->
<aop:after-throwing pointcut-ref="myPointCut" method="..."/>
<!-- 最终通知 -->
<aop:after pointcut-ref="myPointCut" method="..."/>
....
</aop:aspect>

示例

下面使用 Eclipse IDE 演示 AspectJ 基于 XML 开发 AOP,步骤如下:

  1. 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  2. 导入 Spring 相关 JAR 包及 Aspectjrt.jar、Aspectjweaver.jar、Aspectj.jar。
  3. 在 net.biancheng 包下创建 Logging、Man、Beans.xml 和 MainApp。
  4. 运行 SpringDemo 项目。

Logging 类的代码如下,定义了在各个点要调用的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package net.biancheng;
public class Logging {
/** * 前置通知 */
public void beforeAdvice() {
System.out.println("前置通知");
}
/** * 后置通知 */
public void afterAdvice() {
System.out.println("后置通知");
}
/** * 返回后通知 */
public void afterReturningAdvice(Object retVal) {
System.out.println("返回值为:" + retVal.toString());
}
/** * 抛出异常通知 */
public void afterThrowingAdvice(IllegalArgumentException ex) {
System.out.println("这里的异常为:" + ex.toString());
}
}

Man 类的代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package net.biancheng;public class Man {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void throwException() {
System.out.println("抛出异常");
throw new IllegalArgumentException();
}
}

Beans.xml 代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<aop:config>
<aop:aspect id="log" ref="logging">
<aop:pointcut id="selectAll" expression="execution(* net.biancheng.*.*(..))" />
<aop:before pointcut-ref="selectAll" method="beforeAdvice" />
<aop:after pointcut-ref="selectAll" method="afterAdvice" />
<aop:after-returning pointcut-ref="selectAll" returning="retVal" method="afterReturningAdvice" />
<aop:after-throwing pointcut-ref="selectAll" throwing="ex" method="afterThrowingAdvice" />
</aop:aspect>
</aop:config>
<bean id="man" class="net.biancheng.Man">
<property name="name" value="bianchengbang" />
<property name="age" value="12" />
</bean>
<bean id="logging" class="net.biancheng.Logging" />
</beans>

MainApp 类代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
package net.biancheng;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Man man = (Man) context.getBean("man");
man.getName();
man.getAge();
man.throwException();
}
}

运行结果如下。

1
2
3
4
5
6
7
8
9
10
11
前置通知
后置通知
返回值为:bianchengbang
前置通知
后置通知
返回值为:12
前置通知
抛出异常
后置通知
这里的异常为:java.lang.IllegalArgumentException
Exception in thread "main" java.lang.IllegalArgumentException

AspectJ基于注解开发AOP

在 Spring 中,尽管使用 XML 配置文件可以实现 AOP 开发,但是如果所有的相关配置都集中在配置文件中,势必会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。

为此,AspectJ 框架为 AOP 开发提供了一套注解。AspectJ 允许使用注解定义切面、切入点和增强处理,Spring 框架可以根据这些注解生成 AOP 代理。

关于注解的介绍如表 1 所示。

名称 说明
@Aspect 用于定义一个切面。
@Pointcut 用于定义一个切入点。
@Before 用于定义前置通知,相当于 BeforeAdvice。
@AfterReturning 用于定义后置通知,相当于 AfterReturningAdvice。
@Around 用于定义环绕通知,相当于MethodInterceptor。
@AfterThrowing 用于定义抛出通知,相当于ThrowAdvice。
@After 用于定义最终final通知,不管是否异常,该通知都会执行。
@DeclareParents 用于定义引介通知,相当于IntroductionInterceptor(不要求掌握)。

启用 @AspectJ 注解有以下两种方法:

使用@Configuration和@EnableAspectJAutoProxy注解

1
2
3
4
5
@Configuration
@EnableAspectJAutoProxy
public class Appconfig {

}

基于XML配置

在 XML 文件中添加以下内容启用 @AspectJ。

<aop:aspectj-autoproxy>

定义切面@Aspect

AspectJ 类和其它普通的 Bean 一样,可以有方法和字段,不同的是 AspectJ 类需要使用 @Aspect 注解,如下所示。

1
2
3
4
5
6
package net.biancheng;
import org.aspectj.lang.annotation.Aspect;
@Aspectpublic
class AspectModule {

}

AspectJ 类也可以像其它 Bean 一样在 XML 中配置,如下。

1
2
3
<bean id = "myAspect" class = "net.biancheng.AspectModule">
...
</bean>

定义切入点@Pointcut

@Pointcut 注解用来定义一个切入点,如下。

1
2
3
4
5
// 要求:方法必须是private,返回值类型为void,名称自定义,没有参数
@Pointcut("execution(*net.biancheng..*.*(..))")
private void myPointCut() {

}

相当于以下代码

<aop:pointcut expression="execution(*net.biancheng..*.*(..))" id="myPointCut"/>

定义通知advice

@AspectJ 支持 5 种类型的 advice,以下为使用 @Before 的示例。

1
2
3
4
@Before("myPointCut()")
public void beforeAdvice(){
...
}

示例

下面使用 Eclipse IDE 演示 AspectJ 基于注解开发 AOP,步骤如下:

  1. 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  2. 导入 Spring 相关 JAR 包及 Aspectjrt.jar、Aspectjweaver.jar、Aspectj.jar。
  3. 在 net.biancheng 包下创建 Logging、Man、Beans.xml 和 MainApp。
  4. 运行 SpringDemo 项目。

Logging 类代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package net.biancheng;import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspectpublic
class Logging {
/** * 定义切入点 */
@Pointcut("execution(* net.biancheng.*.*(..))")
private void selectAll() {

}
/** * 前置通知 */
@Before("selectAll()")
public void beforeAdvice() {
System.out.println("前置通知");
}
/** * 后置通知 */
@After("selectAll()")
public void afterAdvice() {
System.out.println("后置通知");
}
/** * 返回后通知 */
@AfterReturning(pointcut = "selectAll()", returning = "retVal")
public void afterReturningAdvice(Object retVal) {
System.out.println("返回值为:" + retVal.toString());
}
/** * 抛出异常通知 */
@AfterThrowing(pointcut = "selectAll()", throwing = "ex")
public void afterThrowingAdvice(IllegalArgumentException ex) {
System.out.println("这里的异常为:" + ex.toString());
}
}

Man 类代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package net.biancheng;
public class Man {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void throwException() {
System.out.println("抛出异常");
throw new IllegalArgumentException();
}
}

Beans.xml 代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<aop:aspectj-autoproxy />
<bean id="man" class="net.biancheng.Man">
<property name="name" value="bianchengbang" />
<property name="age" value="12" />
</bean>
<bean id="logging" class="net.biancheng.Logging" />
</beans>

MainApp 类代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
package net.biancheng;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Man man = (Man) context.getBean("man");
man.getName();
man.getAge();
man.throwException();
}
}

运行结果如下。

1
2
3
4
5
6
7
8
9
10
前置通知
后置通知
返回值为:bianchengbang
前置通知
后置通知
返回值为:12
前置通知
抛出异常
后置通知
这里的异常为:java.lang.IllegalArgumentException

AspectJ和Spring AOP的区别?

相信作为Java开发者我们都很熟悉Spring这个框架,在spring框架中有一个主要的功能就是AOP,提到AOP就往往会想到AspectJ,下面我对AspectJ和Spring AOP作一个简单的比较:

  • Spring AOP

    • 基于动态代理来实现,默认如果使用接口的,用JDK提供的动态代理实现,如果是方法则使用CGLIB实现;

    • Spring AOP需要依赖IOC容器来管理,并且只能作用于Spring容器,使用纯Java代码实现;

    • 在性能上,由于Spring AOP是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得Spring AOP的性能不如AspectJ的那么好;

  • AspectJ

    • AspectJ来自于Eclipse基金会,AspectJ属于静态织入,通过修改代码来实现,有如下几个织入的时机:

    • 编译期织入(Compile-time weaving): 如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。

    • 编译后织入(Post-compile weaving): 也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。

    • 类加载后织入(Load-time weaving): 指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法。1、自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到 JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。2、在 JVM 启动的时候指定 AspectJ 提供的 agent:-javaagent:xxx/xxx/aspectjweaver.jar。

AspectJ可以做Spring AOP干不了的事情,它是AOP编程的完全解决方案,Spring AOP则致力于解决企业级开发中最普遍的AOP(方法织入)。而不是成为像AspectJ一样的AOP方案。

因为AspectJ在实际运行之前就完成了织入,所以说它生成的类是没有额外运行时开销的。

来源——C语言中文网
g/)**