**
由动态代理浅析SpringAOP和声明式事务
**
动态代理顾名思义就是代理呗!,有人说,那你不跟没讲一样,其实就类似于中介一样,比如,你要卖房子,给中介代理,也就是说它要怎么宣传怎么吹牛逼,什么添油加醋都是中介干的事,但是你的房子是不变的。同理,动态代理就是不改变原有的(房子)代码的基础上,进行扩展,增强。
java动态代理的方式主要由两种
1.基于jdk环境的Proxy生成一个动态代理对象(代理对象只能代理实现接口的方式)
2.第三方cglib生成代理对象(代理对象可以实现基于类的)
下面我们来浅析下jdk实现代理对象
下面有个接口,实现类,非常简单
package com.lzh.staticProxy;
public interface Vehicle {
void run();
}
public class Car implements Vehicle{
@Override
public void run() {
System.out.println("小汽车开始运行了");
}
}
再写一个能代理Vehicle接口实现类的工具类,提供一个方法去返回被代理类的代理对象,写个非常简单的demo
public class VehicleProxyProvided implements InvocationHandler{
private Vehicle vehicle;
public Vehicle bind(Vehicle vehicle){
this.vehicle = vehicle;
//这里就是重点通过反射包的Proxy,返回一个代理对象
//这里就是要传三个参数,直接要什么参数就提供什么参数
return (Vehicle)Proxy.newProxyInstance(vehicle.getClass().getClassLoader(),
vehicle.getClass().getInterfaces()
,this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("交通工具开始运行");
Object invoke = method.invoke(vehicle, args);
System.out.println("交通工具停止运行");
return invoke;
}
}
有小伙伴可能一脸懵逼,它是怎么获得的代理对象,直接进入源码,就晓得了
提示一下上面的Proxy的第三个参数是一个接口类型InvocationHandler,直接打开该接口,定义了一个invoke方法,由于是接口就没办法new了,那就想肯定传进去的是该接口的实现类,如上代码 VehicleProxyProvided implements InvocationHandler 说明对该接口的方法进行实现,this传进去就完事。
进入源码:直接看重点
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
//源码翻译的很明白查找或生成指定的代理的Class类,是由asm技术动态的生成Class类。这里怎么动态
//生成的Class并加载到内存,装载到JVM有点复杂,有兴趣可以自己往下追
//那拿到Class创建个对象不是轻轻松松嘛,直接底层反射创建对象
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
/*通过反射
拿到参数构造器,追进去源码看他的构造器的参数类型是啥
private static final Class<?>[] constructorParams ={ InvocationHandler.class };
这参数类型不就是第三个传的参数类型嘛,说明它生成的代理类一定有个构造器是带这种类型
*/
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
/**
这里直接进行了把那个第三个传进去的参数,并且创建了个实例,进行返回,也就是得到了代理对象
**/
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
可能还有小伙伴懵逼怎么就生成了代理对象,由于代理类直接加载到内存里,但是我们可以通过流输入该class文件,
当我们看到该Class类的源文件就明白通透了
如下:
public class Test {
public static void main(String[] args) {
//只要加上这句话就能输出代理类对象的Class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
VehicleProxyProvided vehicleProxyProvided = new VehicleProxyProvided();
Vehicle bind = vehicleProxyProvided.bind(new Car());
bind.run();
}
}
输出结果如下:
交通工具开始运行
小汽车开始运行了
交通工具停止运行
输出代理类Class文件如下:
你们看看这代理了干了啥事,直接在源码的疑惑都明白了
public final class $Proxy0 extends Proxy implements Vehicle {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
//这不是静态代码块嘛?也就是初始化的时候先通过反射自动给这些属性进行初始化
static {
try {
m1=Class.forName("java.lang.Object").getMethod("equals",Class.forName("java.lang.Object"));
m3 = Class.forName("com.lzh.staticProxy.Vehicle").getMethod("run");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
//反射的时候它拿的是这个构造器,它居然调用了它的父类的那个构造器,追进去看父类干了啥
/*
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
*/
//这就是它的父类构造器,它居然把第三个参数赋值给一个h的属性?
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
//这里是重点重点!!!!
//你看它干了啥事,这不就是上面说的h属性,居然去调用父类h属性的.invoke()方法,
//并把三个参数传过去了那就通透了
//也就是说,根据OOP的动态绑定,会绑定到第三个参数的invoke方法
//上面我们不是重写了invoke方法也就是会动态绑定到我们这个invoke方法,把上面的方法拿下来如下所示进行分析
/*
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("交通工具开始运行");也就是说我们可以在这里进行扩展增强
Object invoke = method.invoke(vehicle, args);这里调用了被代理的对象的run
System.out.println("交通工具停止运行");我们在这里也可以进行扩展增强
return invoke;
}
你们开头看静态代码块的m3初始化的是什么
m3 = Class.forName("com.lzh.staticProxy.Vehicle").getMethod("run");
不就是通过反射,拿到我们被代理类的run方法嘛
在进行调用被代理类的方法逻辑,实现不改变原有的代码的基础上增强和扩展的目的
*/
public final void run() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
由上面分析大家大概知道为啥代理对象,能调动被代理对象的方法,
其实底层都是通过反射+动态绑定,在由初始化静态代码块的Method方法的不同,再通过Mothod对象反射调用被代理对象的不同方法,底层还重写了Object类的hashCode(),toString()…等等方法,在重写的方法一样里调用invoke(),再去调用原来的对象的方法
不得不说,这个底层自动生成Class的代理类是真的牛逼
有了一定的基础我们再去简析SpringAOP机制,有一定的IOC基础可以往下浅析
为什么我们通过注解就能把方法切入各种类各个面
为了理解我们可以配置如下如示的后置处理器
public class PostProcessedHandler implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("after bean type = "+bean.getClass());
return null;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("before bean type = "+bean.getClass());
return null;
}
}
由于SpringIOC容器会初始化,看你是否是单例,是否懒加载,等等,底层会创建好来放入IOC容器中,底层是通过SetXX对某些类进行初始化的,后它会执行我们上面配置的后置处理器
先调用postProcessBeforeInitialization, 在调用(如果你配置了初始化方法的话)初始化方法,再调用后置处理器,最后放入你的IOC容器中
Spring会直接上来就扫描我们的配置的文件,或者是注解有没有配置切面类
举个例子:如下切面类
@Aspect
@Component
public class AspectClass {
@Before(value = "execution(* Pig.getSub(..))")
public void showBeforeLog(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String methodName = signature.getName();
System.out.println("beforeLog...代码执行成功 methodName = "+methodName);
}
public void successEndLog(JoinPoint joinPoint, Object res){
System.out.println("afterReturning ... result = "+res);
}
以before为例子:其他同理
它会扫描到这个注解类,底层通过反射看你的方法上面有没有注解有注解扫描到你的value值,
底层通过自己的一套匹配机制哪个类需要切入,是Pig类需要切入,对这个value字符串进行解析
并把解析的结果存起来,再回到我们IOC初始化的时候去进行匹配,当对我们Pig类初始化的时候
setXX完成后调用我们后置处理器的postProcessBeforeInitialization()方法
打印出来这个对象运行类型还是原来的Pig实例对象
但是postProcessAfterInitialization()后确是代理类型,牛逼,,这也就是为啥我们能切入到Pig类了
举个简单的例子更容易理解
由学到的动态代理,代理对象底层不是都会调用到传进去第三参数的invoke()方法里
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName.equal("getSub")){//匹配到getSub
Spring底层肯定能拿到切面类实例 用切面对象实例.showBeforeLog()//这不就相当于前置通知
try{
Object invoke = method.invoke(pig, args);// 这里相当于调用原对象的方法
....后置通知等等也如此,spring肯定不肯能如此简单,只是简单举例子理解
}catch(Exception e){
} finnaly{
}
}else{
//如果不是getSub方法就不进行调用
Object invoke = method.invoke(pig, args);//
}
return invoke;
}
上面只是简单理解,Spring底层进行了大量的封装,才有我们看到的注解配置一下就能切入想切入的方法
也就是说IOC容器最后存放进去的是代理对象,通过代理对象切入需要的方法后,再去调用真正的对象,
而且这种机制也就是能有四个切面的出现,调用方法前,前置通知, 后 返回通知, 出现异常 异常通知进行切入
finally中的最终通知。
基于这几个切面,这不就是为事务管理进行的量身定制,真的牛逼
//传统的事务管理不就是把start Transaction set false
try{
//进行crud操作数据库(这个必须是原子性,要嘛全部成功,要嘛全部失败)
//所有操作成功后提交
} catch(Exception e){
//这里进行回滚
}
这不就一一对应上AOP的切面,只是Spring底层进行了封装,只需要加个注解就行,只要有这个注解就是IOC容器里放的是代理对象,这不就又是AOP了
为什么说不要在声明事务的类内部方法去调用声明事务的方法,这会导致声明事务失效?我想由上面的讲解大家都明白了吧
没有走代理对象,不就没走切面
异常通知进行切入
finally中的最终通知。
基于这几个切面,这不就是为事务管理进行的量身定制,真的牛逼
//传统的事务管理不就是把start Transaction set false
try{
//进行crud操作数据库(这个必须是原子性,要嘛全部成功,要嘛全部失败)
//所有操作成功后提交
} catch(Exception e){
//这里进行回滚
}
这不就一一对应上AOP的切面,只是Spring底层进行了封装,只需要加个注解就行,只要有这个注解就是IOC容器里放的是代理对象,这不就又是AOP了
为什么说不要在声明事务的类内部方法去调用声明事务的方法,这会导致声明事务失效?我想由上面的讲解大家都明白了吧
没有走代理对象,不就没走切面