JDK8动态代理源码分析

动态代理的基本使用就不详细介绍了:

例子:

  1. class proxyed implements pro{
  2. @Override
  3. public void text() {
  4. System.err.println("本方法");
  5. }
  6. }
  7.  
  8. interface pro {
  9. void text();
  10. }
  11.  
  12. public class JavaProxy implements InvocationHandler {
  13. private Object source;
  14. public JavaProxy(Object source) {
  15. super();
  16. this.source = source;
  17. }
  18. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  19. System.out.println("before");
  20. Object invoke = method.invoke(source, args);
  21. System.out.println("after");
  22. return invoke;
  23. }
  24. public Object getProxy(){
  25. return Proxy.newProxyInstance(getClass().getClassLoader(), source.getClass().getInterfaces(), this);
  26. }
  27. public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
  28. //第一种,自己写
  29. //1.设置saveGeneratedFiles值为true则生成 class字节码文件方便分析
  30. System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
  31. //2.获取动态代理类
  32. Class proxyClazz = Proxy.getProxyClass(pro.class.getClassLoader(),pro.class);
  33. //3.获得代理类的构造函数,并传入参数类型InvocationHandler.class
  34. Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
  35. //4.通过构造函数来创建动态代理对象,将自定义的InvocationHandler实例传入
  36. pro iHello = (pro) constructor.newInstance(new JavaProxy(new proxyed()));
  37. //5.通过代理对象调用目标方法
  38. iHello.text();
  39. //第二种,调用JDK提供的方法,实现了2~4步
  40. Proxy.newProxyInstance(JavaProxy.class.getClassLoader(),proxyed.class.getInterfaces(),new JavaProxy(new proxyed()));
  41. }
  42. }

入口:newProxyInstance

  1. public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
  2. //Objects.requireNonNull 判空方法,之后所有的单纯的判断null并抛异常,都是此方法
  3. Objects.requireNonNull(h);
  4. //clone 类实现的所有接口
  5. final Class<?>[] intfs = interfaces.clone();
  6. //获取当前系统安全接口
  7. final SecurityManager sm = System.getSecurityManager();
  8. if (sm != null) {
  9. //Reflection.getCallerClass返回调用该方法的方法的调用类;loader:接口的类加载器
  10. //进行包访问权限、类加载器权限等检查
  11. checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
  12. }
  13.  
  14. /*
  15. * Look up or generate the designated proxy class.
  16. * 查找或生成代理类
  17. */
  18. Class<?> cl = getProxyClass0(loader, intfs);
  19.  
  20. /*
  21. * Invoke its constructor with the designated invocation handler.
  22. * 使用指定的调用处理程序调用它的构造函数
  23. */
  24. try {
  25. if (sm != null) {
  26. checkNewProxyPermission(Reflection.getCallerClass(), cl);
  27. }
  28. //获取构造
  29. final Constructor<?> cons = cl.getConstructor(constructorParams);
  30. final InvocationHandler ih = h;
  31. if (!Modifier.isPublic(cl.getModifiers())) {
  32. AccessController.doPrivileged(new PrivilegedAction<Void>() {
  33. public Void run() {
  34. cons.setAccessible(true);
  35. return null;
  36. }
  37. });
  38. }
  39. //返回 代理对象
  40. return cons.newInstance(new Object[]{h});
  41. } catch (IllegalAccessException|InstantiationException e) {
  42. throw new InternalError(e.toString(), e);
  43. } catch (InvocationTargetException e) {
  44. Throwable t = e.getCause();
  45. if (t instanceof RuntimeException) {
  46. throw (RuntimeException) t;
  47. } else {
  48. throw new InternalError(t.toString(), t);
  49. }
  50. } catch (NoSuchMethodException e) {
  51. throw new InternalError(e.toString(), e);
  52. }
  53. }

  从上面的分析中可以看出,newProxyInstance帮我们执行了生成代理类----获取构造器----生成代理对象这三步;

  我们重点分析生成代理类

