Java 源码剖析——动态代理的实现原理

本贴最后更新于 2343 天前,其中的信息可能已经天翻地覆

在本篇博客中,博主将和大家一起深入分析 Jdk 自带的动态代理的实现原理。如果有同学对代理模式静态代理动态代理这些概念比较模糊,请先阅读博主的另一篇文章《一步一步学设计模式——代理模式》

为了方便讲解,我们继续使用代理模式中的购票例子,下面是这个例子的主要代码:

  • 首先我们先建立一个接口:
package com.wxueyuan.DesignPettern.StaticProxy;

public interface Operation {
	void buyTicket(Ticket t);
}

  • 接着我们建立一个学生类并实现上面的接口,表示学生需要购票:
package com.wxueyuan.DesignPettern.StaticProxy;

public class Student implements Operation{
	
	@Override
	public void buyTicket(Ticket t) {
		// TODO Auto-generated method stub
		System.out.println("学生买到一张票,票价为"+t.getPrice());
	}

}
  • 然后我们建立一个 TicketOperationInvocationHandler 实现 InvocationHandler 接口:
package com.wxueyuan.DesignPettern.DynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class TicketOperationInvocationHandler implements InvocationHandler {
	
	//将需要代理的委托对象传入Handler中
	private Object target; 
	
	public TicketOperationInvocationHandler(Object target) {
		this.target = target;
	}

	//获得帮助购票者买票的代理
	public Object getProxy() {
		return Proxy.newProxyInstance(Thread.currentThread()  
                .getContextClassLoader(), target.getClass().getInterfaces(),  
                this);  
	}

	//实际上黄牛执行的购票操作
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// TODO Auto-generated method stub
		System.out.println("黄牛收取购票者的钱");
		System.out.println("黄牛连夜排队");
		Object ret = method.invoke(target, args);
		System.out.println("黄牛将票交给购票者");
		return ret;
	}

}

  • 最后是测试类
package com.wxueyuan.DesignPettern.DynamicProxy;

import com.wxueyuan.DesignPettern.StaticProxy.Operation;
import com.wxueyuan.DesignPettern.StaticProxy.Student;
import com.wxueyuan.DesignPettern.StaticProxy.Ticket;

public class Test {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		//学生需要购买的ticket实例
		Ticket studentTicket = new Ticket(200);
		
		//创建为学生买票的代理
		Operation studentProxy = (Operation) new TicketOperationInvocationHandler(new Student()).getProxy();
		studentProxy.buyTicket(studentTicket);
		
	}

}

执行结果为:
黄牛收取购票者的钱
黄牛连夜排队
购票者买到一张票,票价为 200.0
黄牛将票交给学生

下面我们就一步一步地分析这个简单的动态代理例子的原理:

首先我们先看我们是如何获得学生的代理的:

Operation studentProxy = (Operation) new TicketOperationInvocationHandler(new Student()).getProxy();

其中的关键就在于我们自定义的 InvocationHandler 中的 getProxy()方法,现在我们就进入这个方法看一下:

public Object getProxy() {
		return Proxy.newProxyInstance(Thread.currentThread()  
                .getContextClassLoader(), target.getClass().getInterfaces(),  
                this);  
	}

这个方法的核心就是使用 Proxy 类的静态方法 newProxyInstance(),我们具体看一下这个方法究竟在干什么,我们以 Jdk1.8 的源码为例,首先来看一下这个方法的注释:

/**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class
     *          to implement
     * @param   h the invocation handler to dispatch method invocations to

这个方法用来返回一个实现了一个或多个指定接口的代理类的实例,这个代理类能够将其实现的方法的调用传递给指定的方法调用处理器。
参数:
     loader:加载这个代理类的类加载器
     interfaces:这个代理类实现的所有接口
     h:将方法调用传至的调用处理器

newProxyInstance()这个方法生成实例的核心代码有以下几句:

    //获得代理类的class类
    Class<?> cl = getProxyClass0(loader, intfs);
	...
	//获取代理类的构造函数
	final Constructor<?> cons = cl.getConstructor(constructorParams);
	...
	//根据构造函数生成一个代理类的实例,至于为什么代理类的构造方法中的参数是方法参数h,稍后我们就会知道了
	return cons.newInstance(new Object[]{h});

从 newProxyInstance()方法中的三行核心代码可以看出,如何获取代理类的 class 类是重中之重,因为获取 class 类之后,我们就可以利用 Java 反射获取构造函数并生成实例了。由于反射并不是本篇博客的主题,我们现在就来着重关注一下 getProxyClass0()方法是如何工作的:

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        //如果代理类的class在缓存中存在,则直接获取,否则的话,通过ProxyClassFactory来创建
        return proxyClassCache.get(loader, interfaces);
    }

这里的 proxyClassCache 是在 Proxy 类中声明的 WeakCache 的实例,那我们就一起来看看这个 WeakCache 是什么:

/**
 * Cache mapping pairs of (key, sub-key) -> value. 
 * Keys and values are weakly but sub-keys are strongly referenced. 
 * Keys are passed directly to get method which also takes a parameter. 
 * Sub-keys are calculated from keys and parameters using the subKeyFactory function
 * passed to the constructor. 
 * Values are calculated from keys and parameters using the valueFactory function passed
 * to the constructor.
*/

