Spring aop:一篇搞懂Java代理模式:静态代理、动态代理、AspectJ织入全解析
一、代理的本质
代理就是在不修改原对象的情况下,让其他的对象帮助自己,来控制对这个对象的访问,在访问的前后添加自己的逻辑;实际生活中这种代理角色很多,比如说:明星经纪人、房产中介、材料等的经销商等等
代理模式又是什么
代理模式就是上面的使用了代理的一种工作模式,准确来说它的核心结构就是三个:
(1)subject:接口,相当于房东要中介来完成什么功能,就比如说是授权代理卖房;
(2)realSubject:真正对象,比如说是房东;
(3)proxy:代理,比如说就是中介;
二、动态代理vs静态代理
静态代理就是在编译器通过程序员手动编写代理类,它的代理关系和逻辑在编译的时候就存在确定;而动态代理就是运行时由框架动态生成字节码,无需手动编写更加简洁方便;
下面通过代码具体看到其中的差别,以出租房子为例:
首先我先创建了几个类,业务实现类、代理类、真正对象类:
/**
* 业务实现类,具体的业务执行,被代理的对象也就是房东
*/
public class RealHouseSubject implements HouseSubject{
@Override
public void rentHouse() {
System.out.println("我是房东,我要出租房子!");
}
}
/**
* 代理类,也就是中介
*/
public class HouseProxy implements HouseSubject{
private HouseSubject target;
public HouseProxy(HouseSubject target) {
this.target = target;
}
@Override
public void rentHouse() {
//代理前
System.out.println("我是中介,我来帮房东出租房子,开始代理!");
//出租房子
target.rentHouse();
//出租后
System.out.println("我是中介,房子出租出去了,结束代理!");
}
}
/**
* 相当于被代理类需要代理类代理什么内容,这里是帮忙出租房子
*/
public interface HouseSubject {//业务接口类
void rentHouse();
}
那最后在主函数里调用方法来最后执行的语句就是:
public class Main {
public static void main(String[] args) {
//找代理,静态代理
HouseProxy proxy = new HouseProxy(new RealHouseSubject());
proxy.rentHouse();
System.out.println("=================================================");
}
}

可以看到如果之后我需要添加比如说,卖房子的代理要求,那我要修改的就很多了,每个类里面我都要添加并修改,有点牵一发而动全身的感觉了,下面再来看看动态代理:
动态代理其实是分为运行时织入、编译时织入、加载时织入等等,我们常用而且默认使用的就是运行时织入里的JDK动态代理和CGLIB动态代理;
织入:把横向切面的逻辑(比如说日志、事务等)插入到目标对象的业务代码中,并生成最终的可执行对象的过程;
动态代理的实现方式有两种,分别是JDK动态代理和CGLIB动态代理;Spring在默认情况下如果目标类至少实现了一个接口,就使用JDK动态代理,如果没有实现接口那就是用CGLIB动态代理:
代理类要代替某个对象去实现或者控制对某个对象的访问,就需要拥有目标类该有的功能,也就是要么是继承目标类重写目标类的目标方法,这个就是CGLIB动态代理,当然如果是继承的话就要注意一些继承的要求,比如说目标类不能被final关键字修饰什么的;那另一种就是在运行时创建一个新的类,这个类是通过实现公共接口来实现对应的目标方法,这个就是JDK动态代理;
JDK动态代理:基于接口,利用JVM的Proxy类和InvocationHandler,在运行时生成了实现相同接口的代理类,通过Method.invoke反射调用目标方法;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* JDK动态代理
*/
public class JDKInvocationHandler implements InvocationHandler {//实现InvocationHandler接口
private Object target;
public JDKInvocationHandler(Object target) {
this.target = target;
}
//重写invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始代理...");
Object result = method.invoke(target, args);
System.out.println("结束代理...");
return result;
}
}
CGLIB动态代理:基于继承,利用ASM字节码库,在运行时生成对应类的子类,重写非final的方法,通过MethodInterceptor和FastClass机制调用,不依赖反射;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLIBMethodInterceptor implements MethodInterceptor {
private Object target;
public CGLIBMethodInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("我是中介,开始代理...");
Object result = method.invoke(target, args);
System.out.println("我是中介,结束代理...");
return result;
}
}
import org.springframework.cglib.proxy.Enhancer;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
//JDK动态代理
RealHouseSubject target = new RealHouseSubject();
/**
* public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
* loader是加载代理类的classLoader
* interfaces是要实现的接口,这也看出JDK只能代理的目标类必须要实现接口
* h是代理要做的事情,需要实现InvocationHandler接口
*/
HouseSubject JDKProxy = (HouseSubject) Proxy.newProxyInstance(target.getClass().getClassLoader(),
new Class[]{HouseSubject.class},
new JDKInvocationHandler(target));
JDKProxy.rentHouse();
System.out.println("=================================================");
//CGLIB动态代理
HouseSubject houseSubject = (HouseSubject) Enhancer.create(target.getClass(), new CGLIBMethodInterceptor(target));
houseSubject.rentHouse();
}
}

三、编译时织入和加载时织入
编译时织入其实用的好像不太多,但还是可以了解一下,比如说了解一下编译时织入的AspectJ,也就是说在源码编译成字节码(.class)的阶段,编译器直接修改目标类的字节码,直接把切面的逻辑代码"写进"目标类的字节码里,通过这种方式生成的类,一出生就带有增强的逻辑,在运行的时候就不要代理,因此性能最高,但其实配置会更加复杂;
而加载时织入就更加少见了,AspectJ除了能在编译时修改字节码,还支持在类加载到JVM阶段进行织入,它是不修改磁盘上的.class文件的,而是在JVM把.class文件读取到内存、准备定义成类对象之前,拦截并修改这个字节码文件,然后把修改好了的版本交给JVM;
最后,我会不定时地更新博客,如果觉得文章不错可以关注一下这个努力的博主,有错误的地方欢迎指出,有任何问题可以在评论区交流等等。
更多推荐

所有评论(0)