AspectJ 是一个 AOP 的具体实现框架。AOP(Aspect Oriented Programming)即面向切面编程,可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。编译时织入是 AspectJ 的一个重要功能,因为 AspectJ 有一个专门的编译器用来生成遵守 Java 字节编码规范的 Class 文件。
一、使用步骤
要完成代码通过 AspectJ 编译时织入,通常需要两步:
1、编写 aspect 文件
2、使用 ajc 编译器结合 aspect 文件对源代码进行编译。
二、准备工具
我们可使用两个工具来方便我们开发 AspectJ 程序:
1、Eclipse 插件AJDT,方便我们在 eclipse 环境下编写切面(AspectJ)并在编译源码时自动织入切面;【参考用法】
2、AspectJ compiler Maven Plugin,Maven 的 AspectJ 编译插件,同样可以在编写源码时将切面织入到字节码。
三、代码实现
下面我们来编写一个简单的例子
1、创建maven项目
利用eclipse创建maven项目,并修改pom文件,增加增加 aspectj 相关内容
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.aspectJ</groupId>
<artifactId>aspectJDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>aspectJDemo</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<compilerArguments>
<bootclasspath>${java.home}/lib/rt.jar</bootclasspath>
</compilerArguments>
</configuration>
</plugin>
<!-- AspectJ 编译插件 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.10</version>
<configuration>
<showWeaveInfo>true</showWeaveInfo>
<source>1.8</source>
<target>1.8</target>
<Xlint>ignore</Xlint>
<complianceLevel>1.8</complianceLevel>
<encoding>UTF-8</encoding>
<verbose>true</verbose>
<outxml>true</outxml>
<aspectLibraries>
<!-- 此处定义外部的aspect包,例如spring的事务aspect包 。这里引用的包必须在依赖中声明 -->
<aspectLibrary>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<!-- use this goal to weave all your main classes -->
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2、创建service接口
package com.aspectJ.aspectJDemo;
public interface SampleService {
int add(int x, int y);
String getPassword(String username);
}
3、创建接口实现类
package com.aspectJ.aspectJDemo;
public class SampleServiceImpl implements SampleService{
public int add(int x, int y) {
return x + y;
}
@AuthCheck
public String getPassword(String username) {
return "password";
}
}
4、定义切面
package com.aspectJ.aspectJDemo;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
public aspect MyAspect{
/**
* 切入点:SampleService继承树中所有 public 且以 add 开头的方法
*/
public pointcut serviceAddMethods(): execution(public * com.aspectJ.aspectJDemo.SampleService+.add*(..));
Object around(): serviceAddMethods(){
Object oldValue = proceed();
System.out.println("执行结果:" + oldValue);
return Integer.MIN_VALUE;
}
/**
* 切入点:SampleService继承树中所有标注了AuthCheck的方法。
*/
public pointcut serviceAuthCheckAnnotatedMethods(): execution(* com.aspectJ.aspectJDemo.SampleService+.*(..)) && @annotation(AuthCheck);
before(): serviceAuthCheckAnnotatedMethods(){
if(1==1){//权限检查代码
System.out.println("权限不足");
}
}
/**
* 切入点:SampleService继承树中所有 public 的方法。
*/
public pointcut serviceAllPublicMethods(): execution(public * com.aspectJ.aspectJDemo.SampleService+.*(..));
after(): serviceAllPublicMethods(){
MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
Method method = methodSignature.getMethod();
System.out.println("[LOG] 方法被调用了: " + method);
}
}
5、通过maven编译
mvn test-compile
6、使用junit测试
package com.aspectJ.aspectJDemo;
import junit.framework.TestCase;
public class AppTest extends TestCase {
public void testApp() {
SampleService service = new SampleServiceImpl();
service.add(2, 4);
System.out.println();
service.getPassword("xxx");
}
}
测试结果
add方法执行结果:6
[LOG] 方法被调用了: public int com.aspectJ.aspectJDemo.SampleServiceImpl.add(int,int)
执行get方法
[LOG] 方法被调用了: public java.lang.String com.aspectJ.aspectJDemo.SampleServiceImpl.getPassword(java.lang.String)
四、Aspectj和Filter、Interceptor的区别
1、Filter
过滤器是处于客户端与服务器资源文件之间的一道过滤网,在访问资源文件之前,通过一系列的过滤器对请求进行修改、判断等,把不符合规则的请求在中途拦截或修改。也可以对响应进行过滤,拦截或修改响应。过滤器一般用于登录权限验证、资源访问权限控制、敏感词汇过滤、字符编码转换等等操作,便于代码重用,不必每个servlet中还要进行相应的操作。但是一个过滤器实例只能在容器初始化时调用一次。
2、Interceptor
拦截器是在面向切面编程中应用的,就是在你的service或者一个方法前调用一个方法,或者在方法后调用一个方法。是基于JAVA的反射机制。Interceptor是spring框架自己带的拦截器,它可以拿到处理的Controller和拿到处理的方法 但是拿不到具体的请求参数。
3、Aspectj
切入要切入的类,当请求的时候回拦截下来,这样可以获取拦截的方法的参数。
三者区别主要是粒度的差异,应用场景的不同
Filter | 可以拿到原始的http请求,但是拿不到你请求的控制器和请求控制器中的方法的信息。 |
Interceptor | 可以拿到你请求的控制器和方法,却拿不到请求方法的参数。 |
Aspect | 可以拿到方法的参数,但是却拿不到http请求和响应的对象 |
五、Aspectj和spring Aop的区别
Spring AOP | AspectJ |
---|---|
在纯 Java 中实现 | 使用 Java 编程语言的扩展实现 |
不需要单独的编译过程 | 除非设置 LTW,否则需要 AspectJ 编译器 (ajc) |
只能使用运行时织入 | 运行时织入不可用。支持编译时、编译后和加载时织入 |
功能不强-仅支持方法级编织 | 更强大 - 可以编织字段、方法、构造函数、静态初始值设定项、最终类/方法等......。 |
只能在由 Spring 容器管理的 bean 上实现 | 可以在所有域对象上实现 |
仅支持方法执行切入点 | 支持所有切入点 |
代理是由目标对象创建的, 并且切面应用在这些代理上 | 在执行应用程序之前 (在运行时) 前, 各方面直接在代码中进行织入 |
比 AspectJ 慢多了 | 更好的性能 |
易于学习和应用 | 相对于 Spring AOP 来说更复杂 |
六、总结
优点
1、由于将切面直接编译进了字节码,所以运行时不再需要动态创建代理对象,节约了内存呢和 CPU 消耗;
2、通过AspectJ,方法被织入了切面后,方法上的 Annotation 还是有效的,因为对象类型没有变。而动态代理可能会以代理类替代原类型,也就失去了 Annotation。
缺点
1、编写aspect 文件有一定的难度;
2、编写过程稍显复杂(借助工具可简化:Eclipse AJDT, Maven AspectJ 插件等)。
七、附件
