面向切面编程-AspectJ的使用
普通Java项目中,启用AspectJ
普通项目中启用AspectJ有点麻烦
安装AspectJ
java -jar aspectj-1.9.5.jar
使用安装程序一步一步安装,需要按照安装程序结尾的提示配置CLASSPATH和PATH环境变量。Maven项目中pom.xml配置依赖
<dependency> <groupId>aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.5.4</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency>
由于javac无法启用Aspect,所以还需要调用ajc命令编译一遍,但是为了方便可以加入一个maven插件,这样就可以直接运行了(IDEA中)
<build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.7</version> <configuration> <complianceLevel>1.8</complianceLevel> <source>1.8</source> <target>1.8</target> <showWeaveInfo>true</showWeaveInfo> <verbose>true</verbose> <Xlint>ignore</Xlint> <encoding>UTF-8</encoding> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>test-compile</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
Spring 项目中启用AspectJ
Spring项目中启用Aspect就比较简单了
pom.xml
中添加依赖<!-- Spring AOP 切面 模块 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.2</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.2</version> </dependency>
ApplicationConetxt.xml
中配置<!-- 配置开启@Aspect支持 --> <aop:aspectj-autoproxy proxy-target-class="true" />
编写Aspect类
常用的以下几个注解
@Aspect
: 这个需要在类上注解@Pointcut
:定义一个切面点,也可以省略,直接在其他阶段注解中增加pointcut
参数@Before
:前置执行,在方法执行前执行,可以用来做参数校验,修改,填充等@After
: 后置执行,在方法结束后执行@AfterReturning
: 返回时执行,如果方法抛出异常就不会执行@AfterThrowing
: 方法异常时执行,可以用来做日志记录,事务回滚等@Around
: 环绕执行,调用proceedingJoinPoint.proceed()
时执行切点方法
注:@Before和@After不可同时与@Around一起出现,否则会出现类似 Error:(51, 0) ajc: circular advice precedence: can't determine precedence between two or more pieces of advice that apply to the same join point: method-execution(void com.sundae.aspectj.dao.BaseDao.save())
这样的错误
例子
//DaoAspect.java
@Aspect
public class DaoAspect {
//定义一个切点
@Pointcut("execution(* com.sundae.aspectj.dao.*.*(..))")
public void point(){}
// 前置通知
// @Before("point()")
// public void before() {
// System.out.println("前置");
// }
// 后置通知 始终会执行
@After("point()")
public void after(JoinPoint joinPoint) {
System.out.println("后置");
}
// 环绕通知
@Around("point()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("around");
Object result = proceedingJoinPoint.proceed();
return result;
}
// 后置 发生异常时不会执行
@AfterReturning("point()")
public void returning() {
System.out.println("After returning 后置");
}
// 发生异常
@AfterThrowing(value = "point()", throwing = "ex")
public void throwing(Exception ex) {
System.out.println("发生异常了");
System.out.println(ex);
}
}
//BaseDao.java
public class BaseDao {
public void save() throws Exception {
System.out.println("BaseDao -> save()");
throw new Exception();
}
public void rollback(){
System.out.println("BaseDao -> rollback()");
}
public void commit() {
System.out.println("BaseDao -> commit()");
}
}
执行顺序
- 无异常:@Around(proceed()之前的部分) → @Before → 方法执行 → @Around(proceed()之后的部分) → @After → @AfterReturning
- 有异常:@Around(proceed(之前的部分)) → @Before → 扔异常ing → @After → @AfterThrowing
Pointcut字符串匹配规则匹配
execution(* com.sundae.aspectj.dao.*.*(..))
- 第一个星号表示返回的类型
- 第二个星号表示匹配dao包下所有的类
- 第三个信号表示匹配任意名字的方法
- 括号内的两个点,表示任意参数
如:
execution(* com.sundae.aspectj.dao.BaseDao.save(..))
表示匹配BaseDao类中的任意save
方法
execution(* com.sundae.aspectj.dao.BaseDao.save(String,int))
表示匹配BaseDao类中的任意返回值的save(String,int)
方法
execution(String com.sundae.aspectj.dao.BaseDao.save(..))
表示匹配BaseDao类中的任意返回值为String的save(..)
方法
扩展
- 可以在@Around中做很多事情,比如
@Around("point()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("around");
try{
BaseDao baseDao = (BaseDao) proceedingJoinPoint.getThis();
Object result = proceedingJoinPoint.proceed();
baseDao.commit(); //事务提交操作
return result;
}catch (Exception e){
//TODO 异常日志记录
//TODO 事务回滚操作
}
return null;
}
其中类似BaseDao baseDao = (BaseDao) proceedingJoinPoint.getThis();
这样直接获取切点实例的方法耦合度高,且降低了通用性,也许可以使用反射来提高通用性,降低耦合。
- 可以以注解作为切入点,可以实现一个注解直接实现某些功能。