1. 方法引用和invokedynamic

invokedynamic是jvm指令集里面最复杂的一条。本文将从高观点的角度下分析invokedynamic指令是如何实现方法引用(Method reference)的。

具体言之,有这样一个方法引用:

  1. interface Encode {
  2. void encode(Derive person);
  3. }
  4. class Base {
  5. public void encrypt() {
  6. System.out.println("Base::speak");
  7. }
  8. }
  9. class Derive extends Base {
  10. @Override
  11. public void encrypt() {
  12. System.out.println("Derive::speak");
  13. }
  14. }
  15. public class MethodReference {
  16. public static void main(String[] args) {
  17. Encode encode = Base::encrypt;
  18. System.out.println(encode);
  19. }
  20. }

使用javap -verbose MethodReference.class查看对应字节码:

  1. // 常量池
  2. Constant pool:
  3. #1 = Methodref #6.#22 // java/lang/Object."<init>":()V
  4. #2 = InvokeDynamic #0:#27 // #0:encode:()LEncode;
  5. #3 = Fieldref #28.#29 // java/lang/System.out:Ljava/io/PrintStream;
  6. #4 = Methodref #30.#31 // java/io/PrintStream.println:(Ljava/lang/Object;)V
  7. #5 = Class #32 // MethodReference
  8. #6 = Class #33 // java/lang/Object
  9. #7 = Utf8 <init>
  10. #8 = Utf8 ()V
  11. #9 = Utf8 Code
  12. #10 = Utf8 LineNumberTable
  13. #11 = Utf8 LocalVariableTable
  14. #12 = Utf8 this
  15. #13 = Utf8 LMethodReference;
  16. #14 = Utf8 main
  17. #15 = Utf8 ([Ljava/lang/String;)V
  18. #16 = Utf8 args
  19. #17 = Utf8 [Ljava/lang/String;
  20. #18 = Utf8 encode
  21. #19 = Utf8 LEncode;
  22. #20 = Utf8 SourceFile
  23. #21 = Utf8 MethodReference.java
  24. #22 = NameAndType #7:#8 // "<init>":()V
  25. #23 = Utf8 BootstrapMethods
  26. #24 = MethodHandle #6:#34 // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;L
  27. java/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang
  28. /invoke/CallSite;
  29. #25 = MethodType #35 // (LDerive;)V
  30. #26 = MethodHandle #5:#36 // invokevirtual Base.encrypt:()V
  31. #27 = NameAndType #18:#37 // encode:()LEncode;
  32. #28 = Class #38 // java/lang/System
  33. #29 = NameAndType #39:#40 // out:Ljava/io/PrintStream;
  34. #30 = Class #41 // java/io/PrintStream
  35. #31 = NameAndType #42:#43 // println:(Ljava/lang/Object;)V
  36. #32 = Utf8 MethodReference
  37. #33 = Utf8 java/lang/Object
  38. #34 = Methodref #44.#45 // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/Str
  39. ing;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallS
  40. ite;
  41. #35 = Utf8 (LDerive;)V
  42. #36 = Methodref #46.#47 // Base.encrypt:()V
  43. #37 = Utf8 ()LEncode;
  44. #38 = Utf8 java/lang/System
  45. #39 = Utf8 out
  46. #40 = Utf8 Ljava/io/PrintStream;
  47. #41 = Utf8 java/io/PrintStream
  48. #42 = Utf8 println
  49. #43 = Utf8 (Ljava/lang/Object;)V
  50. #44 = Class #48 // java/lang/invoke/LambdaMetafactory
  51. #45 = NameAndType #49:#53 // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Lj
  52. ava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  53. #46 = Class #54 // Base
  54. #47 = NameAndType #55:#8 // encrypt:()V
  55. #48 = Utf8 java/lang/invoke/LambdaMetafactory
  56. #49 = Utf8 metafactory
  57. // 字节码指令
  58. public static void main(java.lang.String[]);
  59. 0: invokedynamic #2, 0 // InvokeDynamic #0:encode:()LEncode;
  60. 5: astore_1
  61. 6: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
  62. 9: aload_1
  63. 10: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
  64. 13: return
  65. // 属性
  66. SourceFile: "MethodReference.java"
  67. InnerClasses:
  68. public static final #51= #50 of #56; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
  69. BootstrapMethods:
  70. 0: #24 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/Method
  71. Type;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  72. Method arguments:
  73. #25 (LDerive;)V
  74. #26 invokevirtual Base.encrypt:()V
  75. #25 (LDerive;)V