//这个WeakCache能够将一组(key,sub-key)的值映射成value的值。其中Key的值是直接通过参数传入的。
//sub-key的值是通过构造方法中的subKeyFactory生成的,value的值是通过构造方法中的valueFactory生成的。
//它的构造方法是:
final class WeakCache<K,P,V> {
  ...
  
  public WeakCache(BiFunction<K, P, ?> subKeyFactory,BiFunction<K, P, V> valueFactory) {
	this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
	this.valueFactory = Objects.requireNonNull(valueFactory);
  }
  
  ...
}

知道了 WeakCache 的构造方法之后,我们一起来看一下我们在 getProxyClass0 方法中使用到的 WeakCache 的 get 方法,它的核心代码如下:

public V get(K key, P parameter) {
  //通过subKeyFactory的apply方法生成subKey
  Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
  ...
  //通过subKey从valuesMap中取出可能存在的缓存提供者supplier
  Supplier<V> supplier = valuesMap.get(subKey);
  ...
  //如果供应者不为空,就调用supplier.get()方法,get方法的返回值就是我们这个方法的返回值
  if (supplier != null) {
	// 这里的供应者有可能是一个factory或者是一个缓存实例
	V value = supplier.get();
	if (value != null) {
		return value;
	}
  }
  ...
  //如果factory没有成功创建,我们此时创建一个factory
  if (factory == null) {
	factory = new Factory(key, parameter, subKey, valuesMap);
  }
  ...

看到这大家也许会问,上面代码中 factory 成功创建之后,如果 supplier 就是 factory,该如何通过 factory 的 get()方法来返回我们需要的 value 值,这个 value 值又是怎么计算得到的呢?原来 Factory 这个类也实现了 Supplier 这个函数式接口,因此它也实现了自己的 get 方法:

private final class Factory implements Supplier<V> {
   @Override
        public synchronized V get() {
			...
	    V value = null;
            try {
		//通过我们的valueFactory的apply方法生成value
                value = Objects.requireNonNull(valueFactory.apply(key, parameter));
            }
	    ...
	    return  value;
	 }
}

知道了 WeakCache 中 get()方法的实现后,我们看一下在 Proxy 类中,是如何定义这个 WeakCache 类型的 proxyClassCache 的呢?

//根据WeakCache的构造函数可知,KeyFactory就是在get方法中生成sub-key的subKeyFactory;
//ProxyClassFactory就是get方法中生成value的valueFactory
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

看到这里读者们应该知道 WeakCache 中的 get 方法是怎么工作的了吧?它利用 subKeyFactory(在 Proxy 类中就是 KeyFactory)来生成 subKey,再利用 valueFactory(在 Proxy 类中就是 ProxyClassFactory)的 apply 方法生成并返回 value 值。那么我们一起来看一下,KeyFactory 是如何生成 subKey 的:

//其实很简单就是根据参数intefaces的数量,来生成不同的subKey对象
private static final class KeyFactory
        implements BiFunction<ClassLoader, Class<?>[], Object>
    {
        @Override
        public Object apply(ClassLoader classLoader, Class<?>[] interfaces) {
            switch (interfaces.length) {
                case 1: return new Key1(interfaces[0]); // 代理类只实现了一个接口
                case 2: return new Key2(interfaces[0], interfaces[1]);//代理类实现了两个接口
                case 0: return key0;//代理类没有实现接口
                default: return new KeyX(interfaces);//代理类实现了三个及以上的接口
            }
        }
    }

然后是 ProxyClassFactory 是如何生成 value,也就是我们这里需要的代理类的 class 类的:

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
  //代理类名字的前缀为$Proxy
  private static final String proxyClassNamePrefix = "$Proxy";
  //为了生成唯一的代理类名的计数器
  private static final AtomicLong nextUniqueNumber = new AtomicLong();
  
  @Override
  public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
  
	...
	
	String proxyPkg = null;     // 代理类的包名
	//对于非公共接口,代理类的包名与接口的包名相同
	for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }
           
  }
  
  if (proxyPkg == null) {
	// 如果没有非公共的接口,就使用com.sun.proxy作为包名
	proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
  }
  //默认生成的公共代理类的全限定名为com.sun.proxy.$Proxy0,com.sun.proxy.$Proxy1,以此数字递增
  long num = nextUniqueNumber.getAndIncrement();
  String proxyName = proxyPkg + proxyClassNamePrefix + num;
  
  //生成代理类的字节码
  byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
	  proxyName, interfaces, accessFlags);
  
  try {  
        //根据上面产生的字节码产生Class实例并返回,至此我们终于获得了代理类的Class实例
	  return defineClass0(loader, proxyName,  
			proxyClassFile, 0, proxyClassFile.length);  
  } catch (ClassFormatError e) {  
            throw new IllegalArgumentException(e.toString());  
  } 
}

