Spring AOP篇

一、什么是AOP?

AOP(Aspect Oriented Programming)面向切面编程。

AOP和OOP的区别与联系
OOP(面向对象编程)针对问题领域中以及业务处理过程中存在的实体及其属性和操作进行抽象和封装,面向对象的核心概念是纵向结构的,其目的是获得更加清晰高效的逻辑单元划分。

而 AOP则是针对业务处理过程中的切面进行提取,用面向对象的思路,将业务操作对象的核心功能和对它的其他服务性功能代码分离,即某一个操作在各个模块中都有涉及,这个操作就可以看成“横切”存在于系统当中。 AOP则将这些操作与业务逻辑分离,使程序员在编写程序时可以专注于业务逻辑的处理,而利用 AOP将贯穿于各个模块间的横切关注点自动耦合进来。

我简单的理解为,OOP是纵向,主要负责业务处理;AOP是横向,负责对每个纵向业务不同阶段执行相同的操作。

AOP是OOP的延续和补充,二者 并不是相互竞争的两种技术,

为什么需要AOP?AOP能干啥?
开发中经常会面临的种种非功能性需求(操作日志、权限控制、性能监测等等),在很多方法中需要做这类重复的工作,如果直接写在方法中,会有很多重复代码,而且难以维护。这个时候,AOP就是一个非常好的选择。

Spring的一个关键组件是AOP框架,Spring AOP 模块提供拦截器来拦截一个应用程序,例如,当执行一个方法时,你可以在方法执行之前或之后添加额外的功能。

二、Spring AOP原理

Spring AOP是通过动态代理来实现的,要弄清楚Spring AOP的原理,首先要明白什么是动态代理。
首先回顾一下设计模式里的代理模式。
代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个人或一个机构代表另一个人或者一个机构采取行动。某些情况下,客户不想或者不能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。客户端分辨不出代理主题对象与真实主题对象。代理模式可以并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代为创建并传入。
更详细内容可以看这一篇:设计模式(一)

然后再来了解一下静态代理。

1.静态代理

直接上代码举例。
首先我们有一个Service接口

public interface Service{
    void doSomething();
}

有Service接口的实现类ServiceImpl

public class ServiceImpl implements Service {
    @Override
    public void doSomething() {
        System.out.println("do something...");
    }
}

然后我们想在do something前后做点别的事情(实际开发过程中可能是参数校验、统计方法执行时间、检查对象状态、记录日志等等),也就是有一些前置或者后续动作,但是又不想改变现有的实现类,该怎么办?这个时候就可以使用代理模式来做方法增强。

创建一个代理类

public class ServiceStaticProxy implements Service {
    private Service service;

    public ServiceStaticProxy(Service service) {
        this.service = service;
    }

    @Override
    public void doSomething() {
        System.out.println("pre actions");
        service.doSomething();
        System.out.prinln("post actions");
    }
}

测试类App.java

public class App {

    public static void main(String[] args) {
        ServiceImpl myService = new ServiceImpl(); // 真实对象
        ServiceStaticProxy proxy = new ServiceStaticProxy(myService); // 与代理对象建立联系
        proxy.doSomething(); // 使用代理
    }
}

测试结果:


测试结果

这样就可以在不修改目标对象功能的前提下,对目标功能进行扩展。静态代理方式在编译时就确定了代理类与目标类之间的关系。

考虑一下,如果ServiceImpl类中还有别的方法,也需要加上前置或者后续动作,就不得不将代码写n遍。进一步如果还有别的Service接口实现类里面的所有方法也需要加上这些动作,那要重复的地方就更多了。这个时候,动态代理闪亮登场。

2.动态代理

动态代理有两种实现方式,JDK反射机制,和cglib。

2.1 JDK动态代理

JDK提供了InvocationHandler接口和Proxy类,借助这两个工具可以实现动态代理。
还是拿上一小节静态代理中使用的Service接口和ServiceImpl类举例,搞一个JDK动态代理工厂类:

public class JDKProxyFactory {
    private Object target;

    public JDKProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        Object proxy = Proxy.newProxyInstance(
            target.getClass().getClassLoader(), 
            target.getClass().getInterfaces(),
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("pre actions");
                    Object result = method.invoke(target, args);
                    System.out.println("post actions");
                    return result;
                }
            });
        return proxy;
    }
}

即使Service接口新增一个方法doSomethingElse也可以达到相同的效果,减少重复代码。

测试类App.java

public class App {

