Spring aop 原理解析(AOP 简介 和 Java 动态代理)

AOP是什么

在软件业,AOP为Aspect Oriented Programming的缩写,意为:[面向切面编程] 通过[预编译]方式和运行期动态代理实现程序功能的统一维护的一种技术(摘自百度百科)。不知道是不是又很多人和我一样,在第一次看到这句话的时候一头雾水。
在我的理解来看,AOP可以理解为一种独立于模块的,可以穿插在方法间执行的技术。当还没有接触AOP这一概念的时候,我们习惯于用OO的思想去解决问题,但并不是什么情况下OO都是合适的。
比方说,当我们想要去统计系统中某个类和它子类的方法调用的次数。如果我们用OO的思想,一般会这样。首先,定义一个工具类,然后让基类持有这个工具类。但可能有一天,需求变了,可能范围变成了某个包下的类,这个时候就麻烦了。因为有可能需要对这个包下的所有类都进行改动,这肯定不是我们想要的结果。
那么理想情况下是什么呢,假设有一个东西叫切面,在这个切面上我们可以定义一个切点以及一系列的操作,而只有当切点满足的时候(比如在某个包下的类的某个方法被执行),这些操作才会被执行,这样,给予刚刚的那种情况,我们只需要定义一个切面,其切点为某个包下的所有类的方法,然后在定义一个统计操作,这样每次方法被调用时,就会去触发统计操作。

在jvm中,一个线程在方法进入时会压栈,在退出时会出栈,这些方法进栈和出栈的节点可以被理解为一个个jointPoint 而我们所添加的操作也时在这些个JointPoint上执行。
main-thread.png

其实对于AOP而言,AspectJ 也是一种非常优秀的解决方案,AspectJ 会在编译期对源码进行织入,从而达到方法增强的效果,不过要使用AspectJ 需要对字节码有一定的了解,而且还要学习AspectJ的语法,需要一定的学习成本。对于Spring 来说,采用动态织入的方法,在运行期生成代理类,从而达到增强的效果。
在了解spring中的aop之前,我们先来复习一下代理模式。
delagate.png
(作图工具只找到了这条黑线,╮(╯▽╰)╭,凑合着看吧。)

代理类和被代理实现同一接口,同时代理类持有被代理类,并对代理类的接口进行增强,客户端在调用接口时,实际上调用的时被代理的接口。在spring-aop中,实际上使用了java自带的动态代理和cglib这个第三方生成类库,这个类库封装了asm(一个操作字节码的库), 可以在运行时动态生成class,这两种策略个有其使用场景。

java动态代理

我们首先先简单的了解一下java动态代理,要使用java的动态代理,首先需要实现java.lang.reflect.InvocationHandler 这个接口

public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

一般来说,可以在自己实现的InvationHandler里去持有被代理的对象,然后通过java.lang.reflect.Proxy#newProxyInstance 去生成代理对象

 public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException

这里 iterfaces 为代理对象所实现的接口,我们看一个简单的例子

dynamic-delegate.png

在这个例子中,我们先定义一个顶层空接口ISub

package com.iplay.dynamic_proxy;

public interface ISub {

}

之后我们分别定义两个接口IHelloICal 并分别继承ISub

package com.iplay.dynamic_proxy;

public interface IHello extends ISub{
    void sayHello();
}
package com.iplay.dynamic_proxy;

public interface ICal extends ISub{
    int add (int a, int b);
}

之后我们定义一个Sub,分别实现IHelloICal

package com.iplay.dynamic_proxy;

public class Sub implements ICal, IHello {
    
    public void sayHello() {
        System.out.println("Hello");
    }

    public int add(int a, int b) {
        return a + b;
    }

}

之后我们定义一个CallHandler并让它实现InvocationHandler

package com.iplay.dynamic_proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class CallHandler implements InvocationHandler {
    
    private final ISub isub;
    
    public CallHandler(ISub isub) {
        this.isub = isub;
    }
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before called");
        
        Object ret = method.invoke(isub, args);
        
        System.out.println("after called");
        
        return ret;
    }

}

最后我们看一下客户端类

package com.iplay.dynamic_proxy;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;

/**
 * Hello world!
 *
 */
public class App {
    
    public static void main(String args[]) {
        ISub isub = new Sub();
        CallHandler callHandler = new CallHandler(isub);
        Object proxy = Proxy.newProxyInstance(Sub.class.getClassLoader(), new Class<?>[] {ICal.class, IHello.class}, callHandler);
        Class<?> clz = proxy.getClass();
        System.out.println("sup clz " + clz.getSuperclass());
        System.out.println(Modifier.toString(clz.getModifiers()) + " " + clz.getName());
        
        Class<?>[] itfs = clz.getInterfaces();
        for(int i=0; i<itfs.length; i++) {
            System.out.println(itfs[i].getName());
        }
        
        Method[] methods = clz.getMethods();
        for(int i=0; i<methods.length; i++) {
            System.out.println(Modifier.toString(methods[i].getModifiers()) + " " + methods[i].getName());
        }
        
        ((IHello) proxy).sayHello();
        System.out.println(((ICal) proxy).add(1, 2));
    }
    
}

执行后结果

sup clz class java.lang.reflect.Proxy
public final com.sun.proxy.$Proxy0
com.iplay.dynamic_proxy.ICal
com.iplay.dynamic_proxy.IHello
public final add
public final equals
public final toString
public final hashCode
public final sayHello
public static isProxyClass
public static getInvocationHandler
public static transient getProxyClass
public static newProxyInstance
public final wait
public final wait
public final native wait
public final native getClass
public final native notify
public final native notifyAll
before called
Hello
after called
before called
after called
3

可以看到Proxy#newInstance 生成了一个新的类$Proxy0, 这个类是final的,继承了Proxy, 并实现了我们传入了的两个接口IHelloICal并将实现的方法都设置为了final,同时这个类持有了我们传入的CallHandler,CallHandler中持有被代理对象,所有当我们通过ICal, IHello调用目标对象时,实际是通过$Proxy调用对应的方法,在调用时,$Proxy 会通过反射获取对应的Method,传给InvocationHandler#invoke执行对应的操作。

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

推荐阅读更多精彩内容