您的位置:首页 > 编程语言 > Java开发

Spring框架:AOP详解

2014-09-28 23:23 381 查看
AOP的中文名称叫做面向切面编程。这个名字非常形象,因为你真的可以把一个系统像面包一样切开,并直接在面包上增加修饰。切面可大可小,大到整个系统,小到某一个方法。

AOP有什么用呢?举个例子,每个组件中都可能含有安全、事务、数据库等方面的逻辑,AOP就是把每个组件中的安全作为一个方面进行集中处理,事务作为一个方面,数据库作为一个方面等等。这样才能做到高内聚、低耦合。AOP中有三个重要的术语:通知、切点、连接点。他们之间的关系如下图。



AOP实现的原理是修改目标类中的代码。至于怎么修改,有多种方式:编译时、类加载时、运行时。编译时修改需要特殊的编译器,类加载时修改需要特殊的类加载器。运行时,就是应用在运行的时候AOP框架会为目标对象生成一个动态代理类。Spring AOP采用的就是运行时代理。Spring容器通过ObjectFactory创建所有的Bean实例,并且实例之外增加一层动态代理。SpringAOP具体实现主要涉及到反射机制中的Proxy.newProxyInstance和InvocationHandler,在后续的JVM文章中还会详细介绍。

除了Spring AOP目前流行的AOP框架还有AspectJ、JBoss AOP。

下面是AOP的Hello World程序。目标是,在某个类的createApple方法调用之前做一些事情,但是又不能直接改变这个方法的代码。下面这段代码就是在createApple方法执行之前,额外执行beforeCreateApple,有点类似于Hook。代码如下:

<bean id="appleListener" class="xxx"/>

<aop:aspect ref="appleListener">
<aop:pointcut id="apple" expression="execution(* *.createApple(..))" />

<aop:before pointcut-ref="apple" method="beforeCreateApple" />
</aop:aspect>

上面这段代码的意思是,当程序中任何一个类的createApple方法被调用之前,都先调用appleListener中的beforeCreateApple方法。

切点表达式语言。上面例子中的execution(* *.createApple(..))就是表达式语言,第一个星号表示返回值的类型,第二个星号表示被调用的类名。支持如下语法:

args() 将参数传递给切面
@args() 匹配注解才传递参数
execution() 匹配具体的方法
this() 匹配当前bean
target() 匹配目标对象
@target() 匹配目标对象的注解
within() 匹配实例的类型
@within() 匹配实例的注解
@annotation() 匹配注解
bean() 匹配bean id

下面举例说明切点表达式语言。

// 切点为执行com.example.Apple.eat方法,返回值任意,参数任意。
execution(* com.example.Apple.eat(..))

// within表示只匹配com.example.*下的任意方法。用了and连接符号。
execution(* com.example.Apple.eat(..) and within(com.example.*))

// bean表示匹配相应的bean
execution(* com.example.Apple.eat(..) and bean(apple))


下面的例子演示了切点的各种修饰方式。

<aop:config>
<!--定义切面,test是事先定义好的一个bean-->
<aop:aspect ref="test">
<!--定义切点-->
<aop:pointcup id="apple-eat" expression="execution(* com.example.Apple.eat(..))"/>

<!--在切点之前调用test.beforeEat-->
<aop:before pointcut-ref="apple-eat" method="beforeEat"/>

<!--在切点执行成功之后调用-->
<aop:after-return pointcut-ref="apple-eat" method="eatSuccess"/>

<!--在切点执行失败之后调用-->
<aop:after-throwing pointcut-ref="apple-eat" method="eatFailed"/>

<!--在切点之后调用,不管成功失败-->
<aop:after pointcut-ref="apple-eat" method="afterEat"/>

<!--环绕通知,下面有详细说明-->
<aop:around pointcut-ref="apple-eat" method="eatApple"/>

<!--动态增加接口,下面有详细说明-->
<aop:declare-parents types-matching="com.example.Apple+" implement-interface="com.example.Fruit" default-impl="com.example.FruitImpl"/>
</aop:aspect>
</aop:config>


现代化的Spring支持注解方式的切面。下面请看例子。

// 定义切面
@Aspect
public class Test {
// 定义切点。方法中不需要写任何代码。
@Pointcut("execution(* com.example.Apple.eat(..))")
public void appleEat() { }

// 切面之前
@Before("appleEat()")
public void beforeEat() { }

// 切面执行成功之后
@AfterReturning("appleEat()")
public void eatSuccess() { }

// 切面执行失败之后
@AfterThrowing("appleEat()")
public void eatFailed() { }

// 切面之后,不管成功失败
@After("appleEat()")
public void afterEat() { }

// 环绕切面,下面有详细说明
@Around("appleEat")
public void eatApple(ProceedingJoinPoint joinpoint) { }

// 定义传递参数的切点
@Pointcut("execution(* com.example.Apple.eat(..)) and args(size)")
public void appleEat2() { }

// 接收切点的参数
@Before("appleEat2")
public void beforeEat2(int size) {
// 能够得到切点的size参数
}
}


环绕通知。它的目的是为了解决切点前后无法通信的问题。本质是四种切点的结合体。比如我想记录一个切点的执行时间,就需要用到环绕通知。下面是环绕通知的代码。

public void eatApple(ProceedingJoinPoint joinPoint) {
// 在切点之前
System.out.println("before pointcut");

// 手动执行切点
joinPoint.proceed();

// 在切点之后
System.out.println("after pointcut");
}


Introduction引入,也就是动态增加新接口。它的作用就是在程序运行的过程中动态地为一个实例增加接口。请看下面的例子。

// Introduction引入。
@DeclareParents(value="com.example.Phone+", defaultImpl="com.example.AppleWatchImpl")
public static AppleWatch appleWatch;

上面的例子中给appleWatch字段增加了一个注解,意思是让appleWatch字段可以转换成Phone类型,原本appleWatch是不能转换成Phone的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: