AOP(Aspect-Oriented Programing,面向切面编程)是OOP(Object-Oriented Programing,面向对象编程)的补充或者说是完善。AOP可以通过预编译和运行期动态代理的方式实现在不修改源代码的情况下动态地给程序统一添加功能的一种技术。AOP是Spring框架中一个重要的内容,主要意图是将日志记录、性能统计、安全控制、事务处理、异常处理等代码从业务逻辑代码中分离出来,实现解耦,以使我们修改这些动作行为的时候不影响业务逻辑的代码。AOP实际上是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说是这种目标的一种实现。
其他的不多说,具体的实例往往能够更能说明问题。先看一下文件结构:
注意到,前面的包都没有打开,因为他们都是在前面的Blog[Spring]Bean的自动装配Autowire中已经有了,这里和前面完全没有变化,为了减小篇幅,这里也就不在专注于它们了。 面向切面编程,首先需要引入相应的jars,在前面的jar是基础上在引入aspectjrt.jar,aspectjweaver.jar,因为我们要使用aop注解,所以还要引入common-annotations.jar。
前面说了,aop主要的意图是为了实现日志记录、事务控制等动作行为的代码与业务逻辑代码的分离,所以这里添加了interceptor包,其中有两个类LogInterceptor和TransactionInterceptor,分别实现日志记录和事务控制的功能。
我们这里在前面Autowire应用的基础上构建aop应用。先看看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-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!-- 我们在基于annotation的Spring应用的基础上构建aop应用 -->
<!-- 基于注解的annotation自动装载 -->
<context:component-scan base-package="beans" />
<context:annotation-config />
<!-- Enabling @AspectJ Support (面向AOP编程) -->
<aop:aspectj-autoproxy />
<!-- 加上下面的bean之后会添加双层通知 有时候可能会用到这种方式-->
<!-- <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" /> -->
</beans>
注意到有两点变化:
一、在<beans>元素属性中多了三个关于aop的项,一个xmlns和xsi:schemaLocation的两个xsd项。
二、多了一个标签<aop:aspectj-autoproxy />,有时候可能还会用到<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator">
OK,其作用已经注释的很明白了。下面看看两个拦截器的具体实现。
类LogInterceptor:
package beans.web.interceptor;
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.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component //将组建交个Spring来管理
@Aspect //Declare 切面
@Order(0) //指定切面的执行顺序,order值小的在外层(@Before先于别的Aspect的,@After后于别的Aspect的)
public class LogInterceptor {
/**
* 声明一个一般的切入点PointCut
* 方法名字可以随意
* 在其他地方通知可以直接使用这个切入点,实现切入点的重用
*/
@Pointcut("execution(* beans.service..*.*(..))")
public void doLog(){
}
@Before("doLog()") //通知使用已经声明的切入点
public void doLogBefore() {
System.out.println("method before...");
}
@After("doLog()") //通知使用已经声明的切入点
public void doLogAfter(JoinPoint joinPoint) { //通过JoinPoint类来获取连接点信息
System.out.println(joinPoint.getSignature().getName()+" method after..."); //获取连接点方法名
}
@AfterReturning("execution(* beans.service..*.save())") //定义切入点和通知
public void doLogAfterReturning() {
System.out.println("method afterReturning...");
}
@AfterThrowing("execution(* beans.service..save())")
public void doLogAfterThrowing() {
System.out.println("method afterThrowing...");
}
@Around("execution(* beans.service..save())")
public void doLogAround(ProceedingJoinPoint procJoinPoint) { //@Around通知需要借助ProceedingJoinPoint类
System.out.println("method around begin...");
try {
procJoinPoint.proceed(); //调用ProceedingJoinPoint类的proceed()方法,是连接点方法执行
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("method around end");
}
}
就依LogInterceptor类来说。首先构建aop应用,并不需要对一个类继承什么特殊的接口。为了能够使Spring容器管理类,它又不属于Controller、Service、Repository中的任意一种,所以直接用@Component注解;@Aspect注解用来声明一个切面,就好比oop以class作为其重要单元一样,aop处于同等地位的是aspect;我们说切面其实是一些列切入点Pointcut与通知Advice的集合,基于annotation的Advice类型有五种:@Before/@After/@AfterReturning/@AfterThrowing/@Around,在类LogInterceptor中,每个通知后面括号中的是aop的表达式,它用来声明一个切入点(切入点就是一系列连接点JoinPoint,也可以理解为方法的集合);为了能够重用切入点,我们还可以定义一个任意的方法,用@Pointcut为其声明切入点,然后在其他Advice处调用这个pointcut。 需要注意使用@Around通知类型时,涉及到了一个ProceedingJoinPoint类。
PS:要获取某个连接点的信息是,需要借助于JoinPoint类,如@After处,给动作添加了JoinPoint参数。
类TransactionInterceptor:package beans.web.interceptor;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component //既不是Controller、Service也不是Repository,所以用Component
@Aspect //定义切面
@Order(1) //指定切面的执行顺序,order值小的在外层,大的在内层
public class TransactionInterceptor {
@Before("execution(* beans.service..*.save())")
public void doTransactionBefore(){
System.out.println("transaction before....");
}
@After("execution(* beans.service..*.save())")
public void doTransactionAfter(){
System.out.println("transaction after....");
}
}
这个Demo中列举了两个Interceptor的原因是,当有多个日志处理,事务控制等需要从业务逻辑中分离的类的时候,会引起一个问题:谁会被先调用?@Order注解解决了这个问题,order值决定了切面的执行先后顺序:order值小的在更外层执行,意味着,@Order(0)的@Before会先于其他的@Before执行,而其@After会后于其他的@After执行。
AOP还有另一种,基于xml的方式实现,不过我还是觉得aop用annotation更为方便。这里主要看一下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-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!-- 我们在基于annotation的Spring应用的基础上构建aop应用 -->
<!-- 基于注解的annotation自动装载 -->
<context:component-scan base-package="beans" />
<context:annotation-config />
<!-- Enabling @AspectJ Support (面向AOP编程) -->
<!-- <aop:aspectj-autoproxy /> -->
<!-- 加上下面的bean之后会添加双层通知 有时候可能会用到这种方式-->
<!-- <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" /> -->
<!-- 基于xml的方式实现 aop -->
<bean id="log" class="beans.web.interceptor.LogInterceptor"></bean>
<aop:config>
<!--ref属性引用的是上面注册的bean-id,也可以把上面注册的bean也交给Spring管理,
直接用@Component标注,默认bean-id为logInterceptor,还可以给@Component加属性value指定bean-id -->
<aop:aspect id="logAspect" ref="log" order="0">
<aop:pointcut expression="execution(public void beans.service..*.*(..)))" id="mycut"/>
<aop:before method="doLogBefore" pointcut-ref="mycut"/>
</aop:aspect>
</aop:config>
</beans>
LogInterceptor类中的注解都已去掉。
OK,实例说完,顺手贴上自己对aop重点理解的整理(非代码,不过用代码排版看起来清晰很多,嘿嘿):
AOP (Aspect-Orianted Programming)
所谓切面(Aspect),强调将对象的运行过程加以分解,然后增加一系列的动作。就是一系列的切点(PointCut)和通知(Advice)的集合
|--->切入点(PointCut)
|--->切入点是连接点(JoinPoint)的集合(一个连接点可以看做是一个method)
|--->声明一个切入点,如:@Before("execution(public void beans.service.base.Dao.save())")
|--->public void beans.service..*.*()就是一些列连接点的集合,也就是一个切入点
|--->execution(* beans.service..*.*(..)) 是Spring中的aop表达式
|--->通知(Advice)
|--->Advice是处理动作.(也可以理解为一个方法)
|--->Advice的几种类型@Before/@After/@AfterReturning/@AfterThrowable/@Around
构建一个简单基于Annotation的AOP应用的步骤:
1.引入包:除了Spring框架基本包之外,还有AOP应用专用包aspectjrt.jar/aspectjweaver.jar/common-annotations.jar
2.构建beans.xml
|---><beans>的属性中关于aop的三个项
|---><aop:aspect-autoproxy /> <!--Enable @AspectJ Support-->
3.声明一个切面(Aspect):
@Component //将组件交给Spring来管理
@Aspect //定义切面
@Order(0) //指定执行顺序,order值小的在外层执行。
class LogInterceptor{
@Before("execution(* beans.service..save(..))") //表达式声明一个切入点,@Before定义通知
public void doLogBefore(){}
}
aop表达式:
1. public void beans.service.base.Dao.save() //单个的连接点组成的集合成为切入点
2. public void beans.service.base.Dao.save(..) //方法具有任意的参数列表
3. public void beans.service.base.Dao.save(int, ..) //方法第一个参数为int类型
4. public void beans.service.base.Dao.save(.., String) //方法最后一个参数为String类型
5. public void beans.service.base.Dao.*(..) //Dao的任意方法
6. public void beans.service.base.*.*(..) //beans.service.base子包下的任意类的任意方法
7. public void beans.service..*.*(..) //beans.service子包下的任意类的方法
8. public * beans.service..*.*(..) //任意返回值类型
9. * void beans.service..*.*(..) //任意访问修饰类型
10. ! void beans.service..*.*(..) //非空返回值类型,访问修饰不限
11. * beans.service..*.*(..) //不限定访问修饰和返回值类型