【Spring——AOP详解】
Spring ——AOP详解
jdk代理(基于接口):如果目标实现了接口,那么默认使用jdk代理。
cglib代理(基于类):如果目标没有实现接口,那么默认使用cglib代理。
Spring AOP概述
-
纵向抽取:
在Java的面向对象中,代码重复了,则1. 抽取成方法;2. 抽取类;其中抽取成类的方式称之为:纵向抽取——通过继承的方式实现纵向抽取
-
横向抽取:
抽取成类还是会出现重复的代码,因为这些(开始,结束,提交事务)依附在我们业务类的方法逻辑中!——反复造轮子
AOP的理念:就是将分散在各个业务逻辑代码中相同的代码通过横向切割的方式抽取到一个独立的模块中!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aGiGXqrd-1620052324452)(https://user-gold-cdn.xitu.io/2018/5/24/1639259ea3e1fbcf?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)]
这样接口只需要关心具体的业务,而不需要关注其他非该接口关注的逻辑或处理。
Spring AOP原理
底层原理就是动态代理。
代理可以帮我们增强对象的行为,动态代理实质上就是调用时拦截对象方法,对方法进行改造,增强!
在Java中动态代理有两种方式:
- JDK动态代理
- CGLib动态代理
JDK动态代理是需要实现某个接口了,而我们类未必全部会有接口,于是CGLib代理就有了~~
- CGLib代理其生成的动态代理对象是目标类的子类
- Spring AOP默认是使用JDK动态代理,如果代理的类没有接口则会使用CGLib代理。
——单例的最好使用CGLib,多例的最好使用JDK代理
原因:
- JDK在创建代理对象时的性能要高于CGLib代理,而生成代理对象的运行性能却比CGLib的低。
- 如果是单例的代理,推荐使用CGLib
Spring AOP(面向切面编程):将相同逻辑的重复代码横向抽取出来,使用动态代理技术将这些重复代码织入到目标对象方法中,实现和原来一样的功能。
AOP的术语
Aspect
(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
- 切面由切点和
增强/通知
组成,它既包括了横切逻辑的定义、也包括了连接点的定义。
Joint point
(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
- 能够被拦截的地方:Spring AOP是基于动态代理的,所以是方法拦截的。每个成员方法都可以称之为连接点~
Pointcut
(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
Advice
(增强):Advice 定义了在 Pointcut
里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
- 表示添加到切点的一段逻辑代码,并定位连接点的方位信息
- 简单来说就定义了是干什么的,具体是在哪干
- Spring AOP提供了5种Advice类型给我们:前置、后置、返回、异常、环绕给我们使用!
Target
(目标对象):织入 Advice
的目标对象.。
Weaving
(织入):将 Aspect
和其他对象连接起来, 并创建 Advice
d object 的过程
- 将
增强/通知
添加到目标类的具体连接点上的过程。
Introduction(引入/引介):
引入/引介
允许我们向现有的类添加新方法或属性。是一种特殊的增强!
下面我以一个简单的例子来比喻一下 AOP 中 Aspect
, Joint point
, Pointcut
与 Advice
之间的关系.
让我们来假设一下, 从前有一个叫爪哇的小县城, 在一个月黑风高的晚上, 这个县城中发生了命案. 作案的凶手十分狡猾, 现场没有留下什么有价值的线索. 不过万幸的是, 刚从隔壁回来的老王恰好在这时候无意中发现了凶手行凶的过程, 但是由于天色已晚, 加上凶手蒙着面, 老王并没有看清凶手的面目, 只知道凶手是个男性, 身高约七尺五寸. 爪哇县的县令根据老王的描述, 对守门的士兵下命令说: 凡是发现有身高七尺五寸的男性, 都要抓过来审问. 士兵当然不敢违背县令的命令, 只好把进出城的所有符合条件的人都抓了起来.
来让我们看一下上面的一个小故事和 AOP 到底有什么对应关系.
首先我们知道, 在 Spring AOP 中 Joint point
指代的是所有方法的执行点, 而 point cut 是一个描述信息, 它修饰的是 Joint point
, 通过 point cut, 我们就可以确定哪些 Joint point
可以被织入 Advice
. 对应到我们在上面举的例子, 我们可以做一个简单的类比, Joint point
就相当于 爪哇的小县城里的百姓,pointcut
就相当于 老王所做的指控, 即凶手是个男性, 身高约七尺五寸, 而 Advice
则是施加在符合老王所描述的嫌疑人的动作: 抓过来审问.
为什么可以这样类比呢?
-
Joint point
: 爪哇的小县城里的百姓: 因为根据定义,Joint point
是所有可能被织入Advice
的候选的点, 在 Spring AOP中, 则可以认为所有方法执行点都是Joint point
. 而在我们上面的例子中, 命案发生在小县城中, 按理说在此县城中的所有人都有可能是嫌疑人. -
Pointcut
:男性, 身高约七尺五寸: 我们知道, 所有的方法(joint point) 都可以织入Advice
, 但是我们并不希望在所有方法上都织入Advice
, 而Pointcut
的作用就是提供一组规则来匹配joinpoint, 给满足规则的 joinpoint 添加Advice
. 同理, 对于县令来说, 他再昏庸, 也知道不能把县城中的所有百姓都抓起来审问, 而是根据凶手是个男性, 身高约七尺五寸, 把符合条件的人抓起来. 在这里 凶手是个男性, 身高约七尺五寸 就是一个修饰谓语, 它限定了凶手的范围, 满足此修饰规则的百姓都是嫌疑人, 都需要抓起来审问. -
Advice
:抓过来审问,Advice
是一个动作, 即一段 Java 代码, 这段 Java 代码是作用于 point cut 所限定的那些Joint point
上的. 同理, 对比到我们的例子中, 抓过来审问 这个动作就是对作用于那些满足 男性, 身高约七尺五寸 的爪哇的小县城里的百姓. -
Aspect
::Aspect
是 point cut 与Advice
的组合, 因此在这里我们就可以类比: “根据老王的线索, 凡是发现有身高七尺五寸的男性, 都要抓过来审问” 这一整个动作可以被认为是一个Aspect
.最后是一个描述这些概念之间关系的图:
Spring对AOP的支持
Spring提供了3种类型的AOP支持:
-
基于代理的经典SpringAOP
- 需要实现接口,手动创建代理
-
纯POJO切面
- 使用XML配置,aop命名空间
-
@AspectJ
注解驱动的切面 -
使用注解的方式,这是最简洁和最方便的!
Spring AOP——动态代理实现AOP
动态代理的原理
动态创建一组指定的接口的实现对象(在运行时,创建实现了指定的一组接口的对象)
interface A {}
interface B {}
//obj对象的类型实现了A和B两个接口
Object obj = 方法(new Class[]{A.class, B.class})
先写两个接口
interface A {
public void a();
}
interface B {
public void b();
}
动态代理创建一个代理对象:
发现A接口和B接口的实现逻辑都是调用了invoke
这个方法中的逻辑,其实除了调用代理对象的native
方法,调用代理对象的其他所有方法本质都是调用了invoke
方法,
public void test3() {
/**
* 三个参数
* 1、ClassLoader
* 方法需要动态生成一个类,这个类实现了A和B两个接口,然后创建这个类的对象
* 需要生成一个类,这个类也需要加载到方法区中,所以我们需要一个ClassLoader来加载该类
*
* 2、Class[] interfaces
* 我们需要代理对象实现的数组
*
* 3、InvocationHandler
* 调用处理器
*
* 代理对象实现的所有接口中的方法,内容都是调用InvocationHandler的invoke()方法
*/
ClassLoader classLoader = this.getClass().getClassLoader();
//这里创建一个空实现的调用处理器。
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("你好!!!!");
return "Hello";//这里改为返回"Hello"
}
};
Object obj = Proxy.newProxyInstance(classLoader, new Class[]{A.class, B.class}, invocationHandler);
//强转为A和B接口类型,说明生成的代理对象实现了A和B接口
A a = (A) obj;
B b = (B) obj;
a.toString();//注意这里调用了toString()方法
b.getClass();//注意这里调用了getClass()方法
//这里在A接口中添加了一个方法public Object aaa(String s1, int i);
Object hello = a.aaa("Hello", 100);
System.out.println(obj.getClass());//这里看一下代理对象是什么
System.out.println(hello);//这里看一下返回值是什么
}
Spring AOP的实现
先写一个接口
//服务生
public interface Waiter {
//服务方法
public void server();
}
给出该接口的实现类:
public class ManWaiter implements Waiter {
@Override
public void server() {
System.out.println("服务中");
}
}
我们就通过动态代理来对上面的ManWaiter
进行增强:
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Demo2 {
@Test
public void test1() {
Waiter waiter = new ManWaiter();
waiter.server();
}
@Test
public void test2() {
Waiter manWaiter = new ManWaiter();
ClassLoader classLoader = this.getClass().getClassLoader();
Class[] interfaces = {Waiter.class};
InvocationHandler invocationHandler = new WaiterInvocationHandler(manWaiter);
//得到代理对象,代理对象就是在目标对象的基础上进行了增强的对象
Waiter waiter = (Waiter) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
waiter.server();//前面添加“你好”,后面添加“再见”
}
}
class WaiterInvocationHandler implements InvocationHandler {
private Waiter waiter;
WaiterInvocationHandler(Waiter waiter) {
this.waiter = waiter;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("你好");
waiter.server();//调用目标对象的方法
System.out.println("再见");
return null;
}
}
增强的代码都是硬编码到invoke
方法中的,目标对象为manWaiter
,增强为System.out.println("你好");
和System.out.println("再见");
,切点为server()
方法调用。
完善的AOP实现:
我们仍然引用上面的Waiter
接口和Manwaiter
实现类。
添加一个前置增强接口:
/**
* 前置增强
*/
public interface BeforeAdvice {
public void before();
}
添加一个后置增强接口:
public interface AfterAdvice {
public void after();
}
们把产生代理对象的代码封装为一个类:
import com.sun.org.apache.regexp.internal.RE;
import org.junit.After;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* ProxFactory用来生成代理对象
* 它需要所有的参数:目标对象,增强,
*/
/**
* 1、创建代理工厂
* 2、给工厂设置目标对象、前置增强、后置增强
* 3、调用creatProxy()得到代理对象
* 4、执行代理对象方法时,先执行前置增强,然后是目标方法,最后是后置增强
*/
//其实在Spring中的AOP的动态代理实现的一个织入器也是叫做ProxyFactory
public class ProxyFactory {
private Object targetObject;//目标对象
private BeforeAdvice beforeAdvice;//前值增强
private AfterAdvice afterAdvice;//后置增强
/**
* 用来生成代理对象
* @return
*/
public Object creatProxy() {
/**
* 给出三个参数
*/
ClassLoader classLoader = this.getClass().getClassLoader();
//获取当前类型所实现的所有接口类型
Class[] interfaces = targetObject.getClass().getInterfaces();
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 在调用代理对象的方法时,会执行这里的内容
*/
if(beforeAdvice != null) {
beforeAdvice.before();
}
Object result = method.invoke(targetObject, args);//调用目标对象的目标方法
//执行后续增强
afterAdvice.after();
//返回目标对象的返回值
return result;
}
};
/**
* 2、得到代理对象
*/
Object proxyObject = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxyObject;
}
//get和set方法略
}
我们将相关的参数注入到ProxyFactory
后就可以通过creatProxy()
方法获取代理对象了,代码如下:
import org.junit.Test;
public class Demo3 {
@Test
public void tset1() {
ProxyFactory proxyFactory = new ProxyFactory();//创建工厂
proxyFactory.setTargetObject(new ManWaiter());//设置目标对象
//设置前置增强
proxyFactory.setBeforeAdvice(new BeforeAdvice() {
@Override
public void before() {
System.out.println("客户你好");
}
});
//设置后置增强
proxyFactory.setAfterAdvice(new AfterAdvice() {
@Override
public void after() {
System.out.println("客户再见");
}
});
Waiter waiter = (Waiter) proxyFactory.creatProxy();
waiter.server();
}
}
其中,Spring
Spring AOP——CGLib实现AOP
CGLib实现代理的原理
GLIB实现目标类增强的原理是这样的:CGLib会动态创建一个目标类的子类,然后返回该子类的对象,也就是增强对象,至于增强的逻辑则是在子类中完成的。
先创建 一个目标对象:
public class SomeService {
public String doFirst() {
System.out.println("执行doFirst()方法");
return "abcde";
}
public void doSecond() {
System.out.println("doSecond()方法");
}
}
目标子类 = 目标类 + 增强逻辑
GLIB底层是如何动态的生成一个目标类的子类?它是使用动态字节码技术,我们知道我们编写的Java对象都是先编译为.class
文件,然后由类加载器加载到内存中变为一个Java对象的,动态字节码技术就是通过转换字节码生成新的类来实现改变一个类的内部逻辑的。——Java反射机制
通过CGLIB代理实现如下:
- 首先实现一个
MethodInterceptor
,方法调用会被转发到该类的intercept()
方法。 - 然后在需要使用
SomeService
(目标对象)的时候,通过CGLIB动态代理获取代理对象。
我们仍然使用上面的SomeService
作为目标对象,然后我们实现一个MethodInterceptor
,在实现MethodInterceptor
之前,我们先看一下这个接口是什么:
public interface MethodInterceptor extends Callback {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable;
}
- Object为由CGLib动态生成的代理类实例
- Method为上文中实体类所调用的被代理的方法引用
- Object[]为参数值列表
- MethodProxy为生成的代理类对方法的代理引用。
下面是实现的一个MethodInterceptor
,同时写了创建增强对象的逻辑即myCglibCreator()
方法,该方法返回增强对象。
public class CglibFactory implements MethodInterceptor {
public SomeService myCglibCreator() {
Enhancer enhancer = new Enhancer();
//将目标类设置为父类,cglib动态代理增强的原理就是子类增强父类,cglib不能增强目标类为final的类
//因为final类不能有子类
enhancer.setSuperclass(SomeService.class);
//设置回调接口,这里的MethodInterceptor实现类回调接口,而我们又实现了MethodInterceptor,其实
//这里的回调接口就是本类对象,调用的方法其实就是intercept()方法
enhancer.setCallback(this);
//create()方法用于创建cglib动态代理对象
return (SomeService) enhancer.create();
}
//回调接口的方法
//回调接口的方法执行的条件是:代理对象执行目标方法时会调用回调接口的方法
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = methodProxy.invokeSuper(o, objects);
//这里实现将返回值字符串变为大写的逻辑
if(result != null) {
result = ((String) result).toUpperCase();
}
return result;
}
}
其中,Object result = methodProxy.invokeSuper(o, objects);
调用代理类实例上的methodProxy
方法的父类方法(即实体类SomeService
中对应的方法),然后返回目标方法的返回值result
,然后实现增强的逻辑,将返回的字符串变为大写的。下面是测试代码:
public class Test {
public static void main(String[] args) {
SomeService target = new SomeService();
SomeService proxy = new CglibFactory(target).myCglibCreator();
String result = proxy.doFirst();
System.out.println(result);
proxy.doSecond();
}
}
我们通过CGLIB
的Enhancer
来指定要代理的目标对象、实际处理代理逻辑的对象,最终通过调用create()
方法得到代理对象,对这个对象所有非final
方法的调用都会转发给MethodInterceptor.intercept()
方法,在intercept()
方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;通过调用MethodProxy.invokeSuper()
方法,我们将调用转发给原始对象,具体到本例,就是SomeService
的具体方法。CGLIG
中MethodInterceptor
的作用跟JDK动态代理代理中的InvocationHandler
很类似,都是方法调用的中转站。
总结
Spring AOP中两种AOP的实现机制,我们可以称JDK动态代理实现的AOP为面向接口的动态增强
,将CGLIB实现的AOP称为面向子类的动态增强
。
https://www.jianshu.com/p/aaeb2355ec5c
https://www.jianshu.com/p/7cc8ffe4372b
https://www.jianshu.com/p/5837230e10d5