AOP(面向方面编程),也可称为面向切面编程,是一种编程范式,提供从另一个角度来考虑程序结构从而完善面向对象编程(OOP)。
在进行 OOP 开发时,都是基于对组件(比如类)进行开发,然后对组件进行组合,OOP 最大问题就是无法解耦组件进行开发,比如我们上边举例,而 AOP 就是为了克服这个问题而出现的,它来进行这种耦合的分离。AOP 为开发者提供一种进行横切关注点(比如日志关注点)分离并织入的机制,把横切关注点分离,然后通过某种技术织入到系统中,从而无耦合的完成了我们的功能。
1 AOP概述AOP(Aspect-Oriented Programming, 面向切面编程): 是一种新的方法论, 是对传统 OOP(Object-Oriented Programming, 面向对象编程) 的补充。AOP 的主要编程对象是切面(aspect), 而切面模块化横切关注点。在应用 AOP 编程时, 仍然需要定义公共功能, 但可以明确的定义这个功能在哪里, 以什么方式应用, 并且不必修改受影响的类. 这样一来横切关注点就被模块化到特殊的对象(切面)里。AOP更多概念。
AOP 的好处:
每个事物逻辑位于一个位置, 代码不分散, 便于维护和升级
业务模块更简洁, 只包含核心业务代码
2 Spring AOP在 Spring2.0 以上版本中,可以使用基于 AspectJ 注解或基于 XML 配置的 AOP,AspectJava是Java社区中最完整最流程的AOP框架。
2.1 在Spring中使用AspectJava注解支持要在 Spring 应用中使用 AspectJ 注解, 必须在 classpath 下包含 AspectJ 类库: aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar。然后将 aop Schema 添加到 <beans> 根元素中。在 Spring IOC 容器中启用 AspectJ 注解支持,只要在 Bean 配置文件中定义一个空的 XML 元素 <aop:aspectj-autoproxy>即可,当 Spring IOC 容器侦测到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素时, 会自动为与 AspectJ 切面匹配的 Bean 创建代理。
在AspectJ注解中,切面只是一个带有@Aspect注解的Java类,通知是标注有某种注解的简单Java方法,AspectJ支持以下5种类型的通知注解:
@Before: 前置通知, 在方法执行之前执行
@After: 后置通知, 在方法执行之后执行
@AfterRunning: 返回通知, 在方法返回结果之后执行
@AfterThrowing: 异常通知, 在方法抛出异常之后
@Around: 环绕通知, 围绕着方法执行
2.2 利用方法签名编写 AspectJ 切入点表达式最典型的切入点表达式时根据方法的签名来匹配各种方法:
execution(public void com.aop.HelloImpl.hi()):匹配HelloImpl中返回值为void且无参数的hi方法
execution(public void com.aop.HelloImpl.*()):匹配HelloImpl中返回值为void且无参数的所有public方法
execution(public void com.aop.HelloImpl.*(..)):匹配HelloImpl中返回值为void的所有public方法
execution(public * com.aop.HelloImpl.*(..)):匹配HelloImpl中所有public方法
在AspectJ中,切入点表达式可以使用操作符&&、||、!结合起来。
// 声明该方法为前置通知 @Before("execution(public void com.aop.HelloImpl.hi()) || execution(public void com.aop.HelloImpl.hihi(String))") public void beforeMethod(JoinPoint point) { String methodName = point.getSignature().getName(); System.out.println("HelloAspect beforeMethod : " + methodName); }
2.3 使用AOP程序示例,使用了全部的5种通知首先定义一个Hello接口,代码如下:
public interface Hello { public void hi(); }
然后定义一个Hello实现类HelloImpl,代码如下:
@Component public class HelloImpl implements Hello { @Override public void hi() { System.out.println("HelloImp hi()..."); } }
然后定义一个Hello的切面类HelloAspect,然后如下所示,注意:Hello接口、HelloImpl类、HelloAspect类都是在com.aop包下的。
@Aspect @Component public class HelloAspect { // 声明该方法为前置通知 @Before("execution(public void com.aop.HelloImpl.hi()) || execution(public void com.aop.HelloImpl.hihi(String))") public void beforeMethod(JoinPoint point) { String methodName = point.getSignature().getName(); System.out.println("HelloAspect beforeMethod : " + methodName); } // 声明该方法为后置通知,无论该方法是否发生异常 @After("execution(public void com.aop.HelloImpl.*())") public void endMethod(JoinPoint point) { String methodName = point.getSignature().getName(); System.out.println("HelloAspect afterMethod : " + methodName); } // 返回通知,正常返回时的通知,不包括异常,可以访问到方法的方绘制 @AfterReturning(value = "execution(public void com.aop.HelloImpl.*())", returning = "result") public void afterReturning(JoinPoint point, Object result) { String methodName = point.getSignature().getName(); System.out.println("HelloAspect afterReturning : " + methodName + ", result: " + result); } // 异常通知 @AfterThrowing(value = "execution(public void com.aop.HelloImpl.*())", throwing = "ex") public void afterThrowing(JoinPoint point) { String methodName = point.getSignature().getName(); System.out.println("HelloAspect afterReturning : " + methodName); } /** * 环绕通知需携带 ProceedingJoinPoint 类型的参数。 * 环绕通知类似于动态代理的全过程, ProceedingJoinPoint 类型的参数可以决定是否执行目标方法。 * 环绕通知必须有返回值,且返回值是目标方法的返回值 */ @Around("execution(public void com.aop.HelloImpl.*())") public Object aroundMethod(ProceedingJoinPoint point) { Object result = null; // 环绕通知(前通知) System.out.println("HelloAspect aroundMethod start..."); try { // 前置通知 result = point.proceed(); // 目标方法执行 } catch (Throwable throwable) { // 异常通知 throwable.printStackTrace(); } // 环绕通知(后通知) System.out.println("HelloAspect aroundMethod end..."); // 后置通知 // 返回通知 return result; } }
配置aopContext.xml文件,如下所示: