为接口创建代理

今天是公历2021年3月14日,难得一遇的日子(201314——爱你一生一世),今天你们有没有出去玩(单身狗的我只能窝在家里撸代码),不过算下来,今天也省了不少钱~(心理mmp)

这是一条分割线===========================splitter

言归正传,今天早上起床看了一会儿大牛写的代码,一款知名的http框架(forest), 大概加调试看了一会儿,写的确实棒,为作者点个赞!
当然,今天的主题是代理模式,代理模式在项目中或多或少都会用到,如果自己没用过,那你所用的框架底层几乎都用过,在这里随便举几个例子

动态代理的使用

例如 spring aop底层

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
	//如果实现了接口 则创建jdk代理 否则创建cglib
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            return new JdkDynamicAopProxy(config);
        }
    }

再如 Mybatis:
MapperProxyFactory

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  } 
//为每个mapper.java 生成一个代理 
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  } 

}

等等etc……

但是最近看到了一款http 框架 forest,内部也是这样子,它的使用很方便,即创建一个接口,然后用方法调用即可…懂行的小伙伴一看这种模式就知道肯定是用了动态代理了
forest

public T createInstance() {
//为接口创建代理 
    synchronized (configuration.getInstanceCache()) {
	...
//创建代理
        instance = (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass, ForestClientProxy.class}, interfaceProxyHandler);
        if (cacheEnabled) {
            configuration.getInstanceCache().put(interfaceClass, instance);
        }
        return instance;
    }
}

调用方法 即调用invoke方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    if ("toString".equals(methodName) && (args == null || args.length == 0)) {
        return "{Forest Proxy Object of " + interfaceClass.getName() + "}";
    }
//读取缓存的方法 并调用
    ForestMethod forestMethod = forestMethodMap.get(method);
    return forestMethod.invoke(args);
}

自己实现一个代理

public class ProxyHandler <T> implements InvocationHandler {

    private Class<T> tClass; 
//获取需要代理的接口对象 即你调用该接口的任何方法都会被拦截
    public ProxyHandler(Class<T> tClass){
        this.tClass = tClass;
    }
//创建动态代理
    public <T> T createProxy(){
       return(T) Proxy.newProxyInstance(tClass.getClassLoader(), new Class[]{tClass}, this);
    }
//执行invoke方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ObjectMapper mapper = new ObjectMapper();

        System.out.println(mapper.writeValueAsString(args)); 
        //执行其他逻辑  todo
        
        return args;
    }

    public static void main(String[] args) throws Exception{
        ProxyHandler<Food> foodProxyHandler = new ProxyHandler<>(Food.class);
        Food proxy = foodProxyHandler.createProxy(); 
        proxy.eat("下午茶");
    }
}

至此,动态代理就模拟完了,有没有很简单~当然底层就很复杂了,包含了动态类的生成,这些都是jdk帮我们生成的,如果想看jdk帮我们生成的类,可以继续往下看!!!

查看jdk生成的代理类

如果想看jdk帮我们生成的代理类,应该咋做呢?
其实不用背,这种网上都很多,添加一个vm的环境变量

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

这个命令什么意思,从字面意思上看就是调用ProxyGenerator类的saveGeneratedFiles字段为true
image.png
然后追踪到sun包下的源码,如上图,其实就是生成以“.class”结尾的代理类,这种就是我们说的动态编译, 通过动态生成二进制码然后让vm运行

当然,更底层的我也没看过,所以点到为止!

好了,接着上面的那个例子继续说,当我们配置了上面生成代理类的配置之后,然后运行main方法,得到下面这张截图

image.png

然后看到我们的项目生成一个目录——com.sun.proxy
底下生成了一个如上图同名同姓的类——$Proxy0
然后点开这个class类,进行反编译,代码我贴到下面:

package com.sun.proxy;

import com.example.demo.proxy.Food;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
//显而易见,它集成了Proxy类并实现了Food接口
//为啥要集成Proxy类呢? 答案就在它用到了Proxy基类的InvocationHandler这个对象,
//对咯,这个对象它就是我们配置的代理回调接口
public final class $Proxy0 extends Proxy implements Food {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    //上面例子diamante实现了InvocationHandler,这里自然可以直接当做调用本地方法一样调用它了
    public final void eat(String var1) throws  {
        try {
//实际上这段代码就是调用 InvocationHandler的实现类的invoke方法
            super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    } 

    static {
        try { 
            m4 = Class.forName("com.example.demo.proxy.Food").getMethod("eat", Class.forName("java.lang.String")); 
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

那么,我们现在应该知道是咋回事了吧?
我把这个用processOn整理了一张图,如下
image.png

# jdk   设计模式  

评论

公众号:mumuser

企鹅群:932154986

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×