Spring的左膀是IoC,右臂是AOP。


一、AOP的定义

AOP(Aspect-Oriented Programming,面向切面编程)中,“切面”是个关键词。什么是切面?


我们知道,Java语言是面向对象的,是OOP(Object-Oriented Programing,面向对象编程)OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。


而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即切面。所谓“切面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。


使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”

Spring的右臂:AOP-大魏Java记11_java

实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用拦截方法的方式,对该方法进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“切面”,从而使得编译器可以在编译期间织入有关“切面”的代码。


二、AOP框架的实现

目前最流行的AOP框架有两类:Spring AOP(使用纯Java实现)和AspectJ(扩展了Java语言,提供了一个专门的编译器,在编译的时候提供横向代码的切入)。

AOP包含以下几个专业术语:

Spring的右臂:AOP-大魏Java记11_java_02

Spring的右臂:AOP-大魏Java记11_java_03


AOP的实现方式整体目录:

  1. Spring APO:

    1.1 手动代理

      1.1.1 JDK动态代理

      1.1.2 CGLIB

      1.2 声明工厂Bean

   2. AspectJ

      2.1 基于XML声明  AspectJ

      2.2 基于Annotation声明 AspectJ


三、手动代理

代理模式是Java中常用的设计模式,代理类通过调用被代理类的相关方法,提供预处理、过滤、事后处理等服务。AOP使用手动代理有两个典型的例子:

  • 1.JDK动态代理

  • 2.CGLIB代理



3.1、JDK动态代理

JDK动态代理是通过JDK中的java.lang.reflect.Proxy类来实现的。我们看一个JDK动态代理的具体实现步骤:

首先创建一个名为UserDao的接口类,接口类中定义了:增删查存四个方法。

Spring的右臂:AOP-大魏Java记11_java_04

接口实现类UserDaoImpl对UserDao接口进行了实现,分别实现了save(),update(),delete()方法。


Spring的右臂:AOP-大魏Java记11_java_05

Spring的右臂:AOP-大魏Java记11_java_06

上上图中,我们看到了@Override

@Override是伪代码,表示重写(当然不写也可以),不过写上有如下好处:


1、可以当注释用,方便阅读;

2、编译器可以给你验证@Override下面的方法名是否是你父类中所有的,如果没有则报错。例如,你如果没写@Override,而你下面的方法名又写错了,这时你的编译器是可以编译通过的,因为编译器以为这个方法是你的子类中自己增加的方法。


新创建一个包,然后创建一个切面类:MyAspect。

类中有两个增强的方法:myBefore()、myAfter(),它们主要是通知(Advice)的内容。

Spring的右臂:AOP-大魏Java记11_java_07

接下来,创建MybeanFactory类,在该类中通过Proxy实现动态代理。

该类代码中,标红部分是对UserDaoImpl()和new MyAspect()进行实例化。

该类代码中,标黄部分是返回Proxy.newProxyInstance。


Proxy.newProxyInstance()方法有三个参数:

1. 类加载器(Class Loader)

2. 需要实现的接口数组

3. InvocationHandler接口。所有动态代理类的方法调用,都会交由InvocationHandler接口实现类里的invoke()方法去处理。这是动态代理的关键所在。


我们看到invoke()调用的是MyAspect的两个方法,即分别打印“方法执行前”

“方法执行后”


package cn.itcast.jdk;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import cn.itcast.dao.UserDao;

import cn.itcast.dao.UserDaoImpl;

public class MyBeanFactory {

public static UserDao getBean(){

final UserDao userDao = new UserDaoImpl();

final MyAspect myAspect = new MyAspect();

return (UserDao)(

MyBeanFactory.class.getClassLoader(),

new Class[]{UserDao.class}, 

new InvocationHandler() {

@Override

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

//增强前

myAspect.myBefore();

//目标类的方式

Object obj = method.invoke(userDao, args);

//增强后

myAspect.myAfter();

return obj;

}

});

}


书写测试类:

Spring的右臂:AOP-大魏Java记11_java_08

测试类运行结果:

Spring的右臂:AOP-大魏Java记11_java_09

我们看得到了预想的结果,即实现了方法的增强。



3.2、CGLIB代理

JDK的动态代理使用起来很方便,但他的局限性在于使用对象代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,可以使用CGLIB。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。


CGLIB的全称是:Code Generation Library,它是一个高性能开源的代码生成包,它的底层通过使用ASM来转换字节码,为一个类创建子类,然后对子类进行增强,解决无接口的问题。


ASM在spring-core*.jar中。

# jar -xvf spring-core-5.3.1.jar

上图中,我们看到asm中包含很多类文件。


我们通过一个代码展示CGLIB代理。

首先创建一个BookDao.java类,里面包含update、delete、find方法。每个方法打印不同的字段。

Spring的右臂:AOP-大魏Java记11_java_10

我们在另外一个包里创建MyBeanFactory类。

CGLIB模式下,Enhancer是核心类。通过它进行类增强,并增强回调函数。

Spring的右臂:AOP-大魏Java记11_java_11

