Spring AOP浅析

来源:互联网 发布:折扣怎么算法 编辑:程序博客网 时间:2024/04/25 11:54

什么是AOP?

AOP Aspect-oriented Programing,也就是面向切面编程,那么什么是面向切面编程呢?
大家对OOP一定很熟悉,也就是面向对象编程。OOP主要是为了解决代码的可重用行,可扩展性等而引入的。它的特征是继承,多态,封装和抽象。
AOP和OOP不在一个层次上,解决的是不同的问题,两者可以一起使用。AOP解决什么问题呢?
我们一定有这样的需求,在一个项目工程中,有些代码是用来实现业务逻辑的,而有些代码实现一些其他的功能,如日志,安全等。通常,我们要实现日志记录,都是在需要的地方new一个Log对象,然后调用这个日志对象的方法实现日志记录等功能。
这样会有一个问题,业务逻辑代码和日志代码混在一起,即便有依赖注入实现解耦,但这种方式依然不完美,不便于调试和修改。那么,有没有一种方法可以使业务逻辑的代码和其它业务无关的代码完全分离开呢?Spring的AOP就是实现这个功能的。

关于AOP编程,有几个重要概念需要介绍:
- Advice
- Joint Points
- Pointcuts
- Aspects
- Weaving

Advice

切面要完成的工作就是Advice,他定义了what和when两个方面,即什么时候完成什么动作。
一共有5种类型的Advice,分别是:
1. Before–在某个指定的方法执行前执行;
2. After–在指定的方法完成后执行,无论成功或失败;
3. After-returning–在指定的方法成功返回后执行
4. After-throwing–在指定的方法抛出异常后执行;
5. Around–可以同时完成上面四个动作;

Joint Points

一个可以插入额外执行流程的位置就是一个Joint Point,它可以是一个方法被调用,一个异常被抛出,甚至是一个变量被改变。以Log为例,每个可以添加日志的地方就是一个Joint Point。Spring只支持方法的调用作为Joint Point,在大部分情况下,这是够用的,如果需要其他的比如参数的更改作为Joint Point,可以考虑其他的AOP框架,如AspectJ。

Pointcuts

不是每个Joint Point都要插入一个advice,那些真正插入了advice,也就是真正加入了额外执行流程的地方就是一个Pointcut。虽然有很多地方可以添加日志,但不是所有日志都是必须的,只有那些真正添加了日志的地方才是Pointcut。如果说advice定义了when和what,那么pointcut定义了where。即在什么地方执行额外的流程。

Aspects

一个Aspect是advice和pointcut的集合,也就是什么时候(when)在哪儿(where)做什么事(what)。

Weaving

weaving是将aspect编织到pointcut的过程,Spring的这个过程可以发生在不同的时段,什么时候编织有如下几个:
1. Compile time:在目标class(也就是提供pointcut的那个class)编译时将aspect编织进去,这需要特殊的编译器,AspectJ 的编译器以这种方式工作。
2. Class load time:在目标class被加载入jvm时编织。这需要一个特殊的ClassLoader。AspectJ5 以这种方式工作。
3. Runtime:程序执行时编织,这是Spring AOP的工作方式。

Spring AOP 示例

说了这么多,下面通过一个例子说明Spring的用法。
首先创建一个performance 接口,如下:

package demo;public interface Performance {    public void perform();}
接口很简单,只有一个perform方法,我们希望捕获所有这个接口的实现类的perform方法的执行,然后添加相应的代码,我们需要创建下面这样的检查点(pointcut),

execution(* concert.Performance.perform(..))
以上的pointcut只是简单的捕获了perform方法的执行,如果我们要添加更多的限制条件,可以使用AspectJ的检查点扩展语言(AspectJ’s pointcut expression language),它有如下几个方法:

AspectJ designator Description args() 仅当检查点的方法的参数和给定的类型相同时才触发advice @args() execution() 限制检查点只能是方法的执行 this() target() @target() within() @within() @annotation 限制仅当检查点的方法带有某个注解时才触发

扩展语言之间使用&&and 连接可以添加多个限制。
除此之外,还可以限制当具体哪个bean的特定方法执行时触发advice,比如:
execution(* demo.Performance.perform(..)) && bean('hello')
这就限制只有当id为“hello”执行perform方法时才会触发advice。

接着上面的例子,下面创建Aspect,编写一个如下的class