getProxyClass0

  1.   /**
  2. * a cache of proxy classes:动态代理类的弱缓存容器
  3. * KeyFactory:根据接口的数量,映射一个最佳的key生成函数,其中表示接口的类对象被弱引用;也就是key对象被弱引用继承自WeakReference(key0、key1、key2、keyX),保存接口密钥(hash值)
  4. * ProxyClassFactory:生成动态类的工厂
  5. * 注意,两个都实现了BiFunction<ClassLoader, Class<?>[], Object>接口
  6. */
  7. private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
  8.  
  9. /**
  10. * Generate a proxy class. Must call the checkProxyAccess method
  11. * to perform permission checks before calling this.
  12. * 生成代理类,调用前必须进行 checkProxyAccess权限检查,所以newProxyInstance进行了权限检查
  13. */
  14. private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
  15. //实现接口的最大数量<65535;谁写的类能实现这么多接口
  16. if (interfaces.length > 65535) {
  17. throw new IllegalArgumentException("interface limit exceeded");
  18. }
  19.  
  20. // If the proxy class defined by the given loader implementing
  21. // the given interfaces exists, this will simply return the cached copy;
  22. // otherwise, it will create the proxy class via the ProxyClassFactory
  23. // 如果缓存中有,就直接返回,否则会生成
  24. return proxyClassCache.get(loader, interfaces);
  25. }

proxyClassCache.get

  1. public V get(K key, P parameter) {
  2. //key:类加载器;parameter:接口数组
  3. Objects.requireNonNull(parameter);
  4. //清除已经被GC回收的弱引用
  5. expungeStaleEntries();
  6.  
  7. //CacheKey弱引用类,refQueue已经被回收的弱引用队列;构建一个CacheKey
  8. Object cacheKey = CacheKey.valueOf(key, refQueue);
  9.  
  10. //map一级缓存,获取valuesMap二级缓存
  11. ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
  12. if (valuesMap == null) {
  13. ConcurrentMap<Object, Supplier<V>> oldValuesMap
  14. = map.putIfAbsent(cacheKey,
  15. valuesMap = new ConcurrentHashMap<>());
  16. if (oldValuesMap != null) {
  17. valuesMap = oldValuesMap;
  18. }
  19. }
  20.  
  21. // subKeyFactory类型是KeyFactory,apply返回表示接口的key
  22. Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
  23. //Factory 实现了supplier,我们实际是获取缓存中的Factory,调用其get方法
  24. Supplier<V> supplier = valuesMap.get(subKey);
  25. Factory factory = null;
  26.  
  27. //下面用到了 CAS+重试 实现的多线程安全的 非阻塞算法
  28. while (true) {
  29. if (supplier != null) {
  30. // 只需要知道,最终会调用get方法,此supplier可能是缓存中取出来的,也可能是Factory新new出来的
  31. V value = supplier.get();
  32. if (value != null) {
  33. return value;
  34. }
  35. }
  36. // else no supplier in cache
  37. // or a supplier that returned null (could be a cleared CacheValue
  38. // or a Factory that wasn't successful in installing the CacheValue)
  39.  
  40. // lazily construct a Factory
  41. if (factory == null) {
  42. factory = new Factory(key, parameter, subKey, valuesMap);
  43. }
  44.  
  45. if (supplier == null) {
  46. supplier = valuesMap.putIfAbsent(subKey, factory);
  47. if (supplier == null) {
  48. // successfully installed Factory
  49. supplier = factory;
  50. }
  51. // else retry with winning supplier
  52. } else {
  53. if (valuesMap.replace(subKey, supplier, factory)) {
  54. // successfully replaced
  55. // cleared CacheEntry / unsuccessful Factory
  56. // with our Factory
  57. supplier = factory;
  58. } else {
  59. // retry with current supplier
  60. supplier = valuesMap.get(subKey);
  61. }
  62. }
  63. }
  64. }

supplier.get

  这个方法中会调用ProxyClassFactory的apply方法,就不过多介绍

ProxyClassFactory.apply

  1. public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
  2.  
  3. Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
  4. for (Class<?> intf : interfaces) {
  5. /*
  6. * Verify that the class loader resolves the name of this interface to the same Class object.
  7. * 类加载器和接口名解析出的是同一个
  8. */
  9. Class<?> interfaceClass = null;
  10. try {
  11. interfaceClass = Class.forName(intf.getName(), false, loader);
  12. } catch (ClassNotFoundException e) {
  13. }
  14. if (interfaceClass != intf) {
  15. throw new IllegalArgumentException( intf + " is not visible from class loader");
  16. }
  17. /*
  18. * Verify that the Class object actually represents an interface.
  19. * 确保是一个接口
  20. */
  21. if (!interfaceClass.isInterface()) {
  22. throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface");
  23. }
  24. /*
  25. * Verify that this interface is not a duplicate.
  26. * 确保接口没重复
  27. */
  28. if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
  29. throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName());
  30. }
  31. }
  32.  
  33. String proxyPkg = null; // package to define proxy class in
  34. int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
  35. /*
  36. * Record the package of a non-public proxy interface so that the proxy class will be defined in the same package.
  37. * Verify that all non-public proxy interfaces are in the same package.
  38. * 验证所有非公共的接口在同一个包内;公共的就无需处理
  39. */
  40. for (Class<?> intf : interfaces) {
  41. int flags = intf.getModifiers();
  42. if (!Modifier.isPublic(flags)) {
  43. accessFlags = Modifier.FINAL;
  44. String name = intf.getName();
  45. int n = name.lastIndexOf('.');
  46. String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
  47. if (proxyPkg == null) {
  48. proxyPkg = pkg;
  49. } else if (!pkg.equals(proxyPkg)) {
  50. throw new IllegalArgumentException( "non-public interfaces from different packages");
  51. }
  52. }
  53. }
  54. if (proxyPkg == null) {
  55. // if no non-public proxy interfaces, use com.sun.proxy package
  56. proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
  57. }
  58. /*
  59. * Choose a name for the proxy class to generate.
  60. * proxyClassNamePrefix = $Proxy
  61. * nextUniqueNumber 是一个原子类,确保多线程安全,防止类名重复,类似于:$Proxy0,$Proxy1......
  62. */
  63. long num = nextUniqueNumber.getAndIncrement();
  64. String proxyName = proxyPkg + proxyClassNamePrefix + num;
  65. /*
  66. * Generate the specified proxy class.
  67. * 生成类字节码的方法:重点
  68. */
  69. byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags);
  70. try {
  71. return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
  72. } catch (ClassFormatError e) {
  73. /*
  74. * A ClassFormatError here means that (barring bugs in the
  75. * proxy class generation code) there was some other
  76. * invalid aspect of the arguments supplied to the proxy
  77. * class creation (such as virtual machine limitations
  78. * exceeded).
  79. */
  80. throw new IllegalArgumentException(e.toString());
  81. }
  82. }

ProxyGenerator.generateProxyClass

  1. public static byte[] generateProxyClass(final String name, Class<?>[] interfaces, int accessFlags) {
  2. ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
  3. //真正生成字节码的方法
  4. final byte[] classFile = gen.generateClassFile();
  5. //如果saveGeneratedFiles为true 则生成字节码文件,所以在开始我们要设置这个参数
  6. //当然,也可以通过返回的bytes自己输出
  7. if (saveGeneratedFiles) {
  8. java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Void>() {
  9. public Void run() {
  10. try {
  11. int i = name.lastIndexOf('.');
  12. Path path;
  13. if (i > 0) {
  14. Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar));
  15. Files.createDirectories(dir);
  16. path = dir.resolve(name.substring(i+1, name.length()) + ".class");
  17. } else {
  18. path = Paths.get(name + ".class");
  19. }
  20. Files.write(path, classFile);
  21. return null;
  22. } catch (IOException e) {
  23. throw new InternalError( "I/O exception saving generated file: " + e);
  24. }
  25. }
  26. });
  27. }
  28. return classFile;
  29. }

最终方法

  

  1. private byte[] generateClassFile() {
  2. /* ============================================================
  3. * Step 1: Assemble ProxyMethod objects for all methods to generate proxy dispatching code for.
  4. * 步骤1:为所有方法生成代理调度代码,将代理方法对象集合起来。
  5. */
  6. //增加 hashcode、equals、toString方法
  7. addProxyMethod(hashCodeMethod, Object.class);
  8. addProxyMethod(equalsMethod, Object.class);
  9. addProxyMethod(toStringMethod, Object.class);
  10. //增加接口方法
  11. for (Class<?> intf : interfaces) {
  12. for (Method m : intf.getMethods()) {
  13. addProxyMethod(m, intf);
  14. }
  15. }
  16.  
  17. /*
  18. * 验证方法签名相同的一组方法,返回值类型是否相同;意思就是重写方法要方法签名和返回值一样
  19. */
  20. for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
  21. checkReturnTypes(sigmethods);
  22. }
  23.  
  24. /* ============================================================
  25. * Step 2: Assemble FieldInfo and MethodInfo structs for all of fields and methods in the class we are generating.
  26. * 为类中的方法生成字段信息和方法信息
  27. */
  28. try {
  29. //增加构造方法
  30. methods.add(generateConstructor());
  31. for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
  32. for (ProxyMethod pm : sigmethods) {
  33. // add static field for method's Method object
  34. fields.add(new FieldInfo(pm.methodFieldName,
  35. "Ljava/lang/reflect/Method;",
  36. ACC_PRIVATE | ACC_STATIC));
  37. // generate code for proxy method and add it
  38. methods.add(pm.generateMethod());
  39. }
  40. }
  41. //增加静态初始化信息
  42. methods.add(generateStaticInitializer());
  43. } catch (IOException e) {
  44. throw new InternalError("unexpected I/O Exception", e);
  45. }
  46.  
  47. if (methods.size() > 65535) {
  48. throw new IllegalArgumentException("method limit exceeded");
  49. }
  50. if (fields.size() > 65535) {
  51. throw new IllegalArgumentException("field limit exceeded");
  52. }
  53.  
  54. /* ============================================================
  55. * Step 3: Write the final class file.
  56. * 步骤3:编写最终类文件
  57. */
  58. /*
  59. * Make sure that constant pool indexes are reserved for the following items before starting to write the final class file.
  60. * 在开始编写最终类文件之前,确保为下面的项目保留常量池索引。
  61. */
  62. cp.getClass(dotToSlash(className));
  63. cp.getClass(superclassName);
  64. for (Class<?> intf: interfaces) {
  65. cp.getClass(dotToSlash(intf.getName()));
  66. }
  67.  
  68. /*
  69. * Disallow new constant pool additions beyond this point, since we are about to write the final constant pool table.
  70. * 设置只读,在这之前不允许在常量池中增加信息,因为要写常量池表
  71. */
  72. cp.setReadOnly();
  73.  
  74. ByteArrayOutputStream bout = new ByteArrayOutputStream();
  75. DataOutputStream dout = new DataOutputStream(bout);
  76.  
  77. try {
  78. // u4 magic;
  79. dout.writeInt(0xCAFEBABE);
  80. // u2 次要版本;
  81. dout.writeShort(CLASSFILE_MINOR_VERSION);
  82. // u2 主版本
  83. dout.writeShort(CLASSFILE_MAJOR_VERSION);
  84.  
  85. cp.write(dout); // (write constant pool)
  86.  
  87. // u2 访问标识;
  88. dout.writeShort(accessFlags);
  89. // u2 本类名;
  90. dout.writeShort(cp.getClass(dotToSlash(className)));
  91. // u2 父类名;
  92. dout.writeShort(cp.getClass(superclassName));
  93. // u2 接口;
  94. dout.writeShort(interfaces.length);
  95. // u2 interfaces[interfaces_count];
  96. for (Class<?> intf : interfaces) {
  97. dout.writeShort(cp.getClass(
  98. dotToSlash(intf.getName())));
  99. }
  100. // u2 字段;
  101. dout.writeShort(fields.size());
  102. // field_info fields[fields_count];
  103. for (FieldInfo f : fields) {
  104. f.write(dout);
  105. }
  106. // u2 方法;
  107. dout.writeShort(methods.size());
  108. // method_info methods[methods_count];
  109. for (MethodInfo m : methods) {
  110. m.write(dout);
  111. }
  112. // u2 类文件属性:对于代理类来说没有类文件属性;
  113. dout.writeShort(0); // (no ClassFile attributes for proxy classes)
  114.  
  115. } catch (IOException e) {
  116. throw new InternalError("unexpected I/O Exception", e);
  117. }
  118.  
  119. return bout.toByteArray();
  120. }