    public static void main(String[] args) throws Throwable {
        Service proxy = (Service) new JDKProxyFactory(new ServiceImpl()).getProxyInstance();
        proxy.doSomething();
        proxy.doSomethingElse();
    }
}

测试结果:


测试结果

可以发现,使用JDK动态代理的目标类必须实现某个接口。如果想要代理的目标类没有实现任何接口怎么办?还有另外一种动态代理的方式。

2.2 cglib动态代理

还是一样的Service接口和ServiceImpl实现类,搞一个cglib动态代理工厂类

public class CglibProxyFactory implements MethodInterceptor {
    private Object target;

    public CglibProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        // Enhancer工具类
        Enhancer enhancer = new Enhancer();
        // 设置父类
        enhancer.setSuperclass(target.getClass());
        // 设置回调函数
        enhancer.setCallback(this);
        // 创建子类(代理对象)
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
        throws Throwable {
        System.out.println("pre actions");
        Object result = method.invoke(target, objects);
        System.out.println("post actions");
        return result;
    }
}

这里的MethodInterceptor接口和Enhancer类是spring-core.jar包中的。

App.java测试类还是一样,不过把jdk动态代理工厂改成了cglib的:

public class App {

    public static void main(String[] args) throws Throwable {
        Service proxy = (Service) new CglibProxyFactory(new ServiceImpl()).getProxyInstance();
        proxy.doSomething();
        proxy.doSomethingElse();
    }
}

测试结果:


测试结果

cglib的实现方式是创建目标类的子类,所以有关键字final修饰的类是无法做动态代理的,因为其无法被继承。

2.3 spring AOP的动态代理

spring AOP的动态代理原理就是jdk方式+cglib方式。对需要做动态代理的类先判断其有没有实现接口,如果有,就用jdk的方式;没有的话,就是cglib方式。

三、AOP相关术语

Aspect(切面):Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。

Joinpoint(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。

Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。

Advice(通知):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。

Target(目标对象):织入 Advice 的目标对象。

Weaving(织入):将切面应用到目标对象并导致代理对象创建的过程。

其中,通知又分为以下几类:
前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。

后置通知(After returning advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。

异常通知(After throwing advice):在方法抛出异常退出时执行的通知。

最终通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。

环绕通知(Around Advice):包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。

四、基于AOP的xml架构

1.导入 spring-aop j架构,如下:
beans.xml

还需要在应用程序的CLASSPATH中使用以下AspectJ 库文件:
aspectjrt.jar
aspectjweaver.jar
aspectj.jar
aopalliance.jar

2.声明aop
beans.xml

作为切面的Logging类:


Logging.java

作为切入点的Student类:


Student.java

MainApp.java内容:


MainApp.java

Beans.xml中bean的声明:


beans.xml

运行结果:


运行结果

五、基于AOP的@AspectJ注解

类比xml声明AOP
首先是Logging类的代码:

package com.tutorialspoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
@Aspect
public class Logging {
   /** Following is the definition for a pointcut to select
    *  all the methods available. So advice will be called
    *  for all the methods.
    */
   @Pointcut("execution(* com.tutorialspoint.*.*(..))")
   private void selectAll(){}
   /** 
    * This is the method which I would like to execute
    * before a selected method execution.
    */
   @Before("selectAll()")
   public void beforeAdvice(){
      System.out.println("Going to setup student profile.");
   }
   /** 
    * This is the method which I would like to execute
    * after a selected method execution.
    */
   @After("selectAll()")
   public void afterAdvice(){
      System.out.println("Student profile has been setup.");
   }
   /** 
    * This is the method which I would like to execute
    * when any method returns.
    */
   @AfterReturning(pointcut = "selectAll()", returning="retVal")
   public void afterReturningAdvice(Object retVal){
      System.out.println("Returning:" + retVal.toString() );
   }
   /**
    * This is the method which I would like to execute
    * if there is an exception raised by any method.
    */
   @AfterThrowing(pointcut = "selectAll()", throwing = "ex")
   public void AfterThrowingAdvice(IllegalArgumentException ex){
      System.out.println("There has been an exception: " + ex.toString());   
   }  
}

Student类的内容:


Student.java

MainApp.java内容:


MainApp.java

Beans.xml配置:


beans.xml

程序输出:


程序输出
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,200评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,526评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,321评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,601评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,446评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,345评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,753评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,405评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,712评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,743评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,529评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,369评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,770评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,026评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,301评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,732评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,927评论 2 336