Spring的右臂:AOP-大魏Java记11_java_12

上图中16步是调用的cglib类库中的如下类:

[root@repo proxy]# ls -ak /spring-framework-5.3.1/libs/org/springframework/cglib/proxy/Enhancer.class

/spring-framework-5.3.1/libs/org/springframework/cglib/proxy/Enhancer.class


接下来,创建测试类:

Spring的右臂:AOP-大魏Java记11_java_13

运行测试类,结果与我们期望相同:

Spring的右臂:AOP-大魏Java记11_java_14


四、声明式工程Bean

通知(Advice)就是对其目标切入点增强的内容。AOP联盟为Advice定义了org.aopalliance.aop.Advice接口,Spring通知按照在目标类方法的连接点位置,可以分为5种类型。


我们看下图列出的5种Advice接口类型,他们的通知位置和作用都有所区别。

Spring的右臂:AOP-大魏Java记11_java_15


在Spring中创建一个AOP代理的基本方法是:使用org.springframework.aop.framework.ProxyFactoryBean这个类对应的切入点和通知提供了完整的控制能力、生成指定的内容。      


ProxyFactoryBean这个类常用的可配置属性如下图所示:   

Spring的右臂:AOP-大魏Java记11_java_16

从上图我们可以ProxyFactoryBean的具体实现,要么通过JDK动态代理(有接口类的时候),要么通过CGLIB(没有接口类的时候)。                                                                             


ProxyFactoryBean依赖如下jar包:

  • spring-aop-*.jar:这个在Spring的包中已经提供:spring-aop-5.3.1.jar

Spring的右臂:AOP-大魏Java记11_java_17

  • com.springsource.org.aopalliance*jar

这个包在网上可以直接搜到。


接下来,我们看代码。

我们接着上一小节的实验,创建切面类MyAspect。

我们看到切面类实现了MethodInterceptor接口,并实现了该接口的invoke方法。

Spring的右臂:AOP-大魏Java记11_java_18

在Spring Aop框架中,MethodInterceptor接口被用来拦截指定的方法,对方法进行增强。

Spring的右臂:AOP-大魏Java记11_java_19



接下来,书写应用上下文配置文件。

  • 需要注意的是,下图11-12行,是直接对一个类进行实现方式的注释,即将ProxyFactoryBean注释为Bean ID为:userDaoProxy。这是创建代理的核心配置。

  • 第14-15行标记标记的是要实现哪个接口,即UserDao(接口的内容见3.1,包含4个方法)。

  • 第17-18行表示的是要代理的目标,是userDao

  • 第19行表示的是要植入的Advice,即名为myAspect的切面类。

  • 第21行表示使用jdk动态代理。

Spring的右臂:AOP-大魏Java记11_java_20

接下来,创建测试类TestFactoryBean。

测试类中有一个名为demo01的方法。这个方法从Spring中获取到UserDaoProxy的实例---->对应上图的第11行--->代理UerDao接口--->执行切面类--->执行切面类:MyAspect中的内容。


Spring的右臂:AOP-大魏Java记11_java_21

Spring的右臂:AOP-大魏Java记11_java_22

执行结果如下:

Spring的右臂:AOP-大魏Java记11_java_23

结果符合我们的预期。


五、AspectJ开发

新版本的Spring,建议使用AspectJ来开发AOP。

AspectJ中又包含两种实现方式:

  • 5.1 基于XML声明式的AspectJ

  • 5.2 基于Annoitation的声明式AspectJ。


这两种实现方式中,第二种用的更多。因为第一种方式需要在XML中需要配置大量的信息。第一种方式我们不展开介绍。


Annotation的注解如下:

Spring的右臂:AOP-大魏Java记11_java_24


接下来,我们看代码。

首先定义一个切面类。

下图16行@Aspect用于声明这是一个切面类,该类作为组件使用,因此必须在第17行添加@Component才能生效。

22行@Pointcut注解用来配置切入点,然后在每个通知相应的的方法上添加注解声明,并且将切入点方法名"myPointCut“作为参数传递,指定要在哪个切入点执行这个方法。

Spring的右臂:AOP-大魏Java记11_java_25

Spring的右臂:AOP-大魏Java记11_java_26

Spring的右臂:AOP-大魏Java记11_java_27

Spring的右臂:AOP-大魏Java记11_java_28

Spring的右臂:AOP-大魏Java记11_java_29

在目标类UserDaoImpl中添加注解@Repository(),将其标识为Spring中的Bean。


书写配置文件如下。

13、14行添加扫描包,使注解生效。

16行开启切面的自动代理。

Spring的右臂:AOP-大魏Java记11_java_30

书写测试类如下:

  • 8行@RunWith注解标识这个是一个JUnit4的测试程序

  • @Autowired注解将UserDao接口的实现类对象类注入到该测试类中,最后调用save()方法进行测试。

Spring的右臂:AOP-大魏Java记11_java_31

Spring的右臂:AOP-大魏Java记11_java_32

执行结果如下:

Spring的右臂:AOP-大魏Java记11_java_33