使用invokedynamic指令生成encode对象,然后存入局部变量槽#1。接着获取getstatic获取java/lang/System类的out字段,最后局部变量槽#1作为参数压栈,invokevirtual虚函数调用System.outprintln方法。

那么invokedynamic到底是怎么生成encode对象的呢?

1.虚拟机解析

hotspotinvokedynamic指令的解释如下:

  1. CASE(_invokedynamic): {
  2. u4 index = Bytes::get_native_u4(pc+1);
  3. ConstantPoolCacheEntry* cache = cp->constant_pool()->invokedynamic_cp_cache_entry_at(index);
  4. // We are resolved if the resolved_references field contains a non-null object (CallSite, etc.)
  5. // This kind of CP cache entry does not need to match the flags byte, because
  6. // there is a 1-1 relation between bytecode type and CP entry type.
  7. if (! cache->is_resolved((Bytecodes::Code) opcode)) {
  8. CALL_VM(InterpreterRuntime::resolve_from_cache(THREAD, (Bytecodes::Code)opcode),
  9. handle_exception);
  10. cache = cp->constant_pool()->invokedynamic_cp_cache_entry_at(index);
  11. }
  12. Method* method = cache->f1_as_method();
  13. if (VerifyOops) method->verify();
  14. if (cache->has_appendix()) {
  15. ConstantPool* constants = METHOD->constants();
  16. SET_STACK_OBJECT(cache->appendix_if_resolved(constants), 0);
  17. MORE_STACK(1);
  18. }
  19. istate->set_msg(call_method);
  20. istate->set_callee(method);
  21. istate->set_callee_entry_point(method->from_interpreted_entry());
  22. istate->set_bcp_advance(5);
  23. // Invokedynamic has got a call counter, just like an invokestatic -> increment!
  24. BI_PROFILE_UPDATE_CALL();
  25. UPDATE_PC_AND_RETURN(0); // I'll be back...
  26. }

使用invokedynamic_cp_cache_entry_at获取常量池对象,然后检查是否已经解析过,如果没有就解析反之复用,然后设置方法字节码,留待后面解释执行。那么,重点是这个解析。我们对照着jvm spec来看。

根据jvm文档的描述,invokedynamic的操作数(operand)指向常量池一个动态调用点描述符(dynamic call site specifier)。

动态调用点描述符是一个CONSTANT_InvokeDynamic_info结构体:

  1. CONSTANT_InvokeDynamic_info {
  2. u1 tag;
  3. u2 bootstrap_method_attr_index;
  4. u2 name_and_type_index;
  5. }
  • tag 表示这个结构体的常量,不用管
  • bootstrap_method_attr_index 启动方法数组
  • name_and_type_index 一个名字+类型的描述字段,就像这样Object p放到虚拟机里面表示是Ljava/lang/Object; p

然后启动方法数组结构是这样:

  1. BootstrapMethods_attribute {
  2. ...
  3. u2 num_bootstrap_methods;
  4. {
  5. u2 bootstrap_method_ref;
  6. u2 num_bootstrap_arguments;
  7. u2 bootstrap_arguments[num_boot]
  8. } bootstrap_methods[num_bootstrap_methods];
  9. }

就是一个数组,每个元素是{指向MethodHandle的索引,启动方法参数个数,启动方法参数}

MethodlHandle是个非常重要的结构,指导了虚拟机对于这个启动方法的解析,先关注一下这个结构:

  1. CONSTANT_MethodHandle_info {
  2. u1 tag;//表示该结构体的常量tag,可以忽略
  3. u1 reference_kind;
  4. u2 reference_index;
  5. }
  • reference_kind是[1,9]的数,它表示这个method handle的类型,这个字段和字节码的行为有关。
  • reference_index 根据reference_kind会指向常量池的不同类型,具体来说
    • reference_kind==1,3,4 指向CONSTANT_Fieldref_info结构,表示一个类的字段
    • reference_kind==5,8,指向CONSTANT_Methodref_info,表示一个类的方法
    • reference_kind==6,7, 同上,只是兼具接口的方法或者类的方法的可能。
    • reference_kind==9,指向CONSTATN_InterfaceMethodref_info,表示一个接口方法