@Aspectpublic class Audience {    @Pointcut("execution(* cap4.Performance.perform(..))")    public void performance(){}    @Before("performance()")    public void silenceCellPhones() {        System.out.println("Silencing cell phones");    }    @Before("performance()")    public void takeSeats() {        System.out.println("taking seats");    }    @AfterReturning("performance()")    public void applause() {        System.out.println("Clap Clap Clap");    }    @AfterThrowing("performance()")    public void demandRefund() {        System.out.println("Demanding a refund");    }}
这里,我们使用`@Pointcut` 注解创建了一个Pointcut,捕获所有实现了Performance接口的bean的perform方法的执行,然后分别使用三种不同的注解定义了四个方法。程序的大意是:表演开始前观众要关闭手机并就坐,表演结束后要鼓掌,而如果演出出现意外,需要退款。需要指出的是,这里我们使用`@Pointcut` 注解定义了一个切入点,也可以在每个方法头上的注解后面的括号里直接添加切入点,只不过上面的例子需要写四次,为了方便,我们使用注解定义了pointcut。

边写好了Audience 类后,需要在配置文件中定义bean,如下:

@Configuration@ComponentScan(basePackageClasses = Audience.class)@EnableAspectJAutoProxypublic class DemoConfig {    @Bean    public  Audience audience(){        return new Audience();    }}

注意,在配置class的上方,我们使用了@EnableAspectJAutoProxy 注解开启了auto-proxy。如果使用xml的配置方式,使用 <aop:aspectj-autoproxy /> 标签。

为了测试,我们编写Concert class实现Performance接口,代码如下:

package demo;import org.springframework.stereotype.Component;/** * Created by chyzh on 2016/3/21. */@Componentpublic class Concert implements Performance{    @Override    public void perform() {        System.out.println("singing");    }}

然后编写如下的测试class:

package demo;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;/** * Created by chyzh on 2016/3/21. */@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = DemoConfig.class)public class TestDemo {    @Autowired    private Performance performance;    @Test    public void test() {        performance.perform();    }}

执行测试,可以看到如下输出:

Silencing cell phonestaking seatssingingClap Clap Clap

高级特性

处理advice中的参数

上面的例子非常简单,perform方法没有任何参数。但如果perform方法有参数,那么其advice能否获得其参数呢?如果能,我们可以做更多的事。
考虑如下场景,某一天有一个演唱会,表演结束后评论家需要给出最演唱会的评价,这就非常适合使用SpringAOP实现,首先我们把Performance 接口改成下面这样,使perform方法接受一个参数,在这里可以是表演的节目的名字:

package demo;/** * Created by chyzh on 2016/3/21. */public interface Performance {    public void perform(String name);}

然后编写Concert 类实现这个接口:

package demo;import org.springframework.stereotype.Component;/** * Created by chyzh on 2016/3/21. */@Componentpublic class Concert implements Performance{    public void perform(String name) {        System.out.println("performing " + name);    }}

接着编写评论家的Critic 类,代码如下:

package demo;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;/** * Created by chyzh on 2016/3/21. */@Aspectpublic class Critic {    @Pointcut("execution(* demo.Performance.perform(String)) && args(name)")    public void performance(String name){}    @AfterReturning("performance(name)")    public void comment(String name) {        System.out.println("The performance  " + name + " is very good!");    }}

我们创建了一个Pointcut,同样是监听perform方法,切使用args()获取了它的参数,然后在评论方法comment中使用额这个参数。

接着是使用java配置的配置文件:

package demo;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.EnableAspectJAutoProxy;/** * Created by chyzh on 2016/3/21. */@Configuration@ComponentScan(basePackageClasses = Critic.class)@EnableAspectJAutoProxypublic class DemoConfig {    @Bean    public Critic critic() {        return  new Critic();    }}

因为Concert bean使用了自动装入,这里就只有Critic bean的定义,接着测试这个例子,测试类如下:

package demo;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;/** * Created by chyzh on 2016/3/21. */@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = DemoConfig.class)public class TestDemo {    @Autowired    private Performance performance;    @Test    public void test() {        performance.perform("Uptown Funk");    }}

测试类自动装入了一个Performance类型的bean,在test方法中调用它的perform方法,并传入一个字符串作为表演节目的名字。运行测试,可以看到如下的输出:

performing Uptown FunkThe performance  Uptown Funk is very good!

可以看到aspect Critic成功获得了传入的参数。

有关AOP的内容介绍的差不多了,有关xml配置AOP的部分,可以参考《Spring in Action》这本书,里面的介绍很详细。

0 0
原创粉丝点击