参考
https://www.cnblogs.com/lcngu/p/5339555.html
https://blog.youkuaiyun.com/yuexianchang/article/details/77018603#t4
https://blog.youkuaiyun.com/luanlouis/article/details/51095702#t4
1 简介
说起AOP就不得不说下OOP了,OOP中引入封装、继承和多态性
等概念来建立一种对象层次结构
,用以模拟公共行为的一个集合。但是,如果我们需要为部分对象引入公共部分
的时候,OOP就会引入大量重复的代码
。例如:日志功能
。
AOP技术利用一种称为“横切”
的技术,解剖封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块
,这样就能减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理
的主要流程是核心关注点
,与之关系不大
的部分是横切关注点
。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。 比如权限认证、日志、事务处理
。
实现AOP的技术,主要分为两大类:一是采用动态代理
技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行
;二是采用静态织入
的方式,引入特定的语法创建“方面”
,从而使得JVM在运行时织入
有关“方面”的代码。
Spring AOP代理对象的生成(基于JDK代理)
Spring提供了两种方式来生成代理对象: JDKProxy
和Cglib
,具体使用哪种方式生成由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类型,它还可以细分为以下三种:After Returning Advice
:只有当前Joinpoint处执行流程正常完成
后,它才会执行;After throwing Advice
:在当前Joinpoint执行过程中抛出异常
的情况下会执行;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应该做什么样的工作:
-
确定自己对什么类的什么方法感兴趣?即
确定 AOP的切入点
(Point Cut),这个可以通过切入点(Point Cut)表达式来完成
; -
对应的的类的方法 的 执行特定时期 给出什么处理建议?------这个需要Spring AOP提供相应的建议 ,即我们常说的
Advice
。
7 Spring中的AOP底层实现原理:动态代理
转自
https://blog.youkuaiyun.com/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();
}
}