Spring AOP的实现原理

参考
https://www.cnblogs.com/lcngu/p/5339555.html
https://blog.csdn.net/yuexianchang/article/details/77018603#t4
https://blog.csdn.net/luanlouis/article/details/51095702#t4

1 简介

说起AOP就不得不说下OOP了,OOP中引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。但是,如果我们需要为部分对象引入公共部分的时候,OOP就会引入大量重复的代码。例如:日志功能

AOP技术利用一种称为“横切”的技术,解剖封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,这样就能减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP把软件系统分为两个部分核心关注点横切关注点业务处理的主要流程是核心关注点与之关系不大的部分是横切关注点横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。 比如权限认证、日志、事务处理

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

Spring AOP代理对象的生成(基于JDK代理)

Spring提供了两种方式来生成代理对象: JDKProxyCglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术否则使用Cglib来生成代理

2 AOP的应用场景

  • 什么时候该用AOP:
    解决重复代码问题,可以通过封装日志逻辑为一个类,然后在各个模块需要的地方通过该类来试下日志功能,但是还是不能解决影响模块封装性的问题
    那么AOP就可以解决,它使用切面动态地织入到各模块中(实际就是使用代理来管理模块对象),这样既解决了重复代码问题又不会影响模块的封装性

  • AOP用来封装横切关注点使用场景:
    Authentication 权限
    Caching 缓存
    Error handling 错误处理
    logging 日志
    Transactions 事务

3 AOP相关概念

  • 切面(Aspect):散落在系统各处的通用的业务逻辑代码,如日志模块,权限模块,事务模块等,切面用来装载pointcut切入点和advice通知。切面用spring的 Advisor拦截器实现

  • 连接点(Joinpoint): 系统运行前,AOP的功能模块需要织入到OOP的功能模块中,连接点就是将要织入AOP功能模块的点,如方法调用时处理异常时。在Spring AOP中,一个连接点总是表示一个方法的执行。常见的几种类型的JoinPoint
    Ø 方法调用:当某个方法被调用的时候所处的程序执行点;
    Ø 方法执行:该类型表示的是某个方法内部执行开始时的点,应该与方法调用相区分;
    Ø 构造方法调用:程序执行过程中对某个对象调用其构造方法进行初始化时的点;
    Ø 构造方法执行:它与构造方法调用关系如同方法调用与方法执行间的关系;
    Ø 字段设置:对象的某个属性通过setter方法被设置或直接被设置的执行点;
    Ø 字段获取:某个对象相应属性被访问的执行点;
    Ø 异常处理执行:某些类型异常抛出后,对应的异常处理逻辑执行点;
    Ø 类初始化:类中某些静态类型或静态块的初始化时的执行点。

  • 通知(Advice): 是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了几类advice:
    Ø Before Advice:在Joinpoint指定位置之前执行的Advice类型,可以采用它来做一些系统的初始化工作,如设置系统初始值,获取必要系统资源等。
    Ø After Advice:在相应连接点之后执行的Advice类型,它还可以细分为以下三种:

    1. After Returning Advice:只有当前Joinpoint处执行流程正常完成后,它才会执行;
    2. After throwing Advice:在当前Joinpoint执行过程中抛出异常的情况下会执行;
    3. After Advice:该类型的Advice不管JoinPoint处执行流程是正常还是抛出异常都会执行。

    Ø Around Advice:对附加其上的Joinpoint进行包裹,可以在joinpoint之前之后都指定相应的逻辑,甚至中断或忽略joinpoint处原来程序流程的执行。

  • 切入点(Pointcut): Pointcut是JoinPoint的表述方式。在将横切逻辑织入当前系统的过程中,虽然知道需要在哪些功能点上织入AOP的功能模块,但需要一种表达方法。Pointcut和一个切入点表达式关联,并在满足这个切入点的Joinpoint上运行。目前通常使用的Pointcut方式有以下几种:
    Ø 直接指定Joinpoint所在的方法名称
    Ø 正则表达式,Spring的AOP支持该种方式;
    Ø 使用特定的Pointcut表述语言,Spring 2.0后支持该方式

  • 目标对象(Target Object): 符合Pointcut 切入点所指定的条件,将在织入过程中,被织入横切逻辑的对象,也被称作被通知或被代理对象

  • AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理

  • 织入(Weaving): 通过切入点切入,将切面应用到目标对象,并导致代理对象创建的过程。这可以在编译时 或 运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。ProxyFactory类是Spring AOP最通用的织入器。

4 如何标识切入点

因为切面本质上是每一个方法调用,选择切面的过程实际上就是选择方法的过程。切入点实际上也是从所有的连接点(Join point)挑选自己感兴趣的连接点的过程。
在这里插入图片描述
既然AOP是对方法调用进行的编程,那么,AOP如何捕获方法调用的呢? 弄清楚这个问题,你不得不了解设计模式中的代理模式了。下面我们先来了解一下引入了代理模式的Java程序执行流是什么样子的。