生成的字节码反编译

  

  1. final class $Proxy0 extends Proxy implements pro {
  2. //fields
  3. private static Method m1;
  4. private static Method m2;
  5. private static Method m3;
  6. private static Method m0;
  7.  
  8. public $Proxy0(InvocationHandler var1) throws {
  9. super(var1);
  10. }
  11.  
  12. public final boolean equals(Object var1) throws {
  13. try {
  14. return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
  15. } catch (RuntimeException | Error var3) {
  16. throw var3;
  17. } catch (Throwable var4) {
  18. throw new UndeclaredThrowableException(var4);
  19. }
  20. }
  21.  
  22. public final String toString() throws {
  23. try {
  24. return (String)super.h.invoke(this, m2, (Object[])null);
  25. } catch (RuntimeException | Error var2) {
  26. throw var2;
  27. } catch (Throwable var3) {
  28. throw new UndeclaredThrowableException(var3);
  29. }
  30. }
  31.  
  32. public final void text() throws {
  33. try {
  34. //实际就是调用代理类的invoke方法
  35. super.h.invoke(this, m3, (Object[])null);
  36. } catch (RuntimeException | Error var2) {
  37. throw var2;
  38. } catch (Throwable var3) {
  39. throw new UndeclaredThrowableException(var3);
  40. }
  41. }
  42.  
  43. public final int hashCode() throws {
  44. try {
  45. return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
  46. } catch (RuntimeException | Error var2) {
  47. throw var2;
  48. } catch (Throwable var3) {
  49. throw new UndeclaredThrowableException(var3);
  50. }
  51. }
  52.  
  53. static {
  54. try {
  55. //这里每个方法对象 和类的实际方法绑定
  56. m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
  57. m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
  58. m3 = Class.forName("spring.commons.api.study.CreateModel.pro").getMethod("text", new Class[0]);
  59. m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
  60. } catch (NoSuchMethodException var2) {
  61. throw new NoSuchMethodError(var2.getMessage());
  62. } catch (ClassNotFoundException var3) {
  63. throw new NoClassDefFoundError(var3.getMessage());
  64. }
  65. }
  66. }

