精华内容
下载资源
问答
  • java 动态代理实现原理

    千次阅读 2015-07-10 21:08:26
    上篇讲了:java动态代理浅析 这篇讲讲其内部实现原理。 1、相关的类和接口 1.1 java.lang.reflect.Proxy 这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。 Proxy 的...


    上篇讲了:java动态代理浅析  这篇讲讲其内部实现原理。


    1、相关的类和接口

    1.1 java.lang.reflect.Proxy

    这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

    Proxy 的静态方法:

    // 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
    static InvocationHandler getInvocationHandler(Object proxy) 
    
    
    // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
    static Class getProxyClass(ClassLoader loader, Class[] interfaces) 
    
    
    // 方法 3:该方法用于判断指定类对象是否是一个动态代理类
    static boolean isProxyClass(Class cl) 
    
    
    // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
    static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
    loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
    interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
    h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上


    1.2 java.lang.reflect.InvocationHandler

    这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

    每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。

    // 该方法负责集中处理动态代理类上的所有方法调用   
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    
    proxy:  指代我们所代理的那个真实对象
    method:  指代的是我们所要调用真实对象的某个方法的Method对象
    args:  指代的是调用真实对象某个方法时接受的参数</span>


    2、动态代理实现过程

    具体有如下步骤:(最终目的得到动态代理类的实例对象

    1.创建自己的调用处理器:通过实现 InvocationHandler 接口创建自己的调用处理器;

    2.创建动态代理类的类对象:通过为 Proxy 类指定 ClassLoader 对象和一组 interface 以及调用处理器来创建动态代理类;

    3.获得动态代理类的构造函数通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;

    4.创建动态代理类实例通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。


    动态代理对象创建过程:

    // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    // 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
    InvocationHandler handler = new InvocationHandlerImpl(..); 
    
    // 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
    Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); 
    
    // 通过反射从生成的类对象获得构造函数对象
    Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); 
    
    // 通过构造函数对象创建动态代理类实例
    Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

    实际使用过程更加简单,因为 Proxy 的静态方法 newProxyInstance 已经为我们封装了步骤 2 到步骤 4 的过程,所以简化后的过程如下:

    简化的动态代理对象创建过程
    
    / InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    InvocationHandler handler = new InvocationHandlerImpl(..); 
    
    // 通过 Proxy 直接创建动态代理类实例
    Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, new Class[] { Interface.class }, handler );


    3、Java 动态代理机制的一些特点

    3.1动态生成的代理类本身的一些特点

    3.1.1包

    如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;

    3.1.2类修饰符

    该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;

    3.1.3类名

    格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。

    3.1.4类继承关系

    该类的继承关系如图:


    由图可见,Proxy 类是它的父类,这个规则适用于所有由 Proxy 创建的动态代理类。而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。

    3.2 代理类实例的一些特点

    3.2.1每个实例都会关联一个调用处理器对象

    可以通过 Proxy 提供的静态方法 getInvocationHandler去获得代理类实例的调用处理器对象。

    3.2.2 代理类实例的方法

    在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行,此外,值得注意的是,代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString

    可能的原因有:

    一是因为这些方法为 public 且非 final 类型,能够被代理类覆盖;

    二是因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被分派到委托类执行。

    当代理的一组接口有重复声明的方法且该方法被调用时,代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器,而无论代理类实例是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用,因为在代理类内部无法区分其当前的被引用类型。

    3.2.3 被代理的一组接口有哪些特点

    首先,要注意不能有重复的接口,以避免动态代理类代码生成时的编译错误。

    其次,这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败。

    再次,需被代理的所有非 public 的接口必须在同一个包中,否则代理类生成也会失败。

    最后,接口的数目不能超过 65535,这是 JVM 设定的限制。

    3.2.4 异常处理方面的特点

    从调用处理器接口声明的方法中可以看到理论上它能够抛出任何类型的异常,因为所有的异常都继承于 Throwable 接口,但事实是否如此呢?答案是否定的,原因是我们必须遵守一个继承原则:即子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。所以虽然调用处理器理论上讲能够,但实际上往往受限制,除非父接口中的方法支持抛 Throwable 异常。那么如果在 invoke 方法中的确产生了接口方法声明中不支持的异常,那将如何呢?放心,Java 动态代理类已经为我们设计好了解决方法:它将会抛出 UndeclaredThrowableException 异常。这个异常是一个 RuntimeException 类型,所以不会引起编译错误。通过该异常的 getCause 方法,还可以获得原来那个不受支持的异常对象,以便于错误诊断。


    4、动态代理源码分析

    4.1 简单的动态代理实现

    package dynamic.proxy;  
      
    /** 
     * 目标对象实现的接口,用JDK来生成代理对象一定要实现一个接口 
     */  
    public interface UserService {  
      
        /** 
         * 目标方法  
         */  
        public abstract void add();  
      
    }


    package dynamic.proxy;   
      
    /** 
     * 目标对象 
     */  
    public class UserServiceImpl implements UserService {  
      
        public void add() {  
            System.out.println("--------------------add---------------");  
        }  
    } 

    package dynamic.proxy;   
      
    import java.lang.reflect.InvocationHandler;  
    import java.lang.reflect.Method;  
    import java.lang.reflect.Proxy;  
      
    /** 
     * 实现自己的InvocationHandler 
     */  
    public class MyInvocationHandler implements InvocationHandler {  
          
        // 目标对象   
        private Object target;  
          
        /** 
         * 构造方法 
         * @param target 目标对象  
         */  
        public MyInvocationHandler(Object target) {  
            super();  
            this.target = target;  
        }  
      
      
        /** 
         * 执行目标对象的方法 
         */  
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
              
            // 在目标对象的方法执行之前简单的打印一下  
            System.out.println("------------------before------------------");  
              
            // 执行目标对象的方法  
            Object result = method.invoke(target, args);  
              
            // 在目标对象的方法执行之后简单的打印一下  
            System.out.println("-------------------after------------------");  
              
            return result;  
        }  
      
        /** 
         * 获取目标对象的代理对象 
         * @return 代理对象 
         */  
        public Object getProxy() {  
            return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),   
                    target.getClass().getInterfaces(), this);  
        }  
    }  
    执行结果如下: 
    ------------------before------------------ 
    --------------------add--------------- 
    -------------------after------------------  

    待弄清楚问题:

    1.代理对象是怎么生成的
    2.InvocationHandler的invoke方法是由谁来调用的


    4.2 Proxy类

    Proxy 的重要静态变量

    // 映射表:用于维护类装载器对象到其对应的代理类缓存
    private static Map<ClassLoader, Map<List<String>, Object>> loaderToCache = new WeakHashMap<>(); 
    
    // 标记:用于标记一个动态代理类正在被创建中
    private static Object pendingGenerationMarker = new Object(); 
    
    // 同步表:记录已经被创建的动态代理类类型,主要被方法 isProxyClass 进行相关的判断
    private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap()); 
    
    // 关联的调用处理器引用
    protected InvocationHandler h;

    Proxy 构造方法

    // 由于 Proxy 内部从不直接调用构造函数,所以 private 类型意味着禁止任何调用
    private Proxy() {} 
    
    // 由于 Proxy 内部从不直接调用构造函数,所以 protected 意味着只有子类可以调用
    protected Proxy(InvocationHandler h) {this.h = h;}


    Proxy 静态方法 newProxyInstance:

    public static Object newProxyInstance(ClassLoader loader, 
                Class<?>[] interfaces, 
                InvocationHandler h) 
                throws IllegalArgumentException { 
        
        // 检查 h 为空,否则抛异常
        if (h == null) { 
            throw new NullPointerException(); 
        } 
    
        // 获得与制定类装载器和一组接口相关的代理类类型对象
        Class cl = getProxyClass(loader, interfaces); 
    
        // 通过反射获取构造函数对象并生成代理类实例
        try { 
            // 调用代理对象的构造方法(也就是$Proxy0(InvocationHandler h))
            Constructor cons = cl.getConstructor(constructorParams); 
            // 生成代理类的实例并把MyInvocationHandler的实例传给它的构造方法
            return (Object) cons.newInstance(new Object[] { h }); 
        } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); 
        } catch (IllegalAccessException e) { throw new InternalError(e.toString()); 
        } catch (InstantiationException e) { throw new InternalError(e.toString()); 
        } catch (InvocationTargetException e) { throw new InternalError(e.toString()); 
        } 
    }

    由此可见,动态代理真正的关键是在 getProxyClass 方法,该方法负责为一组接口动态地生成代理类Class(类型)对象。


    Proxy 静态方法 getProxyClass:

    该方法总共可以分为四个步骤

    1.对这组接口进行一定程度的安全检查

    包括检查接口类对象是否对类装载器可见并且与类装载器所能识别的接口类对象是完全相同的,还会检查确保是 interface 类型而不是 class 类型。这个步骤通过一个循环来完成,检查通过后将会得到一个包含所有接口名称的字符串数组,记为 String[] interfaceNames。总体上这部分实现比较直观,所以略去大部分代码,仅保留留如何判断某类或接口是否对特定类装载器可见的相关代码。

    通过 Class.forName 方法判接口的可见性:

    try { 
        // 指定接口名字、类装载器对象,同时制定 initializeBoolean 为 false 表示无须初始化类
        // 如果方法返回正常这表示可见,否则会抛出 ClassNotFoundException 异常表示不可见
        interfaceClass = Class.forName(interfaceName, false, loader); 
    } catch (ClassNotFoundException e) { 
    }

    2.从 loaderToCache 映射表中获取以类装载器对象为关键字所对应的缓存表,如果不存在就创建一个新的缓存表并更新到 loaderToCache。

    缓存表是一个 HashMap 实例,正常情况下它将存放键值对(接口名字列表,动态生成的代理类的类对象引用)。当代理类正在被创建时它会临时保存(接口名字列表,pendingGenerationMarker)。标记 pendingGenerationMarke 的作用是通知后续的同类请求(接口数组相同且组内接口排列顺序也相同)代理类正在被创建,请保持等待直至创建完成。

    private static Map<ClassLoader, Map<List<String>, Object>> loaderToCache = new WeakHashMap<>();

    ClassLoader:类装载器

    Map<List<String>, Object>:List<String> --- 接口名字列表  Object---动态生成的代理类的类对象引用


    缓存表的使用:

    do { 
        // 以接口名字列表作为关键字获得对应 cache 值
        Object value = cache.get(key); 
        if (value instanceof Reference) { 
            proxyClass = (Class) ((Reference) value).get(); 
        } 
        if (proxyClass != null) { 
            // 如果已经创建,直接返回
            return proxyClass; 
        } else if (value == pendingGenerationMarker) { 
            // 代理类正在被创建,保持等待
            try { 
                cache.wait(); 
            } catch (InterruptedException e) { 
            } 
            // 等待被唤醒,继续循环并通过二次检查以确保创建完成,否则重新等待
            continue; 
        } else { 
            // 标记代理类正在被创建
            cache.put(key, pendingGenerationMarker); 
            // break 跳出循环已进入创建过程
            break; 
    } while (true);

    3.动态创建代理类的Class(类)对象

    1.首先是确定代理类所在的包,其原则如前所述,如果都为 public 接口,则包名为空字符串表示顶层包;如果所有非 public 接口都在同一个包,则包名与这些接口的包名相同;如果有多个非 public 接口且不同包,则抛异常终止代理类的生成。

    2.确定了包后,就开始生成代理类的类名,同样如前所述按格式“$ProxyN”生成。

    3.类名也确定了,接下来就是见证奇迹的发生 ——动态生成代理类 (解决了4.1中待弄清楚问题1)

    动态生成代理类

    // 动态地生成代理类的字节码数组
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); 
    try { 
        // 动态地定义新生成的代理类
        proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, 
            proxyClassFile.length); 
    } catch (ClassFormatError e) { 
        throw new IllegalArgumentException(e.toString()); 
    } 
    
    // 把生成的代理类的类对象记录进 proxyClasses 表
    proxyClasses.put(proxyClass, null);

    由此可见,所有的代码生成的工作都由神秘的 ProxyGenerator 所完成了,当你尝试去探索这个类时,你所能获得的信息仅仅是它位于并未公开的 sun.misc 包,有若干常量、变量和方法以完成这个神奇的代码生成的过程,但是 sun 并没有提供源代码以供研读。至于动态类的定义,则由 Proxy 的 native 静态方法 defineClass0 执行。

    4.根据结果更新缓存表

    如果成功则将代理类的类对象引用更新进缓存表,否则清楚缓存表中对应关键值,最后唤醒所有可能的正在等待的线程。


    JDK是怎样动态生成代理类的字节的原理已经一目了然了。再来解决另外一个问题,那就是由谁来调用InvocationHandler的invoke方法的。要解决这个问题就要看一下JDK到底为我们生成了一个什么东西。我们可以import sun.misc.ProxyGenerator,调用 generateProxyClass方法产生binary data,然后写入文件,最后通过反编译工具来查看内部实现原理。

    import dynamic.proxy.UserService;  
    import java.lang.reflect.*;  
      
    public final class $Proxy11 extends Proxy  
        implements UserService  
    {  
      
        // 构造方法,参数就是刚才传过来的MyInvocationHandler类的实例  
        public $Proxy11(InvocationHandler invocationhandler)  
        {  
            super(invocationhandler);  
        }  
      
        public final boolean equals(Object obj)  
        {  
            try  
            {  
                return ((Boolean)super.h.invoke(this, m1, new Object[] {  
                    obj  
                })).booleanValue();  
            }  
            catch(Error _ex) { }  
            catch(Throwable throwable)  
            {  
                throw new UndeclaredThrowableException(throwable);  
            }  
        }  
      
        /** 
         * 这个方法是关键部分 
         */  
        public final void add()  
        {  
            try  
            {  
                // 实际上就是调用MyInvocationHandler的public Object invoke(Object proxy, Method method, Object[] args)方法,第二个问题就解决了  
                super.h.invoke(this, m3, null);  
                return;  
            }  
            catch(Error _ex) { }  
            catch(Throwable throwable)  
            {  
                throw new UndeclaredThrowableException(throwable);  
            }  
        }  
      
        public final int hashCode()  
        {  
            try  
            {  
                return ((Integer)super.h.invoke(this, m0, null)).intValue();  
            }  
            catch(Error _ex) { }  
            catch(Throwable throwable)  
            {  
                throw new UndeclaredThrowableException(throwable);  
            }  
        }  
      
        public final String toString()  
        {  
            try  
            {  
                return (String)super.h.invoke(this, m2, null);  
            }  
            catch(Error _ex) { }  
            catch(Throwable throwable)  
            {  
                throw new UndeclaredThrowableException(throwable);  
            }  
        }  
      
        private static Method m1;  
        private static Method m3;  
        private static Method m0;  
        private static Method m2;  
      
        // 在静态代码块中获取了4个方法:Object中的equals方法、UserService中的add方法、Object中的hashCode方法、Object中toString方法  
        static   
        {  
            try  
            {  
                m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {  
                    Class.forName("java.lang.Object")  
                });  
                m3 = Class.forName("dynamic.proxy.UserService").getMethod("add", new Class[0]);  
                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);  
                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);  
            }  
            catch(NoSuchMethodException nosuchmethodexception)  
            {  
                throw new NoSuchMethodError(nosuchmethodexception.getMessage());  
            }  
            catch(ClassNotFoundException classnotfoundexception)  
            {  
                throw new NoClassDefFoundError(classnotfoundexception.getMessage());  
            }  
        }  
    }  


    美中不足:
    诚然,Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。
    有很多条理由,人们可以否定对 class 代理的必要性,但是同样有一些理由,相信支持 class 动态代理会更美好。接口和类的划分,本就不是很明显,只是到了 Java 中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。
    但是,不完美并不等于不伟大,伟大是一种本质,Java 动态代理就是佐例。



    参考来源:

    java的动态代理机制详解

    Java 动态代理机制分析及扩展,第 1 部分

    JDK动态代理实现原理

    彻底理解JAVA动态代理




    展开全文
  • Apollo配置中心动态生效实现原理

    万次阅读 多人点赞 2019-06-23 12:38:57
    这里写自定义目录标题Spring中的重要概念Spring框架启动过程回顾Apollo原理解析自定义BeanFactoryPostProcessor自定义BeanPostProcessor总结 Spring中的重要概念 在了解Apollo配置中心实现原理之前,我们需要先熟悉...

    Spring中的重要概念

    在了解Apollo配置中心实现原理之前,我们需要先熟悉一下Spring框架中的几个重要的概念:
    1、BeanDefinition
    用于描述Bean的配置信息,Bean配置一般有三种方式:
    (1)XML配置文件
    (2)@Service、@Component等注解
    (3)Java Config方式
    对应的BeanDefinition实现类如下图,Spring容器启动时,会把所有的Bean配置信息转换为BeanDefinition对象。
    在这里插入图片描述
    2、BeanDefinitionRegistry
    BeanDefinition容器,所有的Bean定义都注册在BeanDefinitionRegistry对象中。

    3、PropertySource
    用于存放Spring配置资源信息,例如spring项目中properties或者yaml文件配置信息均会保存在PropertySource对象中。Spring支持使用@PropertySource注解,將配置信息加载到Environment对象中。

    4、ImportBeanDefinitionRegistrar
    ImportBeanDefinitionRegistrar是一个接口,该接口的实现类作用于在Spring解析Bean配置生成BeanDefinition对象阶段。
    在Spring解析Configuration注解时,向Spring容器中增加额外的BeanDefinition。

    5、BeanFactoryPostProcessor
    Bean工厂后置处理器,用于在BeanDefinition对象注册完成后,修改Bean工厂信息,例如增加或者修改BeanDefinition对象。

    6、BeanDefinitionRegistryPostProcessor
    它是一个特殊的BeanFactoryPostProcessor,用于在BeanDefinition对象注册完成后,访问、新增或者修改BeanDefinition信息。

    7、PropertySourcesPlaceholderConfigurer
    它是一个特殊的BeanFactoryPostProcessor,用于解析Bean配置中的${…}参数占位符。

    8、BeanPostProcessor
    Bean后置处理器,bean初始化方法调用前后,执行拦截逻辑,可以对原有的Bean进行包装或者根据标记接口创建代理对象。

    Spring框架启动过程回顾

    Spring框架启动大致会经过以下几个阶段:
    1、解析Bean配置信息,將配置信息转换为BeanDefinition对象,注册到BeanDefinitionRegistry中。

    2、执行所有的BeanFactoryPostProcessor的postProcessBeanFactory()方法对Bean工厂信息进行修改,包括修改或新增BeanDefinition对象。

    注意:如果需要控制BeanFactoryPostProcessor的执行顺序需要实现PriorityOrdered接口,getOrder()方法返回的值越小,执行优先级越高。

    3、通过BeanDefinition对象实例化所有Bean,注入依赖。

    4、执行所有BeanPostProcessor对象的postProcessBeforeInitialization()方法。

    5、执行Bean的初始化方法,例如InitializingBean接口的afterPropertiesSet方法,或init-method属性指定的方法。

    执行所有BeanPostProcessor对象的postProcessAfterInitialization()方法

    Apollo原理解析

    Apollo框架使用非常简单,如果是Spring Boot项目,只需要在启动类上增加@EnableApolloConfig注解即可。例如:

    @SpringBootApplication
    @EnableApolloConfig
    public class Application {
    
        public static void main(String[] args) {
            ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
            SpringApplication.run(Application.class, args);
        }
    }
    

    那么@EnableApolloConfig注解到底做了什么事情了,我们可以看下EnableApolloConfig注解的定义,代码如下:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Import(ApolloConfigRegistrar.class)
    public @interface EnableApolloConfig {
      /**
       * Apollo namespaces to inject configuration into Spring Property Sources.
       */
      String[] value() default {ConfigConsts.NAMESPACE_APPLICATION};
    
      /**
       * The order of the apollo config, default is {@link Ordered#LOWEST_PRECEDENCE}, which is Integer.MAX_VALUE.
       * If there are properties with the same name in different apollo configs, the apollo config with smaller order wins.
       * @return
       */
      int order() default Ordered.LOWEST_PRECEDENCE;
    }
    

    如上面代码所示,在EnableApolloConfig注解中,通过@Import注解导入了一个ApolloConfigRegistrar,接下来我们就来看一下ApolloConfigRegistrar的实现:

    public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar {
      @Override
      public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata
            .getAnnotationAttributes(EnableApolloConfig.class.getName()));
        String[] namespaces = attributes.getStringArray("value");
        int order = attributes.getNumber("order");
        PropertySourcesProcessor.addNamespaces(Lists.newArrayList(namespaces), order);
    
        Map<String, Object> propertySourcesPlaceholderPropertyValues = new HashMap<>();
        // to make sure the default PropertySourcesPlaceholderConfigurer's priority is higher than PropertyPlaceholderConfigurer
        propertySourcesPlaceholderPropertyValues.put("order", 0);
    
        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(),
            PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues);
    
        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesProcessor.class.getName(),
            PropertySourcesProcessor.class);
    
        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(),
            ApolloAnnotationProcessor.class);
    
        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class);
        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class.getName(), SpringValueDefinitionProcessor.class);
    
        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(),
                ApolloJsonValueProcessor.class);
      }
    }
    

    如上面代码所示,ApolloConfigRegistrar实现了ImportBeanDefinitionRegistrar接口,前面有提到过,ImportBeanDefinitionRegistrar接口的实现类作用于在Spring解析Bean配置生成BeanDefinition对象阶段,在Spring解析Configuration注解时,向Spring容器中增加额外的BeanDefinition。

    ApolloConfigRegistrar中注册了几个BeanDefinition,具体如下:
    1、PropertySourcesPlaceholderConfigurer -------->BeanFactoryPostProcessor
    2、PropertySourcesProcessor -------->BeanFactoryPostProcessor
    3、ApolloAnnotationProcessor -------->BeanPostProcessor
    4、SpringValueProcessor -------->BeanFactoryPostProcessor和BeanPostProcessor
    5、SpringValueDefinitionProcessor-------->BeanDefinitionRegistryPostProcessor
    (即BeanFactoryPostProcessor)
    6、ApolloJsonValueProcessor -------->BeanPostProcessor

    这些类要么实现了BeanFactoryPostProcessor接口,要么实现了BeanPostProcessor接口,前面有提到过BeanFactoryPostProcessor和BeanPostProcessor是Spring提供的扩展机制,BeanFactoryPostProcessor一定是在BeanPostProcessor之前执行。

    接下来我们就来看一下这些自定义的BeanFactoryPostProcessor和BeanPostProcessor的执行顺序,以及它们具体做了什么事情。

    自定义BeanFactoryPostProcessor

    1、SpringValueDefinitionProcessor

    对所有的BeanDefinition进行遍历,將属性中包含${…}参数占位符的属性添加到Apollo 属性注册表。Apollo 属性注册表具体结构如下:
    在这里插入图片描述
    2、PropertySourcesProcessor
    (1)根据命名空间从配置中心获取配置信息,创建RemoteConfigRepository和LocalFileConfigRepository对象。RemoteConfigRepository表示远程配置中心资源,LocalFileConfigRepository表示本地缓存配置资源。

    (2)LocalFileConfigRepository对象缓存配置信息到C:\opt\data 或者/opt/data目录。

    (3)RemoteConfigRepository开启HTTP长轮询请求定时任务,默认2s请求一次。

    (4)將本地缓存配置信息转换为PropertySource对象(Apollo自定义了Spring的PropertySource),加载到Spring的Environment对象中。

    (5)將自定义的ConfigPropertySource注册为观察者。一旦RemoteConfigRepository发现远程配置中心信息发生变化,ConfigPropertySource对象会得到通知。

    3、PropertySourcesPlaceholderConfigurer
    加载本地Properties文件,將${…}参数占位符替换为具体的值。

    4、SpringValueProcessor
    仅仅是为了获取SpringValueDefinitionProcessor中获取的 包含${…}参数占位符的BeanDefinition。(从面向对象设计原则的角度,不符合单一责任原则,可以注册到Guice容器里,然后从Guice容器获取。)

    自定义BeanPostProcessor

    5、ApolloJsonValueProcessor
    处理ApolloJsonValue注解,属性或者方法中包含ApolloJsonValue注解的Bean,属性值也会根据配置中心配置的修改发生变化,因此也需要添加到配置中心可配的容器中

    6、ApolloAnnotationProcessor
    处理ApolloConfigChangeListener注解,ApolloConfigChangeListener注解用于注册一个配置变化监听器。

    7、SpringValueProcessor
    处理Spring中的Value注解,將属性或者方法中包含Value注解的Bean信息添加到Apollo属性注册表。

    整个过程如下图所示:
    在这里插入图片描述

    总结

    Apollo配置中心动态生效机制,是基于Http长轮询请求和Spring扩展机制实现的,在Spring容器启动过程中,Apollo通过自定义的BeanPostProcessor和BeanFactoryPostProcessor將参数中包含${…}占位符和@Value注解的Bean注册到Apollo框架中定义的注册表中。然后通过Http长轮询不断的去获取服务端的配置信息,一旦配置发生变化,Apollo会根据变化的配置的Key找到对应的Bean,然后修改Bean的属性,从而实现了配置动态生效的特性。

    需要注意的是,Apollo在配置变化后,只能修改Bean的属性,例如我们数据源的属性发生变化,新创建的Connection对象是没问题的,但是连接池中已经创建的Connection对象相关信息是不能动态修改的,所以依然需要重启应用。

    展开全文
  • 关于MySql的索引原理以及优化原则

    千次阅读 2019-03-27 23:58:26
    在面试或者实际的工作中,我们都会遇到会需要优化MySql的场景,那么优化MySql的原理到底是什么呢。 曾经看过一篇公众号文章,具体阐述了MySql的优化原理,所以我的绝大部分见解是基于这篇文章之上,其他部分是自己...

          在面试或者实际的工作中,我们都会遇到会需要优化MySql的场景,那么优化MySql的原理到底是什么呢。

          曾经看过一篇公众号文章,具体阐述了MySql的优化原理,所以我的绝大部分见解是基于这篇文章之上,其他部分是自己参考了其他的博客包括查询了大量的资料获得的经验。

         首先盗一张图来看下MySql的优化原理吧,方便大家更方便的理解MySql,以下是MySql的运行图片

    根据当前的图片我们可以根据他的执行过程来进行对应的优化操作。

    1.避免使用select *或者尽量使用limit 1这种限量级的语句

      原因:从上图我们可以看到,MySql是的通信协议是“半双工”的,意思就是如果服务器向MySql发出请求之后,MySql必须接收到服务器的所有请求参数以后,才可以对数据进行处理操作,然后再返回给服务器端,也就是说我们不能在边请求的过程中边获得数据。所以当我们查询语句很长的时候,需要设置max_allowed_packet参数,但是如果查询语句真的特别长,服务器端可能会因为无法接受过多数据而导致抛出异常。当我们对MySql发起请求的时候,MySql必须返回我们查询的所有结果,不能只返回前几条结果。因而在实际开发中,尽量保持查询简单且只返回必需的数据,减小通信间数据包的大小和数量是一个非常好的习惯,这也是查询中尽量避免使用 SELECT * 以及加上 LIMIT 限制的原因之一

    2.查询的缓存设置

     原因:从上面的图中可以看到,所有打进MySql的查询第一步都是会从缓存中查询数据,如果缓存命中会直接从缓存中获取数据的。但是并不是一味的设置缓存就能提高我们的查询效率的。既然是缓存,那肯定会有失效的,缓存和失效时都会带来性能上的额外消耗,并且缓存命中的要求比较严格,比如第一次查询和第二次查询时,字段顺序发生改变会导致缓存无法命中;使用MySql的一些函数特性,比如now(),current_date()等一些函数也会导致缓存无法命中。所以当我们的业务场景下缓存带来的收益效果大于其性能消耗时,可以考虑引入缓存的配置。

    3.数据类型优化

    原因:选择数据类型只要遵循小而简单的原则就好,越小的数据类型通常会更快,占用更少的磁盘、内存,处理时需要的CPU周期也更少。越简单的数据类型在计算时需要更少的CPU周期,比如,整型就比字符操作代价低,因而会使用整型来存储ip地址,使用 DATETIME来存储时间,而不是使用字符串

    4.一些优化sql的操作

     比如优化联表查询,在大数据场景下,表与表之间通过一个冗余字段来关联,要比直接使用 JOIN有更好的性能。还有比如用UNION ALL去代替UNION,除非我们需要用到去重。如果没有 ALL关键字,MySQL会给临时表加上 DISTINCT选项,这会导致整个临时表的数据做唯一性检查,这样做的代价非常高

    5.存储引擎的选择

    读操作多并且不考虑事物的情况下可以选择MyISAM,否则的话就只能选择InnoDB,这部分读者可以自己去查询相关资料,网上有很多文章介绍了区别

    5.索引

    索引我放到最后一个说是有原因的,因为索引涉及的东西太多了。

    首先需要考虑一个问题:我们需要在哪个列上加索引,如果单列查询,那索引肯定是加在对应的列上,如果多列索引呢,因为索引是遵循“最左原则”的,索引的字段在最左边才会效率最高。比如我们有 select * from user where name = "urey" and age = "25",我们可以通过执行下面的语句,看哪一列的语句最接近于1那么就可以将此列放至最左边并加入索引

    select count(distinct name)/count(*) as name_selectivity, count(distinct age)/count(*) as age_selectivity,count(*) from user

    关于索引的最左原则:select * from user where name = "urey" and age = "25"  还是这句语句,MySql在查询的时候会先查询 name = “urey” 的所有数据,而后在前者的结果中再查询age = 25的数据,因此如果age 上加入索引的话,效率会很低下。

    关于索引还有一个就是我们常说的为什么索引常用的是B+树呢,他的查找效率肯定是低于HASH的,为什么不用HASH呢。

    原理:因为B+树是多路搜索树,在查找单条数据的时候,哈希查找的时间复杂度为O(1),确实比B+树会快很多,但是B+树的优势在于他的所有数据都存储在子节点之上,并且子节点之间是按照链表顺序相连的,比如我们需要查询20-30的数据的时候,B+树会先从多路中查询20的子节点位置,而到30相当于就是limit10,此时B+树会遍历链表向后查询10个数字,不需要再去从跟节点进行查找,所以在查询多数据情况下时,B+树的威力就体现出来了。

     

    MySql方面自己也没有太多的了解深入,文中可能会涉及许多问题,所以有不对的地方欢迎指出一起讨论。

    展开全文
  • booth 乘法原则 [ 乘法 ]补 低位添加补0 参考判断 00 结尾 原部分积+0 ,然后右移一位 01 结尾 原部分积+【被乘数】补 , 然后右移一位 10 结尾 原部分积+【-被乘数】补, 然后右移一位 11 结尾 原部分积+ 0,然后...

    booth 乘法原则

    [ 乘法 ]补 低位添加补0 参考判断
    00 结尾 原部分积+0 ,然后右移一位
    01 结尾 原部分积+【被乘数】补 , 然后右移一位
    10 结尾 原部分积+【-被乘数】补, 然后右移一位
    11 结尾 原部分积+ 0,然后右移一位

    • 初始状态,部分积全为0
    • 移位时,部分积最高位如果是0就补0 ,如果是1就补1

    提出问题:boot乘法举例
    booth 乘法举例

    个人分析:
    首先,我们得了解一下关于补码的减法,下面的图片是推到过程。或者直接看结论吧!
    结论:从【y】补求【-y】补的法则是:对【y】补包括符号位“求反且最末位加1”,
    即可得到【-y】补

    注意,这里不是我们通常的求补码了,我们平常求补码就是取反加1 但是对于符号位是不取的,因为这是对【-y】求补 这里是连符号位都要取反的
    举例:
    x=-1110 【x】补=10010 然而,【-x】补=01110

    解:
    首先,x=-1110 对于负数,符号位为1 那么x=11110 这里我介绍我的对x求补码的简单方式,从右往左看,找到第一个为1的位置,然后前面除符号位的数字全部取反就是x的补码了,比如样例
    x=11110 按照上面所说 得到1 0 0 1 0 如果不太相信的话,可以用先取反+1的方式验证一下!

    那么,对于【-x】补 就是对【x】补 先全部取反(包括符号位)结果是0 1 1 0 1 然后再加1得到
    0 1 1 1 0 等于上面那个结果,证明完毕!

    补码减法:
    在这里插入图片描述
    booth乘法举例01110 * 11011:
    对于之前那个问题,我自己又推了一遍,我们把补码换为源码验证一下好了:
    0 1 1 1 0 对应源码转化为10进制等于 14
    1 1 0 1 1 对应源码转化为10进制等于 -5
    它们相乘的结果为-70
    然后我们最终结果1 1 1 0 1 1 1 0 1 0 1 转化一下也是-70
    显然,这个乘法原则是正确的

    然后,我想可能这里有一个难点就是对于负数的补码,转化10进制这里可能不知道怎么转化,这里我也有我的方法:
    比如1 1 0 1 1 我们数字位有4位 1 0 1 1 符号位1 代表负 先求1011对应10进制等于11
    然后用2的4次方减去11即16-11=5 然后加个负号就是我们负数补码对应10进制了

    同理,对于最终结果

    1 1 1 0 1 1 1 0 1 0 1
    最后一个1不算,是我们为了计算补进去的
    前面不管有多少个1 只算一个1 相当于符号位了
    中间的0 1 1 1 0 1 0 算出来对应10进制为58 这里有7位
    所以结果就是2的7次方减去58 即128-58=70 然后加符号位的负号 -70 显然也是正确的

    在这里插入图片描述

    学如逆水行舟,不进则退
    
    展开全文
  • 硬件原理图设计规范——基本原则

    千次阅读 2017-12-22 08:37:00
     原理图设计是产品设计的理论基础,设计一份规范的原理图对设计PCB、跟机、做客户资料具有指导性意义,是做好一款产品的基础。原理图设计基本要求: 规范、清晰、准确、易读。&#13;  因此制定此《规范》的目的...
  •  c) 选择一个新的数据点作为新的聚类中心,选择的原则是:距离较大的点,被选取作为聚类中心的概率较大  d) 重复b和c直到选择出k个聚类质心  e) 利用这k个质心来作为初始化质心去运行标准的K-Means算法 4. K...
  • ik中文分词器分词原则原理

    千次阅读 2019-11-06 11:38:43
    L1对应的词元链如下: L11:{张三} L12:{张} L13:{三} L3对应的词元链如下 L31:{的,确实,在理} L32:{的确,实,在理} L33:{的确,实在,理} L34:{的确,实在} L35:{确实,在理} L36:{确实} … smart...
  • 程序设计原则——局部性原理

    千次阅读 2010-08-15 20:52:00
    DRAM动态,一般作为大容量的主存储器每次CPU和主存之间的数据传送都是通过一些列的步骤完成的,这些步骤称为总线事务。读事务从主存传送数据到CPU,写事务从CPU传送数据到主存。局部性:一般较好的程序都有较好的...
  • 1. 加法原则 ( 1 ) 加法原则 ( 不能叠加 的事件才能用 加法原则 | 适用于 分类选取 ) ( 2 ) 乘法法则 ( 相互独立...( 1 ) 习题 1 ( 加法原理 ) ( 2 ) 习题 2 ( 加法原则 乘法原则 综合运用 ) ( 3 ) 习题 3 ( 乘法原则 )
  • 在了解Apollo配置中心实现原理之前,我们需要先熟悉一下Spring框架中的几个重要的概念: BeanDefinition 用于描述Bean的配置信息,Bean配置一般有三种方式: XML配置文件 @Service,@Component等注解 Java ...
  • 动态规划最优化原理与无后效性

    千次阅读 2015-03-31 22:07:42
    原文地址: ... 上面已经介绍了动态规划模型的基本组成,现在需要解决的问题是:什么样的“多阶段决策问题”才可以采用动态规划的方法求解?...(1)动态规划的最优化原理。作为整个过程的最优策略具有如下性
  • 静态联编和动态联编 、虚函数和动态联编 、虚函数的工作原理 、有关虚函数的注意事项 RTTI 运行时类型识别
  • Eureka工作原理

    万次阅读 多人点赞 2019-07-03 10:46:48
    Eureka 工作原理 上节内容为大家介绍了,注册中心 Eureka 产品的使用,以及如何利用 Eureka 搭建单台和集群的注册中心。这节课我们来继续学习 Eureka,了解它的相关概念、工作流程机制等。 Eureka 作为 Spring Cloud...
  • Apollo配置中心动态生效机制,是基于Http长轮询请求和Spring扩展机制实现的,在Spring容器启动过程中,Apollo通过自定义的BeanPostProcessor和BeanFactoryPostProcessor將参数中包含${…}占位符和@Value注解的Bean...
  • 哈夫曼树原理,及构造方法

    万次阅读 多人点赞 2018-08-05 12:13:21
    每个 字符 的 二进制编码 为(从根节点 数到对应的叶子节点,路径上的值拼接起来就是叶子节点字母的应该的编码) 字符 编码 A 10 B 01 C 0011 D 11 E 000 ...
  • 数据库的索引原理 索引作用 索引是用来快速查找特定值的记录。如果没有索引、一般来说执行查询时会遍历...3、在倒排内容中拼接对应的数据地址 4、查询时先拿到倒排表的内容,再取出数据地址,然后在拿到具体的数据 ...
  • Java两种动态代理JDK动态代理和CGLIB动态代理

    万次阅读 多人点赞 2018-08-07 15:33:35
    JDK动态代理 cglib动态代理 测试 代理模式 代理模式是23种设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但是B是...
  • 抽屉原理(鸽巢原理

    千次阅读 2018-08-03 18:56:05
    一、抽屉原理初介绍: 桌上有十个苹果,要把这十个苹果放到九个抽屉里,无论怎样放,我们会发现至少会有一个抽屉里面至少放两个苹果。这一现象就是我们所说的“抽屉原理”。 抽屉原理的一般含义为:“如果每个抽屉...
  • 最大熵原理

    万次阅读 2013-03-20 17:51:41
    最近看到一位高手,说了最大熵原理应用在排名!让我倍感发抖!网上有个人连研究基本步骤都写完了,着实让蛋疼了一小下,就引用一下吧  最大熵原理在1957 年由E.T.Jaynes 提出的  主要思想是,在只掌握关于未知...
  • 面向对象六大原则

    万次阅读 多人点赞 2015-11-30 00:10:44
    1、优化代码的第一步——单一职责原则单一职责原则的英文名称是Single Responsibility Principle,简称SRP。它的定义是:就一个类而言,应该仅有一个引起它变化的原因。简单来说,一个类中应该是一组相关性很高的...
  • 通俗易懂的AI算法原理

    万次阅读 2019-06-27 08:24:55
    我想尽量用直白的语言、较少的数学知识给各位产品经理讲清楚各个算法的原理是什么。 机器学习的过程 机器学习的过程从本质上来说就是通过一堆的训练数据找到一个与理想函数(f)相接近的函数。在理想情况下,对于...
  • Spring Security 工作原理概览

    万次阅读 多人点赞 2019-04-27 08:02:58
    我这里建议采用继承的方式,SimpleUrlAuthenticationSuccessHandler 是默认的处理器,采用继承可以契合里氏替换原则,提高代码的复用性和避免不必要的错误。 投票器 投票器可继承 WebExpressionVoter 或者实现 ...
  • Raft算法原理

    万次阅读 2019-05-17 09:57:23
    关于Raft算法,有两篇经典的论文,一篇是《In search of an Understandable Consensus Algorithm》,这是作者最开始讲述Raft算法原理的论文,但是这篇论文太简单了,很多算法的细节没有涉及到。更详细的论文是...
  • 由浅入深的分析HashMap原理

    万次阅读 2019-12-13 19:45:27
    现在能不能想到点什么了,没错,如果我们把长度换成了10,有一些index的值出现的几率更大,而有一些将永远不会出现(比如这个例子中0111不会出现),这样一来完全不符合hash算法均匀分布的原则,相反,我们把长度...
  • 本文参考了一些书籍的若干章节,比如《Android进阶解密-第9章-JNI原理》、《深入理解Android虚拟机-第4章-分析JNI》、《深入理解Android系统-第2章-分析JNI》、《Android NDK Beginner_'s Guide》等 之所以会参考...
  • 最近一直在研究模板匹配方法的过程,起先一直对模板匹配的过程不能够有一个充分的理解,开始时是对模板序列和样本序列的组织不能够理解,后期又是对其中的关系不能够有清醒的认识,再之后又对Bellman的最优化原理不...
  • 本文本着动手实操(念第一声)的原则,用java实现一个简单的编译器,让读者朋友能一感编译原理的实质,我秉持一个原则,没有代码可实践的计算机理论,都是耍流氓。 编译器作用就是将一种计算机无法理解的文本,转译成...
  • Maven 核心原理

    万次阅读 多人点赞 2016-11-05 11:18:38
    (依赖调节原则: 1. 路径最近者优先; 2. 第一声明者优先.) 更多传递依赖信息可参考: Dependency Mechanism-Transitive Dependencies . 声明一个或者多个项目依赖, 可以包含的元素有: 元素 描述 ...
  • 区块链技术原理

    万次阅读 多人点赞 2016-07-17 17:48:15
    区块链设计者没有为专业的会计记录者预留一个特定的位置,而是希望通过自愿原则来建立一套人人都可以参与记录信息的分布式记账体系,从而将会计责任分散化,由整个网络的所有参与者来共同记录。  区块链中每一...
  • ORM原理

    千次阅读 2015-11-30 16:22:30
    ORM原理 对象关系映射 (Object Relational Mapping ,简称ORM )是一种为了解决面向对象 与关系数据库 存在的互不匹配的现象的技术。简单的说,ORM是通过使用描述对象 和数据库之间映射的元数据 ,将java...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 151,012
精华内容 60,404
关键字:

动态原理对应的原则