ProxyGenerator.generateProxyClass 这个方法的源码并没有公开,我们可以反编译 class 文件,然后简单看一下:

 public static byte[] generateProxyClass(final String var0, Class[] var1) {  
    ProxyGenerator var2 = new ProxyGenerator(var0, var1);  
    final byte[] var3 = var2.generateClassFile();  
    // 这里根据参数配置,决定是否把生成的字节码(.class文件)保存到本地磁盘,默认是不保存的
    if(saveGeneratedFiles) {  
        AccessController.doPrivileged(new PrivilegedAction() {  
            public Void run() {  
                try {  
                    FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class");  
                    var1.write(var3);  
                    var1.close();  
                    return null;  
                } catch (IOException var2) {  
                    throw new InternalError("I/O exception saving generated file: " + var2);  
                }  
            }  
        });  
    }  
    return var3;  
}  

我们可以通过设置 sun.misc.ProxyGenerator.saveGeneratedFiles 这个 boolean 值的属性,来使方法默认将 class 文件保存到磁盘。那么我们就来修改一下我们的 Test 代码,来将生成的 proxy 文件保存到磁盘上:

public static void main(String[] args) {
		// TODO Auto-generated method stub
		
	//学生需要购买的ticket实例
	Ticket studentTicket = new Ticket(200);
		
	//将是否在系统属性修改为true,使字节文件保存到磁盘上
	System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");  
        //显示生成的代理类的全限定名    
	System.out.println(Proxy.getProxyClass(Operation.class.getClassLoader(), Operation.class)); 
	//创建为学生买票的黄牛代理
	Operation studentProxy = (Operation) new TicketOperationInvocationHandler(new Student()).getProxy();
	studentProxy.buyTicket(studentTicket);
	
	}

执行结果为:
class com.sun.proxy.$Proxy0
黄牛收取购票者的钱
黄牛连夜排队
学生买到一张票,票价为 200.0
黄牛将票交给购票者
同时,在 com/sun/proxy 下生成了Proxy0.class</font>,(注:如果我们的接口类不是public的,那么我们生成的Proxy0 代理类的包名会和接口类的包名相同哦)。下面我们将这个字节码反编译看一下:

package com.sun.proxy;

import com.wxueyuan.DesignPettern.StaticProxy.Ticket;
import java.lang.reflect.UndeclaredThrowableException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import com.wxueyuan.DesignPettern.StaticProxy.Operation;
import java.lang.reflect.Proxy;

public final class $Proxy0 extends Proxy implements Operation
{
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    
    public $Proxy0(final InvocationHandler invocationHandler) {
        super(invocationHandler);
    }
    