java 1.8 动态代理源码分析的更多相关文章

  1. JDK7动态代理源码分析

    IObject proxy = (IObject) Proxy.newProxyInstance(IObject.class.getClassLoader(), new Class[]{IObject ...

  2. 动态代理学习(二)JDK动态代理源码分析

    上篇文章我们学习了如何自己实现一个动态代理,这篇文章我们从源码角度来分析下JDK的动态代理 先看一个Demo: public class MyInvocationHandler implements ...

  3. jdk 动态代理源码分析

    闲来无事,撸撸源码 使用方法 直接看代码吧.. package com.test.demo.proxy; import java.lang.reflect.InvocationHandler; imp ...

  4. JDK动态代理源码分析

    先抛出一个问题,JDK的动态代理为什么不支持对实现类的代理,只支持接口的代理??? 首先来看一下如何使用JDK动态代理.JDK提供了Java.lang.reflect.Proxy类来实现动态代理的,可 ...

  5. 设计模式之JDK动态代理源码分析

    这里查看JDK1.8.0_65的源码,通过debug学习JDK动态代理的实现原理 大概流程 1.为接口创建代理类的字节码文件 2.使用ClassLoader将字节码文件加载到JVM 3.创建代理类实例 ...

  6. java动态代理源码解析

    众所周知,java动态代理同反射原理一直是许多框架的底层实现,之前一直没有时间来分析动态代理的底层源码,现结合源码分析一下动态代理的底层实现 类和接口 java动态代理的主要类和接口有:java.la ...

  7. 【趣味设计模式系列】之【代理模式2--JDK动态代理源码解析】

    1. 图解 上图主要描述了JDK动态代理的执行过程,下面做详细分析. 2. Proxy源码分析 上一篇,在使用JDK动态代理的时候,借助于Proxy类,使用newProxyInstance静态方法,创 ...

  8. 【趣味设计模式系列】之【代理模式3--Cglib动态代理源码解析】

    1. 图解 上图主要描述了Cglib动态代理的主要执行过程,下面做详细分析,以下源码使用的Cglib版本为3.2.12. 2. Enhancer源码分析 public Object create() ...

  9. JDK动态代理源码学习

    继上一篇博客设计模式之代理模式学习之后http://blog.csdn.net/u014427391/article/details/75115928,本博客介绍JDK动态代理的实现原理,学习一下JD ...

随机推荐

  1. git的使用及常用命令

    一,GIT是什么? git是目前世界上最先进的分布式版本控制系统 Git是分布式版本控制系统,那么它就没有中央服务器的,每个人的电脑就是一个完整的版本库,这样,工作的时候就不需要联网了,因为版本都是在 ...

  2. javaweb项目-医者天下 (Spring+SpringMVC+MyBatis)

    项目下载地址:http://download.csdn.net/detail/qq_33599520/9826683 项目完整结构图: 项目简介: 医者天下项目是一个基于Spring+SpringMV ...

  3. .NET遇上Docker - Docker集成Cron定时运行.NETCore(ConsoleApp)程序.md

    配置项目的Docker支持 对于VS中Docker的配置,依旧重复一些废话. 给项目添加Docker支持,VS2015可以直接使用Docker for VS插件,VS2017在安装时选择容器支持.VS ...

  4. & and &&区别

    &:位逻辑运算: &&:逻辑运算: &&在java中又称为短路,第一个条件是false的话后面就不执行: &是所有条件都执行: System.out.p ...

  5. java 无法连接ftp服务器(500 OOPS: cannot change directory)

    在使用java连接ftp服务器时可能会出现无法连接的情况,检查代码是没有错误的,这时就应该考虑一下服务器端的情况了: 首先用在本地打开命令窗口,输入:ftp ftp服务器IP,窗口会提示你输入用户名密 ...

  6. JS基础——循环很重要

    介绍循环之前,首先要说一下同样很重要的if-else结构,switch-case结构 ①if-else结构 if(判断条件) { 条件为true时执行 } else{ 条件为false时执行 } ②i ...

  7. java并发程序——Excutor

    概述 Excutor这个接口用的不多,但是ThreadPoolExcutor这个就用的比较多了,ThreadPoolExcutor是Excutor的一个实现.Excutor体系难点没有,大部分的关键点 ...

  8. idea: 纯 http 上的双向通信

    纯 http 上的双向通信 最近大概看了下 rxJava 的订阅者模式,然后突发奇想有没有可能用类似的思路实现纯 http 上的双向通信 A 是传统的 http 服务器 B 是普通的客户端,假设我们能 ...

  9. logback配置文件详解

    一:根节点<configuration>包含的属性: scan: 当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true. scanPeriod: 设置监测配置文 ...

  10. PDO(数据访问抽象层)、pdo事务功能和预处理功能---2017-05-05

    之前所学的数据访问都是用mysqli做成类来访问的,但是mysqli这个类只是针对mysql这个数据库的:那么如果访问其他类型的数据库呢? 那么这就用到了PDO(数据访问抽象层). 一.关于PDO基本 ...