面向切面编程
面向切面编程是怎么来的?
假设我们有个加减乘除的计算器程序,这个程序的核心代码就是加减乘除功能的实现。
在传统面向对象的过程中,我们只需要一个接口,一个实现类就可以完成这个程序。
但如果我们需要在计算器的基础上添加日志功能,在计算过程中添加一些打印的日志。我们需要在各个方法中都添加打印代码。这部分日志代码属于非核心业务。它会影响和干扰我们后续的维护和封装。
这部分非核心业务我们无法抽象并抽取出来。
那我们需要怎么办呢。我们还是得把非核心代码抽取出来。这个过程,叫做解耦。主要方法里只放核心代码。抽取出来的代码要套到我们的核心代码上。
这对我们的核心代码是没有影响的。但困难在我们抽取的内容是在代码的内部。我们用老方法,把子类的代码放到父类中没法解决。
面向对象是一种纵向继承机制。只能把一段连续执行的代码进行封装。但不能说对一段代码 只抽取其中分散的几行进行封装。
我们调用的过程是把一段代码的全部内容都执行一遍。
我们可以通过代理模式来处理这个问题,属于结构型模式。我们通过代理对象来访问目标对象。
秘书、经纪人、中介是一种代理模式。代理中间可以添加额外的操作。
前置通知、后置通知、异常通知、返回通知。代理模式无法在核心代码中间插入额外的操作。
静态代理实现了一些解耦,但静态代理的代码是写死的,一实现了相同的接口,二对应的目标对象也是写死的,代理的实现过程也是写死的,完全不具备任何的灵活性。
动态代理,指的是动态的创建目标的代理类。这个过程需要JDK和JVM来实现,因为是在执行过程中创建的。
JDK的动态代理在反射的包下,java.lang.reflect下的。所以是通过反射来实现的动态代理。
根类加载器、扩展类加载器、应用类加载器、自定义类加载器。
动态代理同样是四个位置插入额外的操作。
异常通知包括触发异常和结束的位置。
动态代理分为两种,一种叫JDK动态代理,还有叫做cglib动态代理。
JDK动态代理必须有接口,目标代码必须有接口,最终生成的代理类在com.sun.proxy下。类名叫proxy2
cglib不要求必须有接口,最终生成的代理类和目标类实现相同的接口。生成的代理类会继承目标类。并且和目标类在相同的包下。
如果需要扫描包,需要用cglib。
AOP的底层是由动态代理实现的。动态代理是通过反射实现的。
AOP是对OOP的补充和完善。事务也是一样的道理。无法抽取事务的代码,具有原子性。
AOP是横向抽取机制。可以抽取非核心业务代码。我们抽取的代码需要交给一个切面类来管理。而且不只是抽取这么简单,还要放到真正实现的位置。
对于AOP来说,我们既要抽取目标类中的代码,还要在目标类执行的时候套回到原来的位置。
在不修改源代码的基础下,拓展功能。
横切关注点,就是目标类中抽取出来的非核心业务代码。一个方法中可以有多个横切关注点。
把横切关注点抽取出来之后放到一个切面类中进行封装。
通知分为五种,前置通知、返回通知、异常通知、后置通知、环绕通知。
切面,切面是用来封装横切关注点的。一个横切关注点,就表明一个横切方法。封装横切关注点的类就叫切面;封装通知方法的类就叫切面。
切面和切面中的通知重要。
在AOP中我们不需要手动创建代理对象。横切关注点封装到一个方法里,这个方法叫做通知。
连接点,横切关注点是从哪里抽取出来的。比如执行之前,异常的时候,返回的时候。还是final的时候。连接点是抽取横切关注点的位置。为什么要有连接点,因为我们抽取出来之后还要放回去,套回去。这是一个逻辑概念,就像是东南西北。
切入点,切入点是一个表达式。这是一个具体的表达式,是一个定位。表示经纬线。用切入点来定位连接点。
有了目标对象才需要AOP进行功能增强。
对于AOP需要做的事情是,从我们的目标对象中把非核心业务代码抽取出来,也就是抽取横切关注点。抽取出来之后要放在一个类中,这个类叫做切面。在切面中封装横切关注点都是一个方法,这个方法叫做通知。横切关注点抽取的位置叫连接点,如何定位连接点?要用切入点定位连接点。
如果把AOP比作抽木条的游戏,抽取非核心业务代码就是抽木条。但我们不能只抽不放,我们抽出来同样需要放回去。
AOP是一种思想,AspctJ是一种具体实现方式,再具体的实现层是JDK动态代理或cglib动态代理。AspctJ本质上的静态代理,因为不会动态生成代理类。而是把代理逻辑植入到被代理目标的字节码文件中。本质是静态代理,但实现的是动态代理。weaver是植入器。Spring只是借用了AspctJ中的注解。
创建切面、创建通知、通过切入点表达式作用到连接点。