讲解AOP代理和重点解析Spring AOP代理(动态代理)

2020-10-14   96 次阅读


上一章我们生动形象的讲了什么是AOP面向切面编程以及代码示例,有不明白的小伙伴可以去看看(https://blog.csdn.net/qq_32317661/article/details/82878679),承上启下,这一篇讲一下Spring AOP代理的两种方式和它的大致原理。

一、AOP代理

AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。

使用AspectJ的编译时增强实现AOP

之前提到,AspectJ是静态代理的增强,所谓的静态代理就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强。

举个实例的例子来说。首先我们有一个普通的Hello

1

2

3

4

5

6

7

8

9

10

public class Hello {

public void sayHello() {

System.out.println(``"hello"``);

}

public static void main(String[] args) {

Hello h = new Hello();

h.sayHello();

}

}

使用AspectJ编写一个Aspect

1

2

3

4

5

6

7

public aspect TxAspect {

void around():call(``void Hello.sayHello()){

System.out.println(``"开始事务 ..."``);

proceed();

System.out.println(``"事务结束 ..."``);

}

}

这里模拟了一个事务的场景,类似于Spring的声明式事务。使用AspectJ的编译器编译

1

ajc -d . Hello.java TxAspect.aj

编译完成之后再运行这个Hello类,可以看到以下输出

1

2

3

开始事务 ...

hello

事务结束 ...

显然,AOP已经生效了,那么究竟AspectJ是如何在没有修改Hello类的情况下为Hello类增加新功能的呢?

查看一下编译后的Hello.class

1

2

3

4

5

6

7

8

9

10

11

12

13

public class Hello {

public Hello() {

}

public void sayHello() {

System.out.println(``"hello"``);

}

public static void main(String[] args) {

Hello h = new Hello();

sayHello_aroundBody1$advice(h, TxAspect.aspectOf(), (AroundClosure)``null``);

}

}

可以看到,这个类比原来的Hello.java多了一些代码,这就是AspectJ的静态代理,它会在编译阶段将Aspect织入Java字节码中, 运行的时候就是经过增强之后的AOP对象。

1

2

3

4

5

public void ajc$around$com_listenzhangbin_aop_TxAspect$``1``$f54fe983(AroundClosure ajc$aroundClosure) {

System.out.println(``"开始事务 ..."``);

ajc$around$com_listenzhangbin_aop_TxAspect$``1``$f54fe983proceed(ajc$aroundClosure);

System.out.println(``"事务结束 ..."``);

}

从Aspect编译后的class文件可以更明显的看出执行的逻辑。proceed方法就是回调执行被代理类中的方法。

使用Spring AOP(重点掌握)

与AspectJ的静态代理不同,Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象(我们可以把它称之为‘代理对象’),这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。下面我们来依次讲解:

一、JDK动态代理

JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。现在都推荐面向接口编程,我们做的项目都是各种接口+实现类,所以是不是觉得这种代理方式和现在的接口编程很符合呢!

所以一个spring项目有接口和实现类,如果不在spring配置文件中特殊配置的话(就是默认配置),默认的动态代理方式就是JDK动态代理。但是,如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。

二、CGLIB动态代理

CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

根据上述,应该能了解两种动态代理的原理了吧,我再举个例子:

JDK动态代理:

目标类(实现类)可以认作为厨师张三,张三能够具体实现接口的所有功能,比如老板让张三做饭,当AOP代理的时候,会根据张三所实现的接口,再创造一个张三A(代理对象)作为张三的分身,这时候张三和张三A具有相同的接口(多态的体现),两者长得一模一样,这时候张三A就可以在张三做饭之前把菜给洗干净了,然后张三本人来做饭。。但是在老板(调用者)看来,自始至终都是张三(目标类)一个人在洗菜做饭。

但是张三(目标类)知道,在他动手做饭之前他的代理对象帮他做了一些事情,代理对象也可以在他做饭之后帮他洗碗等等。

所以目标类要是没有实现接口,程序就不能根据接口再实现一个代理对象,也就不能代替目标类(实现类)去做一些事情。

这种代理方式,只有在通过接口调用方法的时候才会有效!

CGLIB代理:

我们把目标类比作李刚(化名),代理的时候,程序会根据制造一个子类来继承目标类,那么这个子类就是代理对象(李刚的儿子),所以李刚的儿子就可以能替他爸收钱(因为他爸是李刚哈哈),因为多态,所以程序识别不出来,然后目标类再替人办事,在外人看来,就是李刚在收钱办事。但是李刚有很多特权他儿子是没权限的,也就是目标类中有final方法,子类是无法继承的,那么这个代理对象就不能代理这部分功能。

三、如何选择spring动态代理的方式

上一章讲了如何配置springAOP,在spring配置文件中,有个配置如下

<aop:aspectj-autoproxy proxy-target-class="true" />

proxy-target-class默认是false,也就是默认使用JDK动态代理,但是如果目标类没有实现接口,会自动转为CGLIB代理;

设置为true,说明使用CGLIB代理!

本次讲解完了,下一章我们讲一下AOP代理和spring事务的联系,以及很重要的同一service中不同方法调用事务失效的问题!!

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

毕生所求无它,爱与自由而已