    public final boolean equals(final Object o) {
        try {
            return (boolean)super.h.invoke(this, $Proxy0.m1, new Object[] { o });
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
    public final String toString() {
        try {
            return (String)super.h.invoke(this, $Proxy0.m2, null);
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
    public final void buyTicket(final Ticket ticket) {
        try {
            super.h.invoke(this, $Proxy0.m3, new Object[] { ticket });
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
    public final int hashCode() {
        try {
            return (int)super.h.invoke(this, $Proxy0.m0, null);
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
    static {
        try {
            $Proxy0.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            $Proxy0.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class<?>[])new Class[0]);
            $Proxy0.m3 = Class.forName("com.wxueyuan.DesignPettern.StaticProxy.Operation").getMethod("buyTicket", Class.forName("com.wxueyuan.DesignPettern.StaticProxy.Ticket"));
            $Proxy0.m0 = Class.forName("java.lang.Object").getMethod("hashCode", (Class<?>[])new Class[0]);
        }
        catch (NoSuchMethodException ex) {
            throw new NoSuchMethodError(ex.getMessage());
        }
        catch (ClassNotFoundException ex2) {
            throw new NoClassDefFoundError(ex2.getMessage());
        }
    }
}

还记得我们之前的 return cons.newInstance(new Object[]{h});这句代码么,因为生成的 Proxy 类的构造函数的参数就是就是 InvocationHandler,因此我们将 newProxyInstance()中的参数 h 传递过来,用来调用它的 invoke()方法。

分析了这么多的源码,我们来总结一下 Java 动态代理的流程吧:

  1. Proxy.newProxyInstance()方法返回一个代理类的实例,需要传入 InvocationHandler 的实例 h
  2. 当新的代理实例调用指定方法时,本质上是 InvocationHandler 实例调用 invoke 方法,并传入指定的 method 类型的参数。

根据我们生成 $Proxy0 代理类,我们能够总结出:

  1. 所有生成的代理类都继承了 Proxy 类,实现了需要代理的接口。正是由于 java 不能多继承,所以 JDK 的动态代理不支持对实现类的代理,只支持接口的代理。
  2. 提供了一个使用 InvocationHandler 作为参数的构造方法。这个参数是由 Proxy.newProxyInstance()方法的参数传入的。当代理类实例调用某个方法时,本质上是 InvocationHandler 实例以该方法的 method 类型作为参数调用 invoke 方法。

Java 的动态代理其实有很多应用场景,比如 Spring 的 AOP 或者是最近很火的 RPC 框架,里面都涉及到了动态代理的知识,因此从原理上分析一下动态代理的源码还是很有帮助的,那么这次的源码分析就到这里了,我们下次再见 ~。

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1083 引用 • 3461 回帖 • 286 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3168 引用 • 8207 回帖
  • 动态代理实现原理
    1 引用

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • Thymeleaf

    Thymeleaf 是一款用于渲染 XML/XHTML/HTML5 内容的模板引擎。类似 Velocity、 FreeMarker 等,它也可以轻易的与 Spring 等 Web 框架进行集成作为 Web 应用的模板引擎。与其它模板引擎相比,Thymeleaf 最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个 Web 应用。

    11 引用 • 19 回帖 • 319 关注
  • 黑曜石

    黑曜石是一款强大的知识库工具,支持本地 Markdown 文件编辑,支持双向链接和关系图。

    A second brain, for you, forever.

    10 引用 • 85 回帖
  • 国际化

    i18n(其来源是英文单词 internationalization 的首末字符 i 和 n,18 为中间的字符数)是“国际化”的简称。对程序来说,国际化是指在不修改代码的情况下,能根据不同语言及地区显示相应的界面。

    7 引用 • 26 回帖 • 2 关注
  • TextBundle

    TextBundle 文件格式旨在应用程序之间交换 Markdown 或 Fountain 之类的纯文本文件时,提供更无缝的用户体验。

    1 引用 • 2 回帖 • 47 关注
  • JSON

    JSON (JavaScript Object Notation)是一种轻量级的数据交换格式。易于人类阅读和编写。同时也易于机器解析和生成。

    51 引用 • 190 回帖
  • 微软

    微软是一家美国跨国科技公司,也是世界 PC 软件开发的先导,由比尔·盖茨与保罗·艾伦创办于 1975 年,公司总部设立在华盛顿州的雷德蒙德(Redmond,邻近西雅图)。以研发、制造、授权和提供广泛的电脑软件服务业务为主。

    8 引用 • 44 回帖
  • 书籍

    宋真宗赵恒曾经说过:“书中自有黄金屋,书中自有颜如玉。”

    76 引用 • 390 回帖
  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    106 引用 • 275 回帖 • 2 关注
  • 服务器

    服务器,也称伺服器,是提供计算服务的设备。由于服务器需要响应服务请求,并进行处理,因此一般来说服务器应具备承担服务并且保障服务的能力。

    124 引用 • 580 回帖
  • 小说

    小说是以刻画人物形象为中心,通过完整的故事情节和环境描写来反映社会生活的文学体裁。

    28 引用 • 108 回帖
  • PWA

    PWA(Progressive Web App)是 Google 在 2015 年提出、2016 年 6 月开始推广的项目。它结合了一系列现代 Web 技术,在网页应用中实现和原生应用相近的用户体验。

    14 引用 • 69 回帖 • 131 关注
  • abitmean

    有点意思就行了

    23 关注
  • Kafka

    Kafka 是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是现代系统中许多功能的基础。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。

    35 引用 • 35 回帖
  • Python

    Python 是一种面向对象、直译式电脑编程语言,具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。

    536 引用 • 672 回帖
  • Webswing

    Webswing 是一个能将任何 Swing 应用通过纯 HTML5 运行在浏览器中的 Web 服务器,详细介绍请看 将 Java Swing 应用变成 Web 应用

    1 引用 • 15 回帖 • 635 关注
  • 宕机

    宕机,多指一些网站、游戏、网络应用等服务器一种区别于正常运行的状态,也叫“Down 机”、“当机”或“死机”。宕机状态不仅仅是指服务器“挂掉了”、“死机了”状态,也包括服务器假死、停用、关闭等一些原因而导致出现的不能够正常运行的状态。

    13 引用 • 82 回帖 • 38 关注
  • GitLab

    GitLab 是利用 Ruby 一个开源的版本管理系统,实现一个自托管的 Git 项目仓库,可通过 Web 界面操作公开或私有项目。

    46 引用 • 72 回帖
  • PWL

    组织简介

    用爱发电 (Programming With Love) 是一个以开源精神为核心的民间开源爱好者技术组织,“用爱发电”象征开源与贡献精神,加入组织,代表你将遵守组织的“个人开源爱好者”的各项条款。申请加入:用爱发电组织邀请帖
    用爱发电组织官网:https://programmingwithlove.stackoverflow.wiki/

    用爱发电组织的核心驱动力:

    • 遵守开源守则,体现开源&贡献精神:以分享为目的,拒绝非法牟利。
    • 自我保护:使用适当的 License 保护自己的原创作品。
    • 尊重他人:不以各种理由、各种漏洞进行未经允许的抄袭、散播、洩露;以礼相待,尊重所有对社区做出贡献的开发者;通过他人的分享习得知识,要留下足迹,表示感谢。
    • 热爱编程、热爱学习:加入组织,热爱编程是首当其要的。我们欢迎热爱讨论、分享、提问的朋友,也同样欢迎默默成就的朋友。
    • 倾听:正确并恳切对待、处理问题与建议,及时修复开源项目的 Bug ,及时与反馈者沟通。不抬杠、不无视、不辱骂。
    • 平视:不诋毁、轻视、嘲讽其他开发者,主动提出建议、施以帮助,以和谐为本。只要他人肯努力,你也可能会被昔日小看的人所超越,所以请保持谦虚。
    • 乐观且活跃:你的努力决定了你的高度。不要放弃,多年后回头俯瞰,才会发现自己已经成就往日所仰望的水平。积极地将项目开源,帮助他人学习、改进,自己也会获得相应的提升、成就与成就感。
    1 引用 • 487 回帖 • 5 关注
  • 职场

    找到自己的位置,萌新烦恼少。

    126 引用 • 1699 回帖 • 1 关注
  • SVN

    SVN 是 Subversion 的简称,是一个开放源代码的版本控制系统,相较于 RCS、CVS,它采用了分支管理系统,它的设计目标就是取代 CVS。

    29 引用 • 98 回帖 • 692 关注
  • SEO

    发布对别人有帮助的原创内容是最好的 SEO 方式。

    35 引用 • 200 回帖 • 23 关注
  • 微服务

    微服务架构是一种架构模式,它提倡将单一应用划分成一组小的服务。服务之间互相协调,互相配合,为用户提供最终价值。每个服务运行在独立的进程中。服务于服务之间才用轻量级的通信机制互相沟通。每个服务都围绕着具体业务构建,能够被独立的部署。

    96 引用 • 155 回帖
  • SSL

    SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS 与 SSL 在传输层对网络连接进行加密。

    69 引用 • 190 回帖 • 495 关注
  • 又拍云

    又拍云是国内领先的 CDN 服务提供商,国家工信部认证通过的“可信云”,乌云众测平台认证的“安全云”,为移动时代的创业者提供新一代的 CDN 加速服务。

    21 引用 • 37 回帖 • 512 关注
  • Google

    Google(Google Inc.,NASDAQ:GOOG)是一家美国上市公司(公有股份公司),于 1998 年 9 月 7 日以私有股份公司的形式创立,设计并管理一个互联网搜索引擎。Google 公司的总部称作“Googleplex”,它位于加利福尼亚山景城。Google 目前被公认为是全球规模最大的搜索引擎,它提供了简单易用的免费服务。不作恶(Don't be evil)是谷歌公司的一项非正式的公司口号。

    49 引用 • 192 回帖
  • uTools

    uTools 是一个极简、插件化、跨平台的现代桌面软件。通过自由选配丰富的插件,打造你得心应手的工具集合。

    5 引用 • 13 回帖
  • JWT

    JWT(JSON Web Token)是一种用于双方之间传递信息的简洁的、安全的表述性声明规范。JWT 作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以 JSON 的形式安全的传递信息。

    20 引用 • 15 回帖 • 20 关注