`
xm_king
  • 浏览: 392823 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
博客专栏
Group-logo
Spring技术内幕读书笔...
浏览量:15397
社区版块
存档分类
最新评论

Spring AOP 学习笔记-引子

阅读更多

引子

在过去的几年里, AOP( 面向方面编程 ) 已成为 Java 领域的热门话题,对 JAVA 开发人员来说,现在已经能够找到许多关于 AOP 的文章、讨论和实现了。 AOP 通常被称为实现横切关注点的工具,这意味着你可以使用 AOP 来将独立的逻辑片段模块化,也就是我们熟知的关注点,并将这些关注点应用于应用程序的多个地方。

AOP OOP 并不相互抵触,它们是可以相辅相成的两个设计模型, Spring AOP 是实现 AOP 的一种技术,而 Spring AOP 也是 Spring 中一些子框架或子功能所依赖的核心。

在本文中,首先会讲解两种截然不同的 AOP 类型,静态的和动态的。在静态 AOP , AspectJ AOP ,横切逻辑会在编译时应用到你的代码上,若非修改代码并重新编译的话你是不能改变它的。而使用动态 AOP 时,如 Spring AOP ,横切逻辑是在运行时被动态加入的,这允许你无需重新编译代码就能修改横切的使用。这两种 AOP 相互补充,将它们结合使用将会在我们的应用中形成强有力的组合。

Spring 项目中有很多能集各种设计模式、编码技巧为一体的编码艺术,在灵活应用 Spring 的同时,若能把 Spring 项目里面的精华、设计思想、编码技巧等吸纳过来,这对于程序员来说将会是一件非常有意义的事。

从代理机制初探 AOP

我们暂且把 AOP 放到一边,先从一个简单例子来看一个议题,这个例子当中包含日志 (Logging) 动作,程序中常需要为某些动作或事件记下记录,以便在事后检查程序运作过程,或是作为出错时的信息。

来看一个最简单的例子,当你需要在执行某些方法时留下日志信息,可能会如下编写:

package inside.aop;
import java.util.logging.Level;
import java.util.logging.Logger;

public class HelloSpeaker {
    private Logger logger=Logger.getLogger(this.getClass().getName());
    public void hello(String name){
       //方法执行开始时留下记录
       logger.log(Level.INFO,"hello method start..............");
       //程序主要功能
       System.out.println("hello"+name);
       //程序执行完毕时留下记录
       logger.log(Level.INFO,"hello method end..............");
    }
}

HelloSpeaker 类中,当执行 hello() 时,你希望方法在开始执行和执行完毕时都能留下记录,最简单的作法就是如以上的程序设计,在方法执行的前后加上日志动作,然而日志的这几行程序代码横切入 (Cross-cutting)HelloSpeaker 类中,对于 HelloSpeaker 类来说,日志的这几个动作并不属于 HelloSpeaker 业务逻辑,这无疑是 HelloSpeaker 增加了额外的职责 ( 违反了面向对象设计的类的单一职责原则 )

可以使用代理 (Proxy) 机制来解决这个问题,在这里讨论两种代理方法:静态代理 (Static Proxy) 与动态代理 (Dynamic Proxy)

静态代理

在静态代理的实现中,代理对象与被代理对象必须实现同一个接口,在代理对象中可以实现日志等相关服务,并在需要的时候在调用被代理的对象,如此,被代理对象当中就可以仅保留与业务相关的职责。

重新设计 HelloSpeaker 类,首先定义一个 IHello 接口:

package inside.aop;

public interface IHello {
    public void hello(String name);
}

        然后让实现业务逻辑的 HelloSpeaker 类实现 IHello 接口,例如:

package inside.aop;
public class HelloSpeaker implements IHello{ 
    public void hello(String name){
       //程序主要业务逻辑
       System.out.println("hello"+name);
    }
}

可以看到,在 HelloSpeaker 类中现在没有任何日志的程序插入其中,日志服务的实现将被放置代理之中,代理对象同样也要实现 IHello 接口,例如:

package inside.aop;

import java.util.logging.Level;
import java.util.logging.Logger;
public class HelloProxy implements IHello {
    private Logger logger=Logger.getLogger(this.getClass().getName());
    private IHello helloObject;
    public HelloProxy(IHello helloObject){
       this.helloObject=helloObject;
    }
    public void hello(String name) {
       //日志服务
       logger.log(Level.INFO,"hello method start..............");
       //执行业务逻辑
       helloObject.hello(name);
       //日志服务
       logger.log(Level.INFO,"hello method end..............");
    }
}

HelloProxy 类的 hello() 方法中,要真正实现业务逻辑前后可以安排日志服务,下面我们编写一个测试程序来看看如何使用代理对象。

package inside.aop;

public class StaticProxyTest {
    public static void main(String[] args) {
       IHello proxy=new HelloProxy(new HelloSpeaker());
       proxy.hello("aop");
    }
}

程序中调用执行的是代理对象,构造代理对象时必须给它一个被代理对象,记得在操作取回代理对象时,必须转换操作接口为 IHello 接口,下面是实际执行的结果。

2010-9-29 19:10:04 inside.aop.HelloProxy hello
信息: hello method start..............
hello,aop
2010-9-29 19:10:04 inside.aop.HelloProxy hello
信息: hello method end..............

这是静态代理的基本范例,然而正如你看到的,代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每种方法进行代理,静态代理在程序规模较大时就无法胜任了,根据这个设计思想在 JDK1.3 之后就加入了动态代理的功能。在这里介绍静态代理的目的,是为了了解代理的基本原理。

动态代理

JDK1.3 之后加入了可协助开发动态代理功能的 API ,从此不必为特定的对象和方法编写特定的代理对象。使用代理对象,可以使用一个处理者 (Handler) 服务于各个对象。首先,一个处理者的类设计必须实现 java.lang.reflect.InvocationHandler 接口,下面用实例来进行说明,设计一个 LogHandler 类:

package inside.aop;
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;

public class LogHandler implements InvocationHandler {
    private Logger logger=Logger.getLogger(this.getClass().getName());
    private Object target;
    public Object bind(Object target){
       this.target=target;
       return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                                  target.getClass().getInterfaces(),
                                  this);
    }

    public Object invoke(Object proxy, Method method, Object[] args)
           throws Throwable {
       Object retValue=null;
       logger.log(Level.INFO,"method start..........");
       retValue=method.invoke(target, args);
       logger.log(Level.INFO,"method end..........");
       return retValue;
    }
}

主要的概念是使用 Proxy .newProxyInstance() 静态方法建立一个代理对象,建立代理对象时必须告知要代理的接口,之后就可以操作所建立的代理对象。在每次操作时会执行 InvocationHandler invoke() 方法, invoke() 方法会传入被代理对象的方法名称与执行参数,实际上要执行的方法会交由 method.invoke() 。如果对上面代码不清楚的地方,请读者自行查阅相关的 JDK 动态代理的只是。

要实现动态代理,同样必须定义所要代理的接口,此处使用之前定义的 IHello 接口,以及 HelloSpeaker 类。测试程序如下所示:

package inside.aop;
public class DynamicProxyTest {
    public static void main(String[] args) {
       LogHandler logHandler=new LogHandler();
       IHello proxy=(IHello) logHandler.bind(new HelloSpeaker());
       proxy.hello("AOP");
    }
}

       来看一下执行结果,如下所示:

2010-9-29 19:42:36 inside.aop.LogHandler invoke
信息: method start..........
hello,AOP
2010-9-29 19:42:36 inside.aop.LogHandler invoke
信息: method end..........

LogHandler 不再服务于特定对象接口,而 HelloSpeaker 也不用插入任何有关日志的动作,它不用意识到日志动作的存在。

现在回到 AOP 的议题上,那么我们之前的例子和 AOP 有什么关系?

HelloSpeaker 本身的职责是显示文字,却要插入日志动作,这使得 HelloSpeaker 的职责加重,用 AOP 的术语来说,日志的程序代码横切 (Cross-cutting) HelloSpeaker 的程序执行流程中,日志这样的动作在 AOP 中称之为横切关注点 (Cross-cutting concern)

使用代理对象将日志等于业务逻辑无关的动作或任务提取出来,设计成一个服务对象,这样的对象称之为切面 (Aspect)

AOP 中的 Aspect 所指的像日志等这类的动作或服务,将这些动作 (Cross-cutting concerns) 设计为通用、不介入特定业务对象的一个职责清楚地 Aspect 对象,这就是所谓的 Aspect-oriented programming ,缩写名称即为 AOP

从上面的几个例子中可以看出,在好的设计之下, Aspect 可以独立于应用程序之外,在必要的时候,可以介入应用程序之中提供服务,在不需要相关服务的时候,又可以将这些 Aspect 直接从应用程序中脱离出来,而应用程序本身不需要修改任何一行程序代码。

AOP 术语

      Cross-cutting concern( 横切关注点 )

在上面的例子中,日志的动作原先被横切 (Cross -cutting) 入至 HelloSpeaker 本身所负责的业务流程中,另外类似于日志这类的动作,如安全 (Security) 检查、事务 (Transaction) 等系统层面的服务 (Service) ,在一些应用程序之中常被见到安插到各个对象的处理流程之中,这些动作在 AOP 的术语称为 Cross-cutting concerns

Aspect(切面)

将散落在各个业务逻辑之中的 Cross-cutting concerns 收集起来,设计成各个独立可重用的对象,这些对象称为 Aspect 。例如,在我们的例子中,将日志的动作设计为一个 LogHandler 类, LogHandler 类在 AOP 的术语就是 Aspect 的一个具体事例。

AOP 中着重于 Aspect 的辨认,使之从业务流程中独立出来。在需要该服务的时候,织入 (weave) 至应用程序之上;在不需要服务的时候,也可以马上从应用程序中剥离,且应用程序中的可重用组件不用做任何修改。

另一方面,对于应用程序中的可重用组件来说,按照 AOP 的设计方式,它不用知道提供服务的对象是否存在,具体地说,与服务相关的 API 不会出现在可重用的应用程序组件之上,因而可提高这些组件的可重用性,你可以把这些组件应用至其他的应用程序之中,不会因为加入了某些服务而与目前的应用程序框架发生耦合。

Spring AOP 中,一个方面是由一个实现 Advisor( 通知者 ) 接口的类来表示。 Spring 提供了一些使用方便的 Advisor 接口的接口类,这样不用在自己的程序中创建各种各样不同的 Advisor 实例。

Advice(增强或通知)

Advice 不管怎么翻译成建议、通知或者增强,都不能直接反映其内容。笔者认为通知稍微能够体现出 Advice 的本质。

Aspect 当中 Cross-cutting concerns 的具体实现称之为 Advice 。以日志的动作而言, Advice 中会包含日志程序代码是如何实现。 Advice 中包含了 Cross-cutting concerns 的行为或所要提供的服务。

换一种说法,通知 (Advice) 是指在定义好的切入点处,所要执行的程序代码。

JoinPoint(连接点)

Advice 在应用程序执行时加入业务流程的点或时机称之为 Joinpoint ,具体来说,就是 Advice 在应用程序中被执行的时机。 Spring 只支持方法的 Joinpoint ,执行时机可能是某个方法被执行之前或之后 ( 或两者都有 ) ,或是方法中某个异常发生的时候。

Spring AOP 中最明显的简化之一就是它只支持一种类型的连接点:方法调用。我们可以用它来完成大多数用到 AOP 的日常编程任务。

Pointcut(切入点)

Pointcut 定义了感兴趣的 Joinpoint ,当调用的方法符合 Pointcut 表示式时,将 Advice 织入至应用程序上提供服务。切入点指一个或多个连接点,可以理解成一个点的集合。切入点的描述比较具体,而且一般会跟连接点上下文环境结合。

Target(目标对象)

         一个 Advice 被应用的对象或目标对象,在基于拦截器机制实现的 AOP 框架中,位于拦截器链上最末端的对象实例。一般情况下,拦截器末端都包含一个目标对象,通常也是实际业务对象。

       Introduction(引入)

对于一个现存的类, Introduction 可以为其增加行为,且不用修改该类的程序,具体来说,可以为某个已编写或编译完的类,在执行时期动态地加入一些方法或行为,而不用修改或新增任何一行程序代码。

Spring 中,引入被认为是一种特殊的通知。

Interceptor(拦截器)

         拦截器是用来实现对连接点进行拦截,从而在连接点或后加入自定义的切面模块功能。在大多数 JAVA 的框架实现中,都是使用拦截器来实现字段访问及方法调用的拦截 (Interception) 。所以作用于同一个连接点的多个拦截器组成的一个拦截器链 (Interceptor  chain) ,链接上的每个拦截器通常会调用下一个拦截器。 Spring  AOP 就是采用拦截器来实现。

       Proxy(代理)

在之前的静态代理和动态代理中,已经使用了实际的程序范例介绍过的代理机制, Spring AOP 主要是通过动态代理来完成的,可用于代理任何的接口。另一方面, Spring 也可以使用 CGLIB 代理,可以代理类。

Weave(织入)

       Advice 被应用至对象之上的过程成为织入 (Weave) ,在 AOP 中织入的方式有几个时间点:编译时期 (Compile time) 、类加载时期 (Classload time) 、执行时期 (Runtime).

分享到:
评论
1 楼 貌似掉线 2013-03-04  
写得很好,,受教了,,谢谢!

相关推荐

    Spring5 框架 ---- AOP ---- 代码

    Spring5 框架 ---- AOP ---- 代码 Spring5 框架 ---- AOP ---- 代码 Spring5 框架 ---- AOP ---- 代码 Spring5 框架 ---- AOP ---- 代码 Spring5 框架 ---- AOP ---- 代码 Spring5 框架 ---- AOP ---- 代码 Spring5 ...

    spring-aop-5.3.10-API文档-中文版.zip

    赠送jar包:spring-aop-5.3.10.jar; 赠送原API文档:spring-aop-5.3.10-javadoc.jar; 赠送源代码:spring-aop-5.3.10-sources.jar; 赠送Maven依赖信息文件:spring-aop-5.3.10.pom; 包含翻译后的API文档:spring...

    spring-aop.jar各个版本

    spring-aop-1.1.1.jar spring-aop-1.2.6.jar spring-aop-1.2.9.jar spring-aop-2.0.2.jar spring-aop-2.0.6.jar spring-aop-2.0.7.jar spring-aop-2.0.8.jar spring-aop-2.0.jar spring-aop-2.5.1.jar spring-aop-...

    开发工具 spring-aop-4.3.6.RELEASE

    开发工具 spring-aop-4.3.6.RELEASE开发工具 spring-aop-4.3.6.RELEASE开发工具 spring-aop-4.3.6.RELEASE开发工具 spring-aop-4.3.6.RELEASE开发工具 spring-aop-4.3.6.RELEASE开发工具 spring-aop-4.3.6.RELEASE...

    spring-aop-5.3.12-API文档-中英对照版.zip

    赠送jar包:spring-aop-5.3.12.jar; 赠送原API文档:spring-aop-5.3.12-javadoc.jar; 赠送源代码:spring-aop-5.3.12-sources.jar; 赠送Maven依赖信息文件:spring-aop-5.3.12.pom; 包含翻译后的API文档:spring...

    spring-aop-5.3.15-API文档-中英对照版.zip

    赠送jar包:spring-aop-5.3.15.jar; 赠送原API文档:spring-aop-5.3.15-javadoc.jar; 赠送源代码:spring-aop-5.3.15-sources.jar; 赠送Maven依赖信息文件:spring-aop-5.3.15.pom; 包含翻译后的API文档:spring...

    spring-aop-5.3.15-API文档-中文版.zip

    赠送jar包:spring-aop-5.3.15.jar; 赠送原API文档:spring-aop-5.3.15-javadoc.jar; 赠送源代码:spring-aop-5.3.15-sources.jar; 赠送Maven依赖信息文件:spring-aop-5.3.15.pom; 包含翻译后的API文档:spring...

    spring-aop-5.3.7-API文档-中文版.zip

    赠送jar包:spring-aop-5.3.7.jar; 赠送原API文档:spring-aop-5.3.7-javadoc.jar; 赠送源代码:spring-aop-5.3.7-sources.jar; 赠送Maven依赖信息文件:spring-aop-5.3.7.pom; 包含翻译后的API文档:spring-aop...

    spring-aop-5.2.0.RELEASE-API文档-中文版.zip

    赠送jar包:spring-aop-5.2.0.RELEASE.jar; 赠送原API文档:spring-aop-5.2.0.RELEASE-javadoc.jar; 赠送源代码:spring-aop-5.2.0.RELEASE-sources.jar; 赠送Maven依赖信息文件:spring-aop-5.2.0.RELEASE.pom;...

    spring-aop-5.3.7-API文档-中英对照版.zip

    赠送jar包:spring-aop-5.3.7.jar; 赠送原API文档:spring-aop-5.3.7-javadoc.jar; 赠送源代码:spring-aop-5.3.7-sources.jar; 赠送Maven依赖信息文件:spring-aop-5.3.7.pom; 包含翻译后的API文档:spring-aop...

    spring-aop-5.3.10-API文档-中英对照版.zip

    赠送jar包:spring-aop-5.3.10.jar;赠送原API文档:spring-aop-5.3.10-javadoc.jar;赠送源代码:spring-aop-5.3.10-sources.jar;赠送Maven依赖信息文件:spring-aop-5.3.10.pom;包含翻译后的API文档:spring-aop...

    aopalliance-1.0.jar,org.springframework.aop-3.0.0.RELEASE.jar

    aopalliance-1.0.jar,org.springframework.aop-3.0.0.RELEASE.jar,org.springframework.jdbc-3.0.0.RELEASEorg.springframework.beans-3.0.0.RELEASE.jar等

    spring-aop-5.0.8.RELEASE-API文档-中英对照版.zip

    赠送jar包:spring-aop-5.0.8.RELEASE.jar; 赠送原API文档:spring-aop-5.0.8.RELEASE-javadoc.jar; 赠送源代码:spring-aop-5.0.8.RELEASE-sources.jar; 赠送Maven依赖信息文件:spring-aop-5.0.8.RELEASE.pom;...

    小马哥讲 Spring AOP 编程思想 - API 线索图.pdf

    小马哥讲 Spring AOP 编程思想 - API 线索图.pdf

    org.springframework.aop-sources-3.0.5.release.jar

    org.springframework.aop-sources-3.0.5.release.jar

    spring ioc aop mvc boot-学习笔记.docx

    自己学习spring课程的笔记。笔记都是根据尚硅谷的课程(spring ioc,spring aop,spring mvc,spring boot等)写的。 主要内容:spring ioc,spring aop,spring mvc,spring boot

    spring-aop-5.0.10.RELEASE-API文档-中文版.zip

    赠送jar包:spring-aop-5.0.10.RELEASE.jar; 赠送原API文档:spring-aop-5.0.10.RELEASE-javadoc.jar; 赠送源代码:spring-aop-5.0.10.RELEASE-sources.jar; 赠送Maven依赖信息文件:spring-aop-5.0.10.RELEASE....

    spring aop 学习笔记

    NULL 博文链接:https://microjava.iteye.com/blog/525796

    spring-aop-4.2.2.RELEASE-API文档-中文版.zip

    赠送jar包:spring-aop-4.2.2.RELEASE.jar; 赠送原API文档:spring-aop-4.2.2.RELEASE-javadoc.jar; 赠送源代码:spring-aop-4.2.2.RELEASE-sources.jar; 赠送Maven依赖信息文件:spring-aop-4.2.2.RELEASE.pom;...

    spring-aop-annotation-log-all

    spring-aop-4.0.4.RELEASE.jar com.springsource.net.sf.cglib-2.2.0.jar com.springsource.org.aopalliance-1.0.0.jar com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar spring-aspects-4.1.2.RELEASE.jar ...

Global site tag (gtag.js) - Google Analytics