通过invokedynamic,我们可以得

  1. 名字+描述符的表示(由name_and_type_index给出)
  2. 一个启动方法数组(由bootstrap_method_attr_index给出)

2.手动解析

可以手动模拟一下解析,看看最后得到的数据是什么样的。在这个例子中:

  1. 0: invokedynamic #2, 0 //第二个operand总是0

查看常量池#2项:

  1. #2 = InvokeDynamic #0:#27 // #0:encode:()LEncode;
  2. #27 = NameAndType #18:#37 // encode:()LEncode;
  3. BootstrapMethods:
  4. 0: #24 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/Method
  5. Type;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  6. Method arguments:
  7. #25 (LDerive;)V
  8. #26 invokevirtual Base.encrypt:()V
  9. #25 (LDerive;)V

得到的名字+描述符是:Encode.encode(),启动方法数组有一个元素,回忆下之前说的,这个元素构成如下:

  1. {指向MethodHandle的索引,启动方法参数个数,启动方法参数}

这里得到的MethodHandle表示的是LambdaMetafactory.metafactory:

  1. #24 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/Method
  2. Type;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;`

启动方法参数有:

  • #25 (LDerive;)V
  • #26 invokevirtual Base.encrypt:()V
  • #25 (LDerive;)V

3. java.lang.invoke.LambdaMetafactory

先说说LambdaMetafactory有什么用。javadoc给出的解释是:

Facilitates the creation of simple "function objects" that implement one or more interfaces by delegation to a provided MethodHandle, after appropriate type adaptation and partial evaluation of arguments. Typically used as a bootstrap method for invokedynamic call sites, to support the lambda expression and method reference expression features of the Java Programming Language.

When the target of the CallSite returned from this method is invoked, the resulting function objects are instances of a class which implements the interface named by the return type of invokedType, declares a method with the name given by invokedName and the signature given by samMethodType. It may also override additional methods from Object.

LambdaMetafactory方便我们创建简单的"函数对象",这些函数对象通过代理MethodHandle实现了一些接口。

当这个函数返回的CallSite被调用的时候,会产生一个类的实例,该类还实现了一些方法,具体由参数给出

将上面得到的MethodHandle写得更可读就是调用的这个方法:

  1. public static CallSite LambdaMetafactory.metafactory(MethodHandles.Lookup caller,
  2. String invokedName,
  3. MethodType invokedType,
  4. MethodType samMethodType,
  5. MethodHandle implMethod,
  6. MethodType instantiatedMethodType);

六个参数,慢慢来。

3.1 LambdaMetafactory.metafactory()调用前

要知道参数是什么意思,可以从它的调用者来管中窥豹:

  1. static CallSite makeSite(MethodHandle bootstrapMethod,
  2. // Callee information:
  3. String name, MethodType type,
  4. // Extra arguments for BSM, if any:
  5. Object info,
  6. // Caller information:
  7. Class<?> callerClass) {
  8. MethodHandles.Lookup caller = IMPL_LOOKUP.in(callerClass);
  9. CallSite site;
  10. try {
  11. Object binding;
  12. info = maybeReBox(info);
  13. if (info == null) {
  14. binding = bootstrapMethod.invoke(caller, name, type);
  15. } else if (!info.getClass().isArray()) {
  16. binding = bootstrapMethod.invoke(caller, name, type, info);
  17. } else {
  18. Object[] argv = (Object[]) info;
  19. maybeReBoxElements(argv);
  20. switch (argv.length) {
  21. ...
  22. case 3:
  23. binding = bootstrapMethod.invoke(caller, name, type,
  24. argv[0], argv[1], argv[2]);
  25. break;
  26. ...
  27. }
  28. }
  29. //System.out.println("BSM for "+name+type+" => "+binding);
  30. if (binding instanceof CallSite) {
  31. site = (CallSite) binding;
  32. } else {
  33. throw new ClassCastException("bootstrap method failed to produce a CallSite");
  34. }
  35. ...
  36. } catch (Throwable ex) {
  37. ...
  38. }
  39. return site;
  40. }

java.lang.invoke.LambdaMetafactory的调用是通过MethodHandle引发的,所以可能还需要补一下MethodHandle的用法,百度一搜一大堆,javadoc也给出了使用示例:

  1. String s;
  2. MethodType mt; MethodHandle mh;
  3. MethodHandles.Lookup lookup = MethodHandles.lookup();
  4. // mt is (char,char)String
  5. mt = MethodType.methodType(String.class, char.class, char.class);
  6. mh = lookup.findVirtual(String.class, "replace", mt);
  7. s = (String) mh.invoke("daddy",'d','n');
  8. // invokeExact(Ljava/lang/String;CC)Ljava/lang/String;
  9. assertEquals(s, "nanny");

回到源码,关键是这句:

  1. binding = bootstrapMethod.invoke(caller, name, type,
  2. argv[0], argv[1], argv[2]);

argv[0],argv[1],argv[2]分别表示之前启动方法的三个参数

caller即调用者,这里是MethodReference这个类,然后name和type参见下面的详细解释:

  • MethodHandles.Lookup caller 表示哪个类引发了调动
  • String invokedName 表示生成的类的方法名,对应例子的encode
  • MethodType invokedType 表示CallSite的函数签名,其中参数类型表示捕获变量的类型,返回类型是类要实现的接口的名字,对应例子的()Encode,即要生成一个类,这个类没有捕获自由变量(所以参数类为空),然后这个类要实现Encode接口(返回类型为生成的类要实现的接口)

    接下来
  • MethodType samMethodType 表示要实现的方法的函数签名和返回值,对于例子的#25 (LDerive;)V,即实现方法带有一个形参,返回void
  • MethodHandle implMethod 表示实现的方法里面应该调用的函数,对于例子的#26 invokevirtual Base.encrypt:()V,表示调用Base的虚函数encrypt,返回void
  • MethodType instantiatedMethodType 表示调用方法的运行时描述符,如果不是泛型就和samMethodType一样

3.2 LambdaMetafactory.metafactory()调用

源码面前,不是了无秘密吗hhh,点进源码看看这个LambdaMetafactory到底做了什么:

  1. */
  2. public static CallSite metafactory(MethodHandles.Lookup caller,
  3. String invokedName,
  4. MethodType invokedType,
  5. MethodType samMethodType,
  6. MethodHandle implMethod,
  7. MethodType instantiatedMethodType)
  8. throws LambdaConversionException {
  9. AbstractValidatingLambdaMetafactory mf;
  10. mf = new InnerClassLambdaMetafactory(caller, invokedType,
  11. invokedName, samMethodType,
  12. implMethod, instantiatedMethodType,
  13. false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
  14. mf.validateMetafactoryArgs();
  15. return mf.buildCallSite();
  16. }

它什么也没做,做事的是InnerClassLambdaMetafactory.buildCallSite()创建的最后CallSite,那就进一步看看InnerClassLambdaMetafactory.buildCallSite()

  1. @Override
  2. CallSite buildCallSite() throws LambdaConversionException {
  3. // 1. 创建生成的类对象
  4. final Class<?> innerClass = spinInnerClass();
  5. if (invokedType.parameterCount() == 0) {
  6. // 2. 用反射获取构造函数
  7. final Constructor<?>[] ctrs = AccessController.doPrivileged(
  8. new PrivilegedAction<Constructor<?>[]>() {
  9. @Override
  10. public Constructor<?>[] run() {
  11. Constructor<?>[] ctrs = innerClass.getDeclaredConstructors();
  12. if (ctrs.length == 1) {
  13. // The lambda implementing inner class constructor is private, set
  14. // it accessible (by us) before creating the constant sole instance
  15. ctrs[0].setAccessible(true);
  16. }
  17. return ctrs;
  18. }
  19. });
  20. if (ctrs.length != 1) {
  21. throw new LambdaConversionException("Expected one lambda constructor for "
  22. + innerClass.getCanonicalName() + ", got " + ctrs.length);
  23. }
  24. try {
  25. // 3. 创建实例
  26. Object inst = ctrs[0].newInstance();
  27. // 4. 根据实例和samBase(接口类型)生成MethodHandle
  28. // 5. 生成ConstantCallSite
  29. return new ConstantCallSite(MethodHandles.constant(samBase, inst));
  30. }
  31. catch (ReflectiveOperationException e) {
  32. throw new LambdaConversionException("Exception instantiating lambda object", e);
  33. }
  34. } else {
  35. try {
  36. UNSAFE.ensureClassInitialized(innerClass);
  37. return new ConstantCallSite(
  38. MethodHandles.Lookup.IMPL_LOOKUP
  39. .findStatic(innerClass, NAME_FACTORY, invokedType));
  40. }
  41. catch (ReflectiveOperationException e) {
  42. throw new LambdaConversionException("Exception finding constructor", e);
  43. }
  44. }
  45. }

首先它生成一个.class文件,虚拟机默认不会输出,需要下面设置VM option-Djdk.internal.lambda.dumpProxyClasses=.,Dump出虚拟机生成的类我得到的是:

  1. import java.lang.invoke.LambdaForm.Hidden;
  2. // $FF: synthetic class
  3. final class MethodReference$$Lambda$1 implements Encode {
  4. private MethodReference$$Lambda$1() {
  5. }
  6. @Hidden
  7. public void encode(Derive var1) {
  8. ((Base)var1).encrypt();
  9. }
  10. }

该类实现了传来的接口函数(动态类生成,熟悉spring的朋友应该很熟悉)。

回到buildCallSite()源码,它使用MethodHandles.constant(samBase, inst)创建MethdHandle,放到CallSite里面,完成整个LambdaMetafactory的工作。

MethodHandles.constant(samBase, inst)相当于一个总是返回inst的方法。

总结

到这里就结束了整个流程,文章有点长,总结一下:

  1. 虚拟机遇到invokedynamic,开始解析操作数
  2. 根据invokedynamic #0:#27获取到启动方法(#0)和一个名字+描述符(#27)

    其中启动方法是
  1. BootstrapMethods:
  2. 0: #24 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/Method
  3. Type;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  4. Method arguments:
  5. #25 (LDerive;)V
  6. #26 invokevirtual Base.encrypt:()V
  7. #25 (LDerive;)V

名字+描述符

  1. #27 = NameAndType #18:#37 // encode:()LEncode;
  1. 启动方法指向LambdaMetafactory.metafactory,但是不会直接调用而是通过MethdHandle间接调用。调用位置位于CallSite.makeCallSite()
  2. LambdaMetafactory.metafactory()其实使用InnerClassLambdaMetafactory.buildCallSite()创建了最后的CallSite
  3. buildCallSite()会创建一个.class,
  4. buildCallSite()会向最后的CallSite里面放入一个可调用的MethdHandle
  5. 这个MethodHandle指向的是一个总是返回刚刚创建的.class类的实例的方法,由MethodHandles.constant(samBase, inst)完成
  6. 最后,用invokevirtual调用CallSite里面的MethdHandle,返回.class类的示例,即inst,即new MethodReference$$Lambda$1

invokedynamic字节码指令的更多相关文章

  1. jvm理论-字节码指令

    Java虚拟机的指令由一个字节长度的.代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需参数(称为操作数,Operands)而构成. 基本数据类型 1.除了l ...

  2. Java字节码指令

    1. 简介 Java虚拟机的指令由一个字节长度的.代表着某种特定操作含义的数字(称为操作码)以及跟随其后的零至多个代表此操作所需参数(称为操作数)而构成. 由于Java虚拟机采用面向操作数栈而不是寄存 ...

  3. Java方法调用的字节码指令学习

    Java1.8环境下,我们在编写程序时会进行各种方法调用,虚拟机在执行这些调用的时候会用到不同的字节码指令,共有如下五种: invokespecial:调用私有实例方法: invokestatic:调 ...

  4. Java虚拟机-字节码指令

    目录 字节码指令 字节码与数据类型 加载和存储指令 运算指令 类型转换指令 对象创建与访问指令 操作数栈管理指令 控制转移指令 方法调用和返回指令 异常处理指令 同步指令 字节码指令 Java虚拟机的 ...

  5. JVM学习第三天(JVM的执行子系统)之字节码指令

    早上看了Class类文件结构,晚上继续来看字节码指令,毕竟谁也不是一步登天的(说白了还是穷); 字节码指令 Java虚拟机的指令由一个字节长度的.代表着某种特定操作含义的数字(称为操作码,Opcode ...

  6. Class 文件结构及深入字节码指令

    JVM的无关性 与平台无关性是建立在操作系统上,虚拟机厂商提供了许多可以运行在各种不同平台的虚拟机,它们都可以载入和执行字节码,从而实现程序的“一次编写,到处运行” https://www.oracl ...

  7. 硬核万字长文,深入理解 Java 字节码指令(建议收藏)

    Java 字节码指令是 JVM 体系中非常难啃的一块硬骨头,我估计有些读者会有这样的疑惑,"Java 字节码难学吗?我能不能学会啊?" 讲良心话,不是我谦虚,一开始学 Java 字 ...

  8. [四] java虚拟机JVM编译器编译代码简介 字节码指令实例 代码到底编译成了什么形式

      前言简介   前文已经对虚拟机进行过了简单的介绍,并且也对class文件结构,以及字节码指令进行了详尽的说明 想要了解JVM的运行机制,以及如何优化你的代码,你还需要了解一下,java编译器到底是 ...

  9. JVM总括三-字节码、字节码指令、JIT编译执行

    JVM总括三-字节码.字节码指令.JIT编译执行 目录:JVM总括:目录 java文件编译后的class文件,java跨平台的中间层,JVM通过对字节码的解释执行(执行模式,还有JIT编译执行,下面讲 ...

随机推荐

  1. 聊聊 Spring Boot 2.x 那些事儿

    本文目录: 即将的 Spring 2.0 - Spring 2.0 是什么 - 开发环境和 IDE - 使用 Spring Initializr 快速入门 Starter 组件 - Web:REST ...

  2. 为何没有asia/beijing时区?

    Asia/Beijing 这个时区是消失了么? 大约1小时 ago @tinyfool 对啊,我就奇怪为什么北京时间就要用上海和成都... 大约1小时 ago @tinyfool @CatChen我所 ...

  3. js中闭包来实现bind函数的一段代码的分析

    今天研究了一下bind函数,发现apply和call还可以有这样的妙用,顺便巩固复习了闭包. var first_object = { num: 42 }; var second_object = { ...

  4. 打包前端WebSite到Go程序

    打包前端WebSite到Go程序 Coolpy5发布在即,新版本要求服务端程序只是一个运行文件,经历了go的template无数坑后,最后还是放弃了,所以还是要把前端独立开发一个纯前端程序,但是go程 ...

  5. bzoj 3343 教主的魔法 分块

    修改直接对整块打标记,两边暴力. 查询需要保证每个整块有序,所以在修改时排序就好啦 #include<cstdio> #include<cstring> #include< ...

  6. BZOJ_1040_[ZJOI2008]骑士_树形DP

    BZOJ_1040_[ZJOI2008]骑士_树形DP 题意: Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英.他们劫富济贫,惩恶扬善,受到社会各 界的赞扬.最近发生了一件可怕的事情,邪 ...

  7. istio添加Fluentd

    这个教程展示了istio如何自定义日志格式,并且将其发送给fluent.Fluentd 是一个开源的日志收集器,支持多种数据输出并且有一个可插拔架构.Elasticsearch是一个流行的后端日志记录 ...

  8. JDK10安装配置详解

    JDK10安装配置详解 1. 下载jdk10 1.1 官网下载jdk7的软件包:        地址:http://www.oracle.com/technetwork/java/javase/dow ...

  9. Docker 集群

    1.  理解swarm swarm(译:集群) 一个swarm是一组运行着Docker的机器,它们一起加入到一个集群.swarm中的机器既可以是物理机,也可以是虚拟机.在加入到一个swarm后,每台机 ...

  10. 啥?客户叫在DataGridView的左上角添加CheckBox?

    效果图是这样的,如何把CheckBox放到左上角是最重要的. 添加方法 InitColumnInfo() 方法,代码如下. private void InitColumnInfo() { ; Data ...