3.5 应用案例:AspectJ开发
AspectJ(也就是AOP)的动机是发现那些使用传统的编程方法无法很好处理的问题。考虑一个要在某些应用中实施安全策略的问题。安全性是贯穿于系统所有模块间的问题,每个模块都需要应用安全机制才能保证整个系统的安全性,很明显这里的安全策略的实施问题就是一个横切关注点,使用传统的编程解决此问题非常的困难而且容易产生差错,这就正是AOP发挥作用的时候了。@AspectJ 使用了Java5 的注解,可以将切面声明为普通的Java类。 在 spring2.0以后,spring新增了对AspectJ 切点表达式的支持;Aspect1.5新增注解功能,通过 JDK5的注解技术,能直接在类中定义切面;新版本的 spring 框架,也都建议使用 AspectJ 来实现 AOP。所以说在 spring AOP 的核心包 Spring-aop-3.2.jar 里面也有对 AspectJ 的支持。
传统的面向对象编程中,每个单元就是一个类,而类似于安全性这方面的问题,它们通常不能集中在一个类中处理因为它们横跨多个类,这就导致了代码无法重用,可维护性差而且产生了大量代码冗余,这是我们不愿意看到的。
面向切面编程的出现正好给处于黑暗中的我们带来了光明,它针对于这些横切关注点进行处理,就好像面向对象编程处理一般的关注点一样。而作为AOP的具体实现之一的AspectJ,它向Java中加入了连接点(Join Point)这个新概念,其实它也只是现存的一个Java概念的名称而已。它向Java语言中加入少许新结构:切点(pointcut)、通知(Advice)、类型间声明(Inter-type declaration)和方面(Aspect)。切点和通知动态地影响程序流程,类型间声明则是静态的影响程序的类等级结构,而方面则是对所有这些新结构的封装。
一个连接点是程序流中指定的一点。切点收集特定的连接点集合和在这些点中的值。一个通知是当一个连接点到达时执行的代码,这些都是AspectJ的动态部分。其实连接点就好比是程序中的一条一条的语句,而切点就是特定一条语句处设置的一个断点,它收集了断点处程序栈的信息,而通知就是在这个断点前后想要加入的程序代码。AspectJ中也有许多不同种类的类型间声明,这就允许程序员修改程序的静态结构、名称、类的成员以及类之间的关系。AspectJ中的方面是横切关注点的模块单元。它们的行为与Java语言中的类很像,但是方面还封装了切点、通知以及类型间声明。
Aspect 通知类型,定义了类型名称以及方法格式。类型如下:
before:前置通知(应用:各种校验),在方法执行前执行,如果通知抛出异常,阻止方法运行
afterReturning:后置通知(应用:常规数据处理),方法正常返回后执行,如果方法中抛出异常,通知无法执行,必须在方法执行后才执行,所以可以获得方法的返回值。
around:环绕通知(应用:十分强大,可以做任何事情),方法执行前后分别执行,可以阻止方法的执行,必须手动执行目标方法。
afterThrowing:抛出异常通知(应用:包装异常信息),方法抛出异常后执行,如果方法没有抛出异常,无法执行。
after:最终通知(应用:清理现场),方法执行完毕后执行,无论方法中是否出现异常。
这里最重要的是around,环绕通知,它可以代替上面的任意通知。
以下是一个使用AspectJ框架进行开发的案例:
案例:AspectJ框架开发
本案例我们采用注解方式开发,程序结构如下:

其中UserDao.java代码如下:
package dao; public interface UserDao { public void addUser(); public void delUser(); } |
UserDaoImpl.java类实现了UserDao接口,并对该类用@Repository("userDao")进行注解,其代码如下:
package impl; import dao.UserDao; @Repository("userDao") public class UserDaoImpl implements UserDao { @Override public void addUser() { //int i=1/0; System.out.println("添加用户!"); } @Override public void delUser() { System.out.println("删除用户!"); } } |
MyAspect.java类是要增强的新功能,这些功能希望添加到原来的旧类里面,从而使用旧类里面的方法拥有更加强大的功能,在这个类里我们设计了多种增强方法并对这些方法进行了注解,我们使用了以下aop注解:
u @Aspect 声明切面,修饰切面类,从而获得 通知。
u @Before前置
u @AfterReturning后置
u @Around环绕
u @AfterThrowing抛出异常
u @After最终
u @PointCut 切入点,修饰方法 private void xxx(){} 之后通过“方法名”获得切入点引用
MyAspect.java文件代码如下:
package aspect; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** 切面类 */ @Aspect @Component public class MyAspect { //定义切入点所扫描的类,以发现这些类中要增强的方法。这里我们扫描包impl中的所有类 @Pointcut("execution(* impl.*.*(..))") // 使用一个无参无返回值的方法定义切入点,该方法代表要增强的类中的方法 private void myPointCut(){} //定义前置通知,该通知位于调用代码之前 @Before("myPointCut()") public void myBefore(JoinPoint joinPoint) { System.out.print("检查权限!"); } //后置通知 @AfterReturning(value="myPointCut()") public void myAfterReturning(JoinPoint joinPoint) { System.out.print("添加操作记录到数据库!" ); } //环绕通知 @Around("myPointCut()") public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕通知:检查权限!"); //调用被增强的目标类的方法 Object obj = proceedingJoinPoint.proceed(); System.out.println("环绕通知:添加操作记录到数据库!"); return obj; } //异常通知 @AfterThrowing(value="myPointCut()",throwing="e") public void myAfterThrowing(JoinPoint joinPoint, Throwable e) { System.out.println("异常通知,表示程序出错了!" + e.getMessage()); } //最终通知 @After("myPointCut()") public void myAfter() { System.out.println("这里的操作可以是关闭连接、关闭文件等收尾工作"); } } |
由于使用了注解,我们编辑配置文件如下:
<?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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <!-- 指定需要扫描的包,使注解生效 --> <context:component-scan base-package="aspect" /> <context:component-scan base-package="impl" /> <!-- 启动基于注解的声明式AspectJ支持 --> <aop:aspectj-autoproxy /> </beans> |
由于存在注解代码的类位于不同的包中,我们使用了两行代码来指定要扫描这两个包中的类。
<context:component-scan base-package="aspect" />
<context:component-scan base-package="impl" />
测试类代码如下:
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import dao.UserDao; public class TestAnnotationAspectj { public static void main(String args[]) { String xmlPath ="applicationContext.xml"; ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath); UserDao userDao = (UserDao) applicationContext.getBean("userDao"); userDao.addUser(); } } |
程序运行结果如下
环绕通知:检查权限! 检查权限! 添加用户 环绕通知:添加操作记录到数据库! 这里的操作可以是关闭连接、关闭文件等收尾工作 添加操作记录到数据库! |
如果我们把UserDaoImpl.java类中的代码int i=1/0;的注释去掉,则会触发异常通知,读者可以自行尝试。

