一、代理的本质

代理就是在不修改原对象的情况下,让其他的对象帮助自己,来控制对这个对象的访问,在访问的前后添加自己的逻辑;实际生活中这种代理角色很多,比如说:明星经纪人、房产中介、材料等的经销商等等 

代理模式又是什么

代理模式就是上面的使用了代理的一种工作模式,准确来说它的核心结构就是三个:

(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;

       最后,我会不定时地更新博客,如果觉得文章不错可以关注一下这个努力的博主,有错误的地方欢迎指出,有任何问题可以在评论区交流等等。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