本文不涉及AOP原理,只是一些简单而完整的AOP程序的实例,包括如下几种环境:
- AspectJ
- Spring
- Spring Boot
开发环境
- 操作系统:Ubuntu 20.04
- 开发工具:IntelliJ IDEA 2020.1.2 (Community Edition)
- JDK:
➜ ~ java -version
openjdk version "14.0.1" 2020-04-14
OpenJDK Runtime Environment (build 14.0.1+7-Ubuntu-1ubuntu1)
OpenJDK 64-Bit Server VM (build 14.0.1+7-Ubuntu-1ubuntu1, mixed mode, sharing)
AspectJ的AOP支持
下载安装AspectJ
登录 http://www.eclipse.org/aspectj/ ,下载AspectJ,本例中下载的是 aspectj-1.9.6.jar
。
打开命令行,运行如下命令:
java -jar aspectj-1.9.6.jar
会显示一个安装对话框,按提示一步步往下走,直到最后安装AspectJ。
注:在对话框最后一步,会推荐为系统添加 CLASSPATH
(指向 aspectjrt.jar
)和 PATH
(指向Aspect的 bin
目录),本例中添加了 PATH
,但是没添加 CLASSPATH
,所以要手工指定CLASSPATH。
使用AspectJ
先写一个最简单的Java程序 packageService/Test0816.java
:
package packageService;
public class Test0816
{
public void doSth()
{
System.out.println("Test0816::doSth()");
}
}
再写一个主程序来调用 Test0816
的 doSth()
方法 packageMain/AspectJTest.java
:
package packageMain;
import packageService.Test0816;
public class AspectJTest
{
public static void main(String[] args)
{
var test0816 = new Test0816();
test0816.doSth();
}
}
用 javac
命令编译:
javac packageMain/AspectJTest.java
会生成 AspectJTest.class
和 Test0816.class
两个文件。
运行程序:
➜ temp0816 java packageMain.AspectJTest
Test0816::doSth()
可见方法被成功调用。
以上是一个入门级的Java “Hello World”程序,现在我们来添加AspectJ的AOP支持。
添加文件 packageAspect/MyAspect.java
:
package packageAspect;
public aspect MyAspect
{
before(): execution(* packageService.*.*(..))
{
System.out.println("Hello Aspect");
}
}
注意这不是一个Java类( aspect
不是Java的关键字,而是AspectJ的关键字)。
接下来用 ajc
命令来编译上面的几个Java文件:
➜ temp0816 ajc -cp ~/aspectj1.9/lib/aspectjrt.jar packageService/*.java packageMain/*.java packageAspect/*.java
/home/ding/temp/temp0816/packageMain/AspectJTest.java:9 [error] var cannot be resolved to a type
var test0816 = new Test0816();
1 error
出错了, ajc
不识别 var
。可以把 var
改为 Test0816
,或者编译时加上 -10
或者 -11
选项( var
是Java 10引入的):
ajc -11 -cp ~/aspectj1.9/lib/aspectjrt.jar packageService/*.java packageMain/*.java packageAspect/*.java
可以看到每个Java文件都各自生成了class文件。
注意,要把 aspectjrt.jar
加到 -classpath
选项里。
接下来运行程序:
➜ temp0816 java -cp ~/aspectj1.9/lib/aspectjrt.jar packageMain.AspectJTest
Error: Could not find or load main class packageMain.AspectJTest
Caused by: java.lang.ClassNotFoundException: packageMain.AspectJTest
报错了,这是因为没有添加 .
到 -cp
选项里。
➜ temp0816 java -cp ~/aspectj1.9/lib/aspectjrt.jar:. packageMain.AspectJTest
Hello Aspect
Test0816::doSth()
可以看到AOP的效果:在调用 Test0816
的 doSth()
方法之前,打印出 Hello Aspect
。
注: .
(当前目录)在编译时( javac
命令)不需要添加到 -cp
选项里,但是运行时( java
命令)则需要添加到 -cp
选项里(如果指定了 -cp
选项)。
Spring的AOP支持
本例使用IntelliJ IDEA工具来开发。
在IntelliJ IDEA新建Java项目 project0817
。
在 src
同一级创建一个libs目录,把Spring所需的21个jar文件和AOP所需的2个jar文件,复制到libs目录下:
-
Spring的jar文件的来源为:在浏览器打开 https://repo.spring.io/ui/native/libs-release-local ,org -> springframework -> spring,找到最新版本,本例为5.2.9.RELEASE,然后下载
spring-5.2.9.RELEASE-dist.zip
,解压,即可得到21个jar文件。 -
AOP的jar文件的来源为:前面步骤中所安装的AspectJ路径下的lib目录(只需
aspectjrt.jar
和aspectjweaver.jar
2个jar文件)。
最后别忘了给项目设置lib。File -> Project Structure -> Libraries,点击 +
图标,添加 libs
目录:
在 src
目录下创建 packageService
package,并创建 Test0817
类:
package packageService;
import org.springframework.stereotype.Component;
@Component
public class Test0817 {
public void doSth() {
System.out.println("Test0817::doSth()");
}
}
在 src
目录下创建 packageAspect
package,并创建 MyAspect
类:
package packageAspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MyAspect {
@Before("execution(* packageService.*.*(..))")
public void Foo() {
System.out.println("Foo");
}
}
在 src
目录下创建 packageMain
package,并创建 TestAspect
类:
package packageMain;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import packageService.Test0817;
public class TestAspect {
public static void main(String[] args) {
var ctx = new ClassPathXmlApplicationContext("beans.xml");
var test0817 = ctx.getBean("test0817", Test0817.class);
test0817.doSth();
}
}
在 src
目录下创建 beans.xml
文件,内容如下:
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="packageService,
packageAspect">
<context:include-filter type="annotation"
expression="org.aspectj.lang.annotation.Aspect"/>
</context:component-scan>
<aop:aspectj-autoproxy/>
</beans>
运行主程序,结果如下:
Foo
Test0817::doSth()
可以看到AOP的效果:在调用 Test0817
的 doSth()
方法之前,打印出 Foo
。
注意:本例中,对于 MyAspect
类,只有 @Aspect
注解,没有 @Component
注解,而实际上 MyAspect
也是容器里的bean,可以用 ctx.getBean("myAspect", MyAspect.class)
将其取出来。但是,对于接下来的Spring Boot程序, MyAspect
类就必须加上 @Component
注解,否则就不起作用。需要研究一下原因。
Spring Boot的AOP支持
在IntelliJ IDEA新建 Spring Assistant
项目 project0818
,并在dependency中选择 Spring Web
。
在生成的pom.xml文件中,添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
创建package com.example.project0818.api
,并创建 MyController
类:
package com.example.project0818.api;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@RequestMapping("/hello")
public String hello() {
return "hello world!";
}
}
先测试一下这个API。启动程序,在命令行里运行如下命令:
➜ ~ curl localhost:8080/hello
hello world
没有问题,一切顺利。接下来添加AOP的逻辑。
创建package com.example.project0818.aspect
,并创建 MyAspect
类:
package com.example.project0818.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyAspect {
@Before("execution(* com.example.project0818.api.*.*(..))")
public void Foo() {
System.out.println("Foo");
}
}
注意:此处必须加上 @Component
,否则不起作用。
再次运行程序,这时AOP已经有效果了,每次运行上面的curl命令的时候,console上就会打印出 Foo
:
......
2021-08-17 23:04:29.466 INFO 6928 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-08-17 23:04:29.478 INFO 6928 --- [ main] c.e.project0818.Project0818Application : Started Project0818Application in 3.509 seconds (JVM running for 4.437)
2021-08-17 23:04:38.907 INFO 6928 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-08-17 23:04:38.907 INFO 6928 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2021-08-17 23:04:38.909 INFO 6928 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
Foo
项目结构如下图所示: