代理模式在Spring、MyBatis以及Feign源码中的应用

结构型模式                 ————顺口溜:适装桥组享代外

目录

1、代理模式

1.1 代理模式UML图

1.2 日常生活中看代理模式

1.3 使用场景

1.4 java代码实现

2、JDK动态代理问题

2.1 为何调用代理类的方法就会自动进入InvocationHandler 的 invoke()方法呢?

2.2 为什么被代理类要实现接口

2.3 为什么JDK动态代理中要求目标类实现的接口数量不能超过65535个

3、CGLib 和 JDK 动态代理对比

4、代理模式在源码中的应用

4.1 Spring源码中代理模式

4.2 MyBatis源码中代理模式

4.2.1 简介

4.2.2 再次认识Configuration

MapperRegistry#addMapper(class)

4.2.3 addMapper和getMapper

4.2.4 映射代理类的实现 MapperProxyFactory、MapperProxy

4.3 Feign源码中代理模式

4.3.1 Feign

4.3.2 ReflectiveFeign.newInstance()

5、代理模式优缺点

5.1 优点

5.2 缺点

5.3 Spring中单例CGLIB代理总结


1、代理模式

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。

举个例子来说明:假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了。用图表示如下:

主要解决:

在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

意图:为其他对象提供一种代理以控制对这个对象的访问。

何时使用:想在访问一个类时做一些控制。

如何解决:增加中间层。

关键代码:实现与被代理类组合。

1.1 代理模式UML图

235619_qHD7_2003960.png

1.2 日常生活中看代理模式

  • 1、Windows 里面的快捷方式。
  • 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。
  • 3、买火车票不一定在火车站买,也可以去代售点。
  • 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。
  • 5、spring aop。

1.3 使用场景

按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。

1.4 java代码实现

根据上UML图,先创建抽象类subject

package com.amosli.dp.structural.proxy;
 
public abstract class Subject {
	abstract void request();
}

具体subject实例


package com.amosli.dp.structural.proxy;
 
public class RealSubject extends Subject{
 
	@Override
	void request() {
		System.out.println("this is realsubject...");
	}
 
}

代理类

package com.amosli.dp.structural.proxy;
 
public class Proxy extends Subject {
	private Subject subject;
 
	public Proxy() {
		subject = new RealSubject();
	}
 
	@Override
	void request() {
		subject.request();
	}
 
}

client端类


package com.amosli.dp.structural.proxy;
 
public class Client {
	public static void main(String[] args) {
		Proxy proxy = new Proxy();
		proxy.request();
	}
}

2、JDK动态代理问题

Jdk中的代理模式:java.lang.reflect.Proxy; RMI

2.1 为何调用代理类的方法就会自动进入InvocationHandler 的 invoke()方法呢?

其实是因为在动态代理类的定义中,构造函数是含参的构造,参数就是我们invocationHandler 实例,而每一个被代理接口的方法都会在代理类中生成一个对应的实现方法,并在实现方法中最终调用invocationHandler 的invoke方法,这就解释了为何执行代理类的方法会自动进入到我们自定义的invocationHandler的invoke方法中,然后在我们的invoke方法中再利用jdk反射的方式去调用真正的被代理类的业务方法,而且还可以在方法的前后去加一些我们自定义的逻辑。比如切面编程AOP等。

2.2 为什么被代理类要实现接口

由于java的单继承,动态生成的代理类已经继承了Proxy类的,就不能再继承其他的类,所以只能靠实现被代理类的接口的形式,故JDK的动态代理必须有接口
相关博客:https://blog.csdn.net/u014301265/article/details/102832131

2.3 为什么JDK动态代理中要求目标类实现的接口数量不能超过65535个

先明确几个概念:
Class文件是一组以8字节为基础单位的二进制流
各个数据项目严格按照顺序紧凑排列在class文件中
中间没有任何分隔符,这使得class文件中存储的内容几乎是全部程序运行的程序
Java虚拟机规范规定,Class文件格式采用类似C语言结构体的伪结构来存储数据,这种结构只有两种数据类型:无符号数和表
接口索引计数器(interfaces_count),占2字节
参考第一句话:class文件是一组8字节为基础的二进制流,interface_count占2字节。也就是16.00000000,00000000 所以,证明
interface_count的数量最多是2^16次方 最大值=65535
这是在JVM的层面上决定了它的数量最多是65535
且在java源码中也可以看到
if (var2.size() > 65535) {
throw new IllegalArgumentException("interface limit exceeded: " var2.size());
直接做了65535的长度的校验,所以,JDK的动态代理要求,目标类实现的接口数量不能超过65535。

3、CGLib 和 JDK 动态代理对比

  1. JDK 动态代理是实现了被代理对象的接口,CGLib 是继承了被代理对象。
  2. JDK 和 CGLib 都是在运行期生成字节码,JDK 是直接写 Class 字节码,CGLib 使用 ASM框架写 Class 字节码,Cglib 代理实现更复杂,生成代理类比 JDK 效率低。
  3. JDK 调用代理方法,是通过反射机制调用,CGLib 是通过 FastClass 机制直接调用方法,CGLib 执行效率更高。
  4. JDK是采用读取接口的信息,CGLib覆盖父类方法。目的:都是生成一个新的类,去实现增强代码逻辑的功能。
  5. JDK Proxy 对于用户而言,必须要有一个接口实现,目标类相对来说复杂,CGLib 可以代理任意一个普通的类,没有任何要求。
  6. CGLib 生成代理逻辑更复杂,效率,调用效率更高,生成一个包含了所有的逻辑的FastClass,不再需要反射调用JDK Proxy生成代理的逻辑简单,执行效率相对要低,每次都要反射动态调用。
  7. CGLib 有个坑,CGLib不能代理final的方法。

4、代理模式在源码中的应用

4.1 Spring源码中代理模式

代理模式在 Spring 源码中的应用
先看 ProxyFactoryBean 核心的方法就是 getObject()方法,我们来看一下源码:

/**
* Return a proxy. Invoked when clients obtain beans from this factory bean.
* Create an instance of the AOP proxy to be returned by this factory.
* The instance will be cached for a singleton, and create on each call to
* {@code getObject()} for a proxy.
* @return a fresh AOP proxy reflecting the current state of this factory
*/
@Override
@Nullable
public Object getObject() throws BeansException {
   initializeAdvisorChain();
   if (isSingleton()) {
      return getSingletonInstance();
   }
   else {
      if (this.targetName == null) {
         logger.info("Using non-singleton proxies with singleton targets is often undesirable. " +
               "Enable prototype proxies by setting the 'targetName' property.");
      }
      return newPrototypeInstance();
   }
}

在getObject()方法中,主要调用 getSingletonInstance()和 newPrototypeInstance();在 Spring 的配置中,如果不做任何设置,那么 Spring 代理生成的 Bean 都是单例对象。如果修改 scope 则每次创建一个新的原型对象。
newPrototypeInstance()里面的逻辑比较复杂,这里不做了解。

Spring 利用动态代理实现 AOP 有两个非常重要的类,一个是 JdkDynamicAopProxy 类和 CglibAopProxy 类,来看一下类图:

在这里插入图片描述

Spring 中的代理选择原则

  1. 当 Bean 有实现接口时,Spring 就会用 JDK 的动态代理。
  2. 当 Bean 没有实现接口时,Spring 选择 CGLib。
  3. Spring 可以通过配置强制使用 CGLib,只需在 Spring 的配置文件中加入如下代码:
<aop:aspectj-autoproxy proxy-target-class="true"/>

4.2 MyBatis源码中代理模式

4.2.1 简介

在mybatis中执行sql时有两种方式,一种是基于statementId,也就是直接调用SqlSession的方法,如sqlSession.update(“statementId”);

还有一种方法是基于java接口,也是日常开发中最常用的方式。

mapper接口中的每个方法都可以喝mapper xml中的一条sql语句对应,我们可以直接通过调用接口方法的方式进行sql执行。因为mybatis会为mapper接口通过jdk动态代理的方法生成接口的实现类,下面将针对mapper接口的代理展开分析。

以下是mapper接口的代理模式的核心组件的类图。

Mapper代理模式类图

4.2.2 再次认识Configuration

public class Configuration {
  //映射注册表
  protected MapperRegistry mapperRegistry = new MapperRegistry(this);

  // 获取映射注册表
  public MapperRegistry getMapperRegistry() {
    return mapperRegistry;
  }

  //添加到映射注册表
  public void addMappers(String packageName, Class<?> superType) {
    mapperRegistry.addMappers(packageName, superType);
  }
  //添加到映射注册表
  public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
  }
  //添加到映射注册表
  public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }
  //从映射注册表中获取
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
  //判断映射注册表中是否存在
  public boolean hasMapper(Class<?> type) {
    return mapperRegistry.hasMapper(type);
  }
}

MapperRegistry通过Map结构的属性knownMappers中维护着mybatis中所有的mapper接口。

MapperRegistry#addMapper(class)

当开发者在配置文件中配置了通过mappers节点的子节点mapper配置了mapper接口时,会调用configuation#addMapper(Class)记录mapper接口,而configuration又委托了MapperRegistry#addMapper(class)处理逻辑。

public class MapperRegistry {

  private Configuration config;
  //映射缓存 键:类对象,值:映射代理工厂
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }
  //从映射注册表中获取  
  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null)
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
  //判断映射注册表中是否存在  
  public <T> boolean hasMapper(Class<T> type) {
    return knownMappers.containsKey(type);
  }
  //添加到映射注册表
  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        // 处理接口类(例如UserDao)中的注解
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

  /**
   * @since 3.2.2
   */
  //获取缓存中所有的Key,并且是不可修改的
  public Collection<Class<?>> getMappers() {
    return Collections.unmodifiableCollection(knownMappers.keySet());
  }

  /**
   * @since 3.2.2
   */
  //添加到映射注册表
  public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

  /**
   * @since 3.2.2
   */
  //添加到映射注册表
  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }
}

在方法getMappers中用到了Collections.unmodifiableCollection(knownMappers.keySet());,如果你不了解,可以查阅:Collections.unmodifiableMap,Collections.unmodifiableList,Collections.unmodifiableSet作用及源码解析

在了解了这两个类之后,就来解决第一个问题:1,为什么在以前的代码流程中从来没有addMapper,而这里却有getMapper?

4.2.3 addMapper和getMapper

关于addMapper,在文章mapper节点解析中,代码:bindMapperForNamespace();addMapper就是在这个方法中用到的。但是前提是,你需要了解java的动态代理。来看看源码:

private void bindMapperForNamespace() {
    //获取当前命名空间(String:com.zcz.learnmybatis.dao.UserDao)
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        // 使用类加载器加载,加载类,获取类对象
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        //判断映射注册表中是否存在
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          // 添加到已经解析的缓存
          configuration.addLoadedResource("namespace:" + namespace);
          // 添加到映射这测表
          configuration.addMapper(boundType);
        }
      }
    }
  }

看到了吧,就在最后一行代码。但是这里并不是简单的保存了一个类对象,而是在MapperRegistry中进行了进一步的处理:

//添加到映射注册表
  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
     // 在这里,保存的是new MapperProxyFactory实例对象。
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        // 处理接口类(例如UserDao)中的注解
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

在第10行,保存的是映射代理工厂(MapperProxyFactory)的实例对象。到这里addMapper就解释清楚了。接下来看看getMapper方法。getMapper,代码:configuration.<T>getMapper(type, this);type:是UserDao.class;this:SqlSession的实例化对象

从第一部分Configuration中可以发现,Configuration又调用了MapperRegistry的getMapper方法

//从映射注册表中获取
  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null)
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

从代码的第4行可以清晰的看到,或者根据类对象从缓存Map中,获取到了addMapper中保存的MapperProxyFactory对象实例。但是并没有将这个对象实例直接返回,而是通过调用的MapperProxyFactory的newInstance方法返回的一个UserDao实现类。接下来我们就解释一下第二个问题:2,UserDao明明是我们定义的一个接口类,根本没有定义实现类,那这个userMapper是什么?是mybatis自动为我们生成的实现类吗?

4.2.4 映射代理类的实现 MapperProxyFactory、MapperProxy

看过JAVA设计模式-动态代理(Proxy)示例及说明这篇文章的同学应该知道这个问题的答案了,userMapper是一个代理类对象实例。是通过映射代理工厂(MapperProxyFactory)的方法newInstance方法获取的。

  但是在这里mybatis有一个很巧妙的构思,使得这个的动态代理的使用方法和文章JAVA设计模式-动态代理(Proxy)示例及说明中的使用方法有些许不同。

  不妨在你的脑海中回顾一下JAVA设计模式-动态代理(Proxy)示例及说明中实现动态代理的关键因素:

    1,一个接口

    2,实现了接口的类

    3,一个调用处理类(构造方法中要传入2中的类的实例对象)

    4,调用Proxy.newProxyInstance方法获取代理类实例化对象

带着这个印象,我们来分析一下mybatis是怎么实现动态代理的。既然userMapper是通过映射代理工厂(MapperProxyFactory)生产出来的,那么我们就看看它的源码:

//映射代理工厂
public class MapperProxyFactory<T> {
  // 接口类对象(UserDao.class)
  private final Class<T> mapperInterface;
  // 对象中的方法缓存
  private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  //构造器
  public MapperProxyFactory(Class<T> mapperInterface) {
    //为接口类对象赋值
    this.mapperInterface = mapperInterface;
  }

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

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  // 实例化映射代理类
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  // 实例化映射代理类
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

我们调用的newInstance方法就是第29行的方法。然后这个方法又调用了24行的方法。我们来看25行的代码,是不是很熟悉?Proxy.newProxyInstance(类加载器,接口类对象数组,实现了InvocationHandler的对象实例 mapperProxy),到这里映射代理类的实例化已经解释清楚了,也就是解决了第二个问题,接下来我们扩展一下:

现在我们还没有看到MapperProxy类的源码,但是我们可以大胆的猜测,类MapperProxy一定是实现了InvocationHandler接口,并且也一定实现了Invoke方法:

// 映射代理
public class MapperProxy<T> implements InvocationHandler, Serializable {
  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  //构造方法
  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }
  //代理类调用的时候执行的方法
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 检查Method方法所在的类是否是Object
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    // 应用缓存
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //执行查询
    return mapperMethod.execute(sqlSession, args);
  }

  //应用缓存
  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
}

那么,mybatis使用动态代理的方式跟文章JAVA设计模式-动态代理(Proxy)示例及说明使用动态代理的方式,有哪些不同呢?

  1,一个接口(UserDao)

  2,实现了接口的类(没有)

  3,一个调用处理类(构造方法中要传入2中的类的实例对象)(2中没有,自然这里也不会传入对象)

  4,调用Proxy.newProxyInstance方法获取代理类实例化对象(有的)

4.3 Feign源码中代理模式

4.3.1 Feign

首先我要说的是springcloud没有rpc,这就涉及rpc和微服务的区别。springcloud的模块通信工具feign跟httpclient和okhttp是一样的东西,都是对http请求封装的工具,其实feign可以选择httpclient或者okhttp作为底层实现(修改配置即可)。Feign makes writing java http clients easier,中文译为Feign使得写java http客户端更容易。选择Feign有如下原因:

  • Feign允许我们通过几个注解的方式实现http的第三方请求
  • 能够让我们以面向对象化的形式简单快速的完成第三方请求的开放,解决了代码的冗余
  • Feign插件化的开放定时允许我们自定义编码器解码器错误处理器拦截器等等

Feign的作用:

  • ①封装http请求,使开发人员对发送请求的过程无感知,给人一种伪rpc感觉(这也许是feign这个名字的由来吧,伪装~)。
  • ②feign整合ribbon和hystrix,结合eureka起到负载均衡和熔断器、降级作用。

通过源码可知道:

  1. Feign通过 Feign.Builder建造者模式方式对各个开放组件进行配置
  2. target()最终会调用new ReflectiveFeign(...)来生成Feign实例
  3. SynchronousMethodHandler.Factory用于创建一个SynchronousMethodHandler对象
  4. ParseHandlersByNameTarget的所有接口方法转换为Map<String, MethodHandler>对象
  5. ReflectiveFeignFeign的具体实现类,最终会调用newInstance方法通过原生的动态代理框架生成最终的动态代理对象

本文就不做Feign过多的陈述了,接下来主要讲Feign中代理模式的提现。

4.3.2 ReflectiveFeign.newInstance()

public class ReflectiveFeign extends Feign {
    //省略部分无关紧要的代码

    /**
     * 最终调用newInstance返回动态的代理对象
     * @param target
     * @param <T>
     * @return
     */
    public <T> T newInstance(Target<T> target) {
        //通过ParseHandlersByName对象apply方法生成方法名与MethodHandler的映射关系,见图1
        Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
        //构建Method与其对应的MethodHandler的关系存放入methodToHandler,主要用于在实际调用的时候,能够根据方法名获取对应的MethodHandler发起请求,见图2
        Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
        List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
        //通过循环构建方法与其对应的MethodHandler的关系存放入methodToHandler
        for (Method method : target.type().getMethods()) {
            if (method.getDeclaringClass() == Object.class) {
                continue;
            } else if(Util.isDefault(method)) {
                DefaultMethodHandler handler = new DefaultMethodHandler(method);
                defaultMethodHandlers.add(handler);
                methodToHandler.put(method, handler);
            } else {
                methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
            }
        }
        //通过InvocationHandlerFactory factory创建代理对象
        InvocationHandler handler = factory.create(target, methodToHandler);
        //生成代理对象
        T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);

        for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
            defaultMethodHandler.bindTo(proxy);
        }
        return proxy;
    }

    static class FeignInvocationHandler implements InvocationHandler {

        private final Target target;
        private final Map<Method, MethodHandler> dispatch;

        FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
            this.target = checkNotNull(target, "target");
            this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
        }

        /**
         * 请求接口的动态代理,在调用方法的过程中,实际是执行该方法进行调用
         * @param proxy
         * @param method
         * @param args
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if ("equals".equals(method.getName())) {
                try {
                    Object
                            otherHandler =
                            args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
                    return equals(otherHandler);
                } catch (IllegalArgumentException e) {
                    return false;
                }
            } else if ("hashCode".equals(method.getName())) {
                return hashCode();
            } else if ("toString".equals(method.getName())) {
                return toString();
            }
            //根据方法名获取对应的MethodHandler发起请求
            return dispatch.get(method).invoke(args);
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof FeignInvocationHandler) {
                FeignInvocationHandler other = (FeignInvocationHandler) obj;
                return target.equals(other.target);
            }
            return false;
        }

        @Override
        public int hashCode() {
            return target.hashCode();
        }

        @Override
        public String toString() {
            return target.toString();
        }
    }
}

具体步骤如下:

  1. 根据target,解析生成MethodHandler对象
  2. 构建methodMethodHandler的关系
  3. 通过jdk动态代理生成代理对象
  4. DefaultMethodHandler绑定到代理对象

5、代理模式优缺点

5.1 优点

1、职责清晰。 2、高扩展性。 3、智能化。

5.2 缺点

  • 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
  • 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

5.3 Spring中单例CGLIB代理总结

CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

参考文章:

https://my.oschina.net/u/2003960/blog/535648

https://www.runoob.com/design-pattern/proxy-pattern.html

https://blog.csdn.net/weixin_38024782/article/details/108701584

https://www.cnblogs.com/zhangchengzi/p/9706395.html

https://www.jianshu.com/p/668ff053adba

https://blog.csdn.net/qq_39470742/article/details/88692557

https://www.cnblogs.com/daniels/p/8242592.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值