5 引入了代理模式的Java程序执行流

我们假设在我们的Java代码里,都为实例对象通过代理模式创建了代理对象,访问这些实例对象必须要通过代理,那么,加入了proxy对象的Java程序执行流会变得稍微复杂起来。
我们来看下加入了proxy对象后,Java程序执行流的示意图
在这里插入图片描述
由上图可以看出,只要想调用某一个实例对象的方法时,都会经过这个实例对象相对应的代理对象即执行的控制权先交给代理对象
在这里插入图片描述我整理的哦~ java的动态代理机制详解
别人的哦~ Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)

加入了代理模式的Java程序执行流,使得所有的方法调用都经过了代理对象。对于Spring AOP框架而言,它负责控制着整个容器内部的代理对象。当我们调用了某一个实例对象的任何一个非final的public方法时,整个Spring框架都会知晓。既然Spring代理层可以察觉到你所做的每一次对实例对象的方法调用,那么,Spring就有机会在这个代理的过程中插入Spring的自己的业务代码

6 advice

前面已经介绍了AOP编程首先要选择它感兴趣的连接点----即切入点(Point cut),那么,AOP能对切入点做什么样的编程呢? 我们先将代理模式下的某个连接点细化,你会看到如下这个示意图所表示的过程:
在这里插入图片描述上述的示意图中已经明确表明了Spring AOP应该做什么样的工作:

  1. 确定自己对什么类的什么方法感兴趣?即确定 AOP的切入点(Point Cut),这个可以通过切入点(Point Cut)表达式来完成

  2. 对应的的类的方法 的 执行特定时期 给出什么处理建议?------这个需要Spring AOP提供相应的建议 ,即我们常说的Advice

在这里插入图片描述

7 Spring中的AOP底层实现原理:动态代理

转自
https://blog.csdn.net/qq_24693837/article/details/54909477

动态代理就是,在不修改原有类对象方法的源代码基础上,通过代理对象实现原有类对象 方法的增强也就是 拓展原有类对象的 功能

■ JDK动态代理中包含一个类和一个接口:

★ InvocationHandler接口:

public interface InvocationHandler { 
	public Object invoke(Object proxy,Method method,Object[] args) throws Throwable; 
} 

参数说明:

proxy:  指代我们所代理的那个真实对象
method:  指代的是我们所要调用真实对象的某个方法的Method对象
args:   指代的是调用真实对象某个方法时接受的参数

可以将InvocationHandler接口的子类想象成一个代理的最终操作类

★ Proxy类:
Proxy类是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:

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

参数说明:

ClassLoader loader:类加载器 
Class<?>[] interfaces:得到全部的接口 
InvocationHandler h:得到InvocationHandler接口的子类实例 

Ps:类加载器
在Proxy类中的newProxyInstance()方法中需要一个ClassLoader类的实例,ClassLoader实际上对应的是类加载器,在Java中主要有一下三种类加载器:Booststrap ClassLoader:此加载器采用C++编写,一般开发中是看不到的; Extendsion ClassLoader:用来进行扩展类的加载,一般对应的是jre\lib\ext目录中的类; AppClassLoader:(默认)加载classpath指定的类,是最常使用的是一种加载器

动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性因为Java 反射机制可以生成任意类型的动态代理类

java.lang.reflect 包中的Proxy类InvocationHandler 接口提供了生成动态代理类的能力。

例子代码:

1.先创建一个接口:

package com.ls.reflect.demo;
 
public interface StudentDao {
	public abstract void login();
	public abstract void regist();
}

2.再创建一个接口实现类:

package com.ls.reflect.demo;
 
public class StudentDaoImpl implements StudentDao {
 
	public void login() {
		System.out.println("登录");
	}
 
	public void regist() {
		System.out.println("注册");
	}
 
}

3.实现InvocationHandler 接口

package com.ls.reflect.demo;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
 
public class MyInvocationHandler implements InvocationHandler {
	private Object target;
	public Object bind(Object target){
        //绑定一个委托对象,其实就是接口实现对象
		this.target=target;
		//返回一个代理对象
		return Proxy.newProxyInstance
				(target.getClass().getClassLoader(), 
						target.getClass().getInterfaces(), this);
	}
    //这里是最关键的部分,动态代理,实现方法增强
	public Object invoke(Object proxy, Method met, Object[] arg2)
			throws Throwable {
		System.out.println("权限检查");
		Object result=met.invoke(target, arg2);
		System.out.println("日志记录");
		return result;
	}
 
}

4.测试代码:

package com.ls.reflect.demo;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
 
public class StudentDemo {
	public static void main(String[] args) {
	StudentDao sd=new StudentDaoImpl();
	sd.login();
	sd.regist();
	System.out.println("----------");
	
	MyInvocationHandler handler=new MyInvocationHandler();
	StudentDao proxy=(StudentDao)handler.bind(sd);
	proxy.login();
	proxy.regist();
 }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值