首页 文章 精选 留言 我的

精选列表

搜索[计算机组成原理],共10000篇文章
优秀的个人博客,低调大师

刨析SpringIOC及其启动原理

IOC总结 1. IOC概述 >三个问题: > > 1. IOC是什么 > 2. 为什么用它 > 3. 怎么用 1.1 是什么? 两个概念:控制反转,依赖注入 来看一下传统的干活方式:在对象单一职责原则的基础上,一个对象很少有不依赖其他对象而完成自己的工作,所以这个时候就会出现对象之间的依赖。而体现在我们的开发中,就是需要什么对象的时候,就创建什么对象,此时对象创建的控制权在我们自己手里。当对象创建的太多的时候,就会出现一个对象更改,就得更改所有依赖它的对象,耦合性大。自主性体现的同时也出现了对象耦合严重的情况。 这个时候,我们就会思考,能不能我们在用的时候直接拿到这个对象去用,而将创建对象的能力交给第三方,这样我们就不需要关心对象是怎么创建的了。即将自己的控制权交出去。这就是控制反转 这个时候,就会有另一个问题产生了,对象怎么才能直接被我们拿来用呢。对象创建的时候,我们把这个对象注入到这个对象中,然后就可以使用了。这就是依赖注入 另一个问题,**耦合性怎么被解决掉的?**通过控制反转我们仅仅使用了这个对象,如果对象发生了修改,我们仅仅需要修改第三方创建对象的方式即可,这个时候难道还会出现所谓的对象耦合吗?:smile: 完成这些工作的就是IOC容器,它帮助我们创建对象,然后在对象被使用的时候,将对象注入到这个对象中。而由于IOC创建对象是通过反射来创建的,所以其速度不如直接new对象 还不理解???放心,听笔者讲一个故事,笔者最喜欢讲故事了 前段时间,天气逐渐回暖,鉴于家里没有短袖的情况,笔者只能选择购买了。这个时候笔者有两种选择,第一、去生产衣服的厂家直接去买(便宜);第二、去实体店或者网店购买(较昂贵)。之后,由于笔者属于宅男大军的一员,直接网上购物。 这个场景就是一个典型的控制反转的过程。笔者不需要关注衣服怎么生产的,而是仅仅去淘宝(IOC容器)上,寻找自己想要的衣服(对象),然后直接拿过来用即可。但是由于存在中间商赚差价,所以价格更贵(时间更长):see_no_evil: 最后两句话: 控制反转:将自己的控制权交给自己信任的第三方,甲乙之间不存在依赖关系 依赖注入:开放一个端口留给A,然后在需要的时候,将B注入到A中。 1.2 为什么用 在上面,笔者已经很清晰的描述了为什么要使用IOC,主要原因就是由于对象之间的耦合。 1.3 怎么用 1.3.1 XML 通过书写XML配置文件,向容器中添加需要注入的Bean 1.3.2 Annotation 通过@Configuration注解指定配置类。 2. IOC架构 一个图搞定,这个就是IOC的架构思路,这不是其执行流程图。 我们接下来一步一步来解读。 2.1 白话版 在第一章中我们了解了IOC是来帮助我们管理和创建对象的。 这个时候我们需要一个承载我们需要创建信息的容器,即图中的XML或者注解,那么有了我们自己的BeanDefiniton信息以后,我们需要一个接口用来读取这些信息,于是出现了BeanDefinitionReader用来读取我们自己的Bean信息。 那么我们需要考虑一个问题了,那么多的对象怎么生产呢? 答案就是工厂模式。Spring默认的工厂是DefaultListableBeanFactory,没错,Spring中的所有对象(容器对象和我们自己创建的对象)都是由他创建的。大批量生产对象 这个时候又有了一个问题,我们不想通过BeanFactory直接生产了,需要对这个工厂进行一些特定处理,于是出现了BeanFactoryPostProcessor,用来对工厂做一些特定的处理。我们自己可以通过实现这个接口,进行自定义BeanFactory。又有兄弟说了:我想单独创建一些我喜欢的对象,安排,FactoryBean诞生了,它可以帮助我们创建一个我们需要的对象(第四部分详细解释他们之间的区别)。 那又有兄弟说了:我想让统一的对象创建之前按照我的方式进行一些特殊的行为,简单,安排:see_no_evil: BeanPostProcessor出现了,他提供了两个方法:一个在对象实例化之后初始化之前,执行内部的Before方法,在初始化之后,执行After方法。(Bean生命周期,第四部分详解) 这个时候有兄弟有疑问了,不是说BeanPostProcessor在创建对象之前执行吗?怎么是创建完毕以后才执行的Before方法。 如果各位兄弟了解过指令重排序这个概念,那么一定会听过一个案例,创建一个对象需要三步 创建空间(实例化) 初始化 赋值 其中在初始化和赋值会出现指令重排序 根据这个点,应该可以get到一个点,实例化和初始化不一样。 所以又引出了一个点,我们对Bean进行一些操作,怎么操作,肯定是修改属性,或者添加一些属性等等,需要等待其在堆中开辟空间即实例化完成以后执行吧。 所以BeanPostProcessor的before方法在实例化之后执行,初始化之前执行。 经历过前面一大堆的操作以后,终于我们的对象进入我们兜里了(容器里)。 关于销毁,一般情况下我们通过ApplicationContext拿不到其销毁方法,只能通过其子类实现获取,关于销毁同样的流程,先执行一个销毁之前的操作,然后再销毁。 2.2 实际工作流程 看过Spring源码或者听过的都知道里面有一个方法叫做refresh,他完成了好多事情。当然他的行为也代表了整个IOC容器加载和实例化对象的过程。第三章的代码解读中我们仔细看 执行过程: 加载配置文件,初始化系统环境Environment接口 准备上下文环境,初始化一些配置资源 创建一个工厂 为工厂添加各种环境 获取子类自己重写的BeanFactoryPostProcessor 执行容器和我们自己的BeanFactoryPostProcessor 注册BeanPostProcessor 国际化处理 转播器 子类初始化Bean 注册监听器,观察者模式 完成Bean创建 发布相应的事件,监听器 3. IOC源码解读 >写在之前:IOC的源码比较复杂,所以个人建议视频方式学习,大家可以B站搜索阁主梧桐(笔者认为讲的不错的一个解读),如果大家不喜欢视频的方式,又想深度学习IOC源码那么推荐**程序员囧辉**它的博客对于IOC的讲解非常深入。另外本文接下来的Spring源码,主要是通过图示的方法梳理其流程,作者水平有限。如有错误请留言。 3.1 上下文配置启动 在创建ClassPathXmlApplicationContext的时候,构造方法中执行了这些方法。 说白了,加载了一个解析配置文件路径的加载器;然后又通过系统环境变量拿到这个配置文件,进行一些配置文件的去空格,转换表达式等等操作(没有进行解析);最后就是那个被我标成红色东东,refresh方法中它完成了几乎所有的工作。下面细聊 3.2 refresh 这个方法几乎完成了所有的操作,创建工厂,执行Processor等等,实例化对象,开启事件监听等等。 接下来细聊 3.3.1 prepareRefresh() 这个方法的主要作用是为应用上下文的刷新做一些准备性的工作。校验资源文件,设置启动时间和活跃状态等。 3.3.2 obtainFreshBeanFactory() 可以get到,它主要就是创建了一个工厂BeanFactory,并且解析了配置文件,加载了Bean定义信息(面试的时候直接答这个点就够了,如果想说的可以将下面的bean信息加载聊聊) 没错,标红的就是咱接下来细聊的点 这个就是加载配置文件的过程,注意:此时仍然没有解析,解析在标红的下面 这个就是读取的过程,具体解析流程来自parse中,这个直接调用了Java中的解析XML的类库,有兴趣自行翻阅,最后返回了一个Document对象。 通过Document对象,读取内部的标签,执行不同的方法,逻辑和MyBatis中解析配置文件的思想相同,大家自行翻阅。 此时所有的Bean定义信息都被保存到了BeanDefinitionRegistry接口,然后走子类DefaultListableBeanFactory工厂的注册方法 3.3.3 prepareBeanFactory(beanFactory) 为BeanFactory准备一些环境,方便在实例化的时候使用,同时添加容器自己的BeanPostProcessor 3.3.4 postProcessBeanFactory 留给子类扩展的BeanFactoryPostProcessor, 3.3.5 invokeBeanFactoryPostProcessors(beanFactory) 这个类,涉及到了两个接口。 BeanFactoryPostProcessor BeanDefinitionRegistryPostProcessor接口,这个接口是BeanFactoryPostProcessor的子接口,它的优先级比BeanFactoryPostProcessor更高 它的总体执行流程是:先执行BeanDefinitionRegistryPostProcessor的BeanFactoryPostProcessor,然后再执行BeanFactoryPostProcessor 下图是BeanDefinitionRegistryPostProcessor接口的处理过程 BeanFactoryPostProcessor的处理逻辑 总逻辑就是先分类,已经处理过的直接跳过,没有处理过的,分类处理,逻辑和上面的相同。 3.3.6 registerBeanPostProcessors 这个方法的逻辑和上面的一样,只不过上面是直接执行了BeanFactoryPostProcessor,而这个仅仅注册没执行。 首先拿到工厂中所有的BeanPostProcessor类型的Bean,然后分类处理,排序注册。 3.3.7 initMessageSource() 执行国际化内容 3.3.8 initApplicationEventMulticaster 创建了一个多播器,为添加Listener提供支持。 主要逻辑: 容器中是否存在applicationEventMulticaster,如果存在直接注册 如果不存在,创建一个SimpleApplicationEventMulticaster,注册到容器中。 3.3.9 onRefresh() 子类扩展 3.3.10 registerListeners() 观察者模式的实现 protected void registerListeners() { // 拿到当前容器中的监听器,注册到多播器中 for (ApplicationListener<!--?--> listener : getApplicationListeners()) { getApplicationEventMulticaster().addApplicationListener(listener); } //拿到容器中为监听器的Bean,注册 String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false); for (String listenerBeanName : listenerBeanNames) { getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName); } // 清空开始的事件,到广播器中 Set<applicationevent> earlyEventsToProcess = this.earlyApplicationEvents; this.earlyApplicationEvents = null; if (earlyEventsToProcess != null) { for (ApplicationEvent earlyEvent : earlyEventsToProcess) { getApplicationEventMulticaster().multicastEvent(earlyEvent); } } } 3.3.11 finishBeanFactoryInitialization >这一部分的内容太多了,所以采用代码和图解的方式来讲解。 /** * Finish the initialization of this context's bean factory, * initializing all remaining singleton beans. 在上下文工厂中完成所有Bean 的初始化 */ protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { // 初始化上下文转换服务Bean if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &amp;&amp; beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) { beanFactory.setConversionService( beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)); } //如果不存在前入值解析器,则注册一个默认的嵌入值解析器,主要是注解属性解析 if (!beanFactory.hasEmbeddedValueResolver()) { beanFactory.addEmbeddedValueResolver(strVal -&gt; getEnvironment().resolvePlaceholders(strVal)); } // 初始化LoadTimeWeaverAware String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false); for (String weaverAwareName : weaverAwareNames) { getBean(weaverAwareName); } // Stop using the temporary ClassLoader for type matching. beanFactory.setTempClassLoader(null); // Allow for caching all bean definition metadata, not expecting further changes. beanFactory.freezeConfiguration(); // Instantiate all remaining (non-lazy-init) singletons. //实例化,重点 beanFactory.preInstantiateSingletons(); } 下图是创建Bean的主要流程 按照途中的序号一个一个说: BeanDefinition是否需要合并。BeanDefinition根据不同类型的配置文件信息,会将Bean封装到不同的Bean信息定义类中。比如我们常用的配置文件版的GenericBeanDefinition;注解扫描版的ScannedGenericBeanDefinition等等。 而在这个过程中就出现了,父定义和子定义,我们需要在实际处理定义信息的时候进行合并处理,主要有一下三个方面 存在父定义信息,使用父定义信息创建一个RootBeanDefinition,然后将自定义信息作为参数传入。 不存在父定义信息,并且当前BeanDefinition是RootBeanDefintion类型的,直接返回一份RootBeanDefintion的克隆 不存在父定义信息,并且当前BeanDefintion不是RootBeanDefintiton类型的,直接通过该BeanDefintion构建一个RootBeanDefintion返回 上面的流程也是源码中的执行流程 isFactoryBean。判断是否为FactoryBean 简单介绍一下:FactoryBean是让开发者创建自己需要Bean接口。内部提供了三个方法 T getObject() throws Exception;//返回的Bean信息 Class<!--?--> getObjectType();//返回的Bean类型 default boolean isSingleton() {return true;}//是否单例 当我们通过GetBean直接该Bean的时候,获取到的是该工厂指定返回的Bean类型。如果想要获取该Bean本身,需要通过一个前缀获得&amp; @Override public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException { String beanName = transformedBeanName(name); //解析真正的BeanName Object beanInstance = getSingleton(beanName, false);//获取容器中的bean if (beanInstance != null) {//如果容器中存在,直接返回该Bean是否为FactoryBea类型 return (beanInstance instanceof FactoryBean); } //没有Bean信息,检查这个Bean信息 if (!containsBeanDefinition(beanName) &amp;&amp; getParentBeanFactory() instanceof ConfigurableBeanFactory) { // 从父工厂中获取 return ((ConfigurableBeanFactory) getParentBeanFactory()).isFactoryBean(name); } //MergedBeanDefinition来检查beanName对应的Bean是否为FactoryBean return isFactoryBean(beanName, getMergedLocalBeanDefinition(beanName)); } 再来看一个点,这个就是从容器中获取Bean的主要方法,也是解决循环依赖的逻辑 protected Object getSingleton(String beanName, boolean allowEarlyReference) { //查看当前容器中是否存在该Bean Object singletonObject = this.singletonObjects.get(beanName); //如果不存在,且当前Bean正在被创建 if (singletonObject == null &amp;&amp; isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { //从早期的容器中获取Bean singletonObject = this.earlySingletonObjects.get(beanName); //如果早期容器也没有且允许创建早期引用 if (singletonObject == null &amp;&amp; allowEarlyReference) { //获取该Bean的ObjectFactory工厂 ObjectFactory<!--?--> singletonFactory = this.singletonFactories.get(beanName); //如果当前工厂不为空 if (singletonFactory != null) { //创建一个对象实例,此时处于半初始化状态 singletonObject = singletonFactory.getObject(); //添加到早期引用中 this.earlySingletonObjects.put(beanName, singletonObject); //移除创建早期引用的工厂,因为该Bean已经创建且添加到了早期容器中,不需要再次进行创建了。 this.singletonFactories.remove(beanName); } } } } return singletonObject; } 来聊一下它是怎么解决循环引用的? 它引入了一个三级缓存的概念 /**存放了所有的单例Bean */ private final Map<string, object> singletonObjects = new ConcurrentHashMap&lt;&gt;(256); /** 存放了Bean创建需要的ObejctFactory */ private final Map<string, objectfactory<?>&gt; singletonFactories = new HashMap&lt;&gt;(16); /** 存放了早期创建的Bean,此时的Bean没有进行属性赋值,仅仅通过构造方法创建了一个实例 */ private final Map<string, object> earlySingletonObjects = new HashMap&lt;&gt;(16); //正在创建的Bean private final Set<string> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap&lt;&gt;(16)); 在发生循环引用的时候,它首先通过ObejctFactory工厂将Bean创建出来,**此时的对象并没有进行属性赋值,仅仅在堆中开辟了空间。**然后将此时的Bean添加到earlySingletonObjects容器里,**也就是说这个容器中保存的Bean都是半成品。**而在之后的属性赋值中,由于对象为单例的,所以其引用地址不会发生变化,即对象最终是完整的。 getBean。通过这个方法直接创建了所有的对象,这也是Spring最核心的方法了 先来看一下它整体的一个流程 它的主要逻辑是:先拿到当前要实例化的Bean的真实名字,主要是为了处理FactoryBean,拿到以后,从当前容器中看是否已经创建过该Bean,如果存在直接返回。 如果不存在,获取其父工厂,如果父工厂不为空,而且当前容器中不存在当前Bean的信息,则尝试从父工厂中获取Bean定义信息,进行Bean实例化 如果父工厂为空,将当前Bean信息存放到alreadyCreated缓存中。 获取当前Bean的合并信息(getMergedLocalBeanDefinition),查看当前Bean是否存在依赖,如果存在则判断当前Bean和依赖Bean是否为循环依赖,如果不是循环依赖则先创建依赖Bean 判断当前Bean的作用域。 如果当前Bean是单例对象,直接创建Bean实例 如果当前Bean是多例对象,将当前Bean信息添加到正在创建多例缓存中,创建完毕以后移除 如果当前Bean是其他类型,如Requtst,Session等类型,则自定义一个ObejctFacotry工厂,重写getObject方法,创建对象 对象创建以后,判断当前对象是否为自己需要的对象,如果是直接返回;如果不是进行类型转换,如果类型转换失败,直接抛异常 接下来看一眼CreateBean的执行 这个方法主要完成的事情是:通过Bean的名字拿到对应的Class对象;如果当前Bean获取到的Class对象不为空且该RootDefintiton可以直接获取到该Bean,克隆一份Bean定义信息,方便之后使用。 验证当前Bean上的@Override信息。执行BeanPostProcessor,返回一个代理对象(如果存在代理的话) 如果不存在代理,则直接创建Bean 接下来我们来聊一下这个玩意——resolveBeforeInstantiation protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) { Object bean = null; if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) { // Make sure bean class is actually resolved at this point. //当前定义信息不是合并,且存在Bean增强器 if (!mbd.isSynthetic() &amp;&amp; hasInstantiationAwareBeanPostProcessors()) { //获取Bean的Class类型 Class<!--?--> targetType = determineTargetType(beanName, mbd); if (targetType != null) { //如果不为null,则执行前置处理器 bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName); if (bean != null) { //如果前置处理器不为null,则后置处理器执行,跳过spring默认初始化 bean = applyBeanPostProcessorsAfterInitialization(bean, beanName); } } } //代表已经再实例化之前进行了解析 mbd.beforeInstantiationResolved = (bean != null); } return bean; } 来吧,继续,看一下那个前置处理器逻辑 protected Object applyBeanPostProcessorsBeforeInstantiation(Class<!--?--> beanClass, String beanName) { for (BeanPostProcessor bp : getBeanPostProcessors()) { //拿到工厂中的所有的BeanPostProcessor if (bp instanceof InstantiationAwareBeanPostProcessor) { //找到所有我们需要的增强器 InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; // 返回一个代理实例 Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName); if (result != null) { return result; } } } return null; } 后置处理器就不看了,就调用了所有的后置处理器,然后执行了一遍,没有其他逻辑。 接下来继续我们的正题:doCreateBean 其大致流程如上图: 先判断以后是否单例,然后从FactoryBean缓存中看一下是否存在正在创建的Bean,如果存在拿出,如果不存在则创建一个当前Bean的包装类实例。然后拿到这个类的实例和实例类型,执行以后后置处理器。 当前Bean是否为单例,是否允许循环依赖,时候正在进行创建,如果是,创建一个当前Bean的ObejctFactory以解决循环依赖的问题 填充Bean的属性,进行Bean的实例化。 查看早期容器缓存中(缓存中的二级缓存中是否有该Bean)。如果有,则说明存在循环依赖,则进行处理 先看循环依赖吧 if (earlySingletonExposure) { //从早期的Bean容器中拿到实例对象,此时的Bean必然存在循环依赖 Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (!this.allowRawInjectionDespiteWrapping &amp;&amp; hasDependentBean(beanName)) { //获取依赖的全部Bean信息 String[] dependentBeans = getDependentBeans(beanName); Set &lt; String &gt; actualDependentBeans = new LinkedHashSet &lt; &gt; (dependentBeans.length); for (String dependentBean: dependentBeans) { //清除这些Bean信息,此时的Bean已经是脏数据了 if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { //无法清理存入actualDependentBeans中 actualDependentBeans.add(dependentBean); } } if (!actualDependentBeans.isEmpty()) { throw new BeanCurrentlyInCreationException } } } } // Register bean as disposable. try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } 接着来,createBeanInstance Spring提供了三种方式创建对象的包装: 通过供给者对象对象直接创建。obtainFromSupplier 通过工厂方法直接创建。 默认创建。 构造方法是否需要自动注入 构造方法不需要自动注入,调用默认的构造方法 这个方法执行完毕以后,你应该知晓的一个点是:此时对象实例已经创建了,剩下的就是执行一系列增强器和初始化方法,属性填充等等。 我们按照代码执行顺序来,属性填充即populateBean 这个方法执行逻辑: 首先判断传入的Bean是否为null,如果为null则判断Bean定义信息中是否存在属性值,如果存在,异常;如果不存在跳过 当前Bean定义信息是否为合并以后的,如果是且此时的工厂中存在InstantiationAwareBeanPostProcessors,那么在属性填充之前进行修改Bean的信息 拿到所有的属性值,解析属性值的自动注入方式,Type或者Name,进行自动注入 判断是否存在InstantiationAwareBeanPostProcessors,修改之前设置的属性 判断是否存在依赖检查,检查依赖 属性赋值 接下来看执行初始化方法,就是调用BeanPostprocessor,init等方法 这个就是这个方法的执行流程图,相信到这个地方,大家应该对于为什么BeanPostProcessor的before方法会在init方法执行了解了。这个方法的作用仅仅是用来进行一个生命周期的打印,对象在之前已经创建了。 接下来看一下销毁的方法。registerDisposableBeanIfNecessary 对于单例Bean来说,Spring将需要销毁的Bean存放到了disposableBeans缓存中,通过DisposableBeanAdapter封装了销毁Bean 对于其他作用域来说,自定义了销毁回调函数,不过最后还是封装为DisposableBeanAdapter 在封装为DisposableBeanAdapter的过程中,会首先判断该Bean中是否存在destroy方法,然后给赋值给destroyMethodName变量。再次判断这个方法的参数,如果参数的个数大于1,则抛出异常 3.3.12 finishRefresh 这个方法进行了一系列的资源清理和 protected void finishRefresh() { // 清空上下文资源缓存 clearResourceCaches(); // 初始化生命周期处理器 initLifecycleProcessor(); // 将已经刷新完毕的处理器传播(扔到)生命周期处理器中 getLifecycleProcessor().onRefresh(); // 推送上下文刷新完毕的时间到相应的监听器 publishEvent(new ContextRefreshedEvent(this)); // Participate in LiveBeansView MBean, if active. LiveBeansView.registerApplicationContext(this); } initLifecycleProcessor,这个方法极具简单,就看一下当前Bean中是否存在生命周期处理器,如果存在直接使用这个,如果不存在则创建一个默认的,并且注册为一个单例的扔到容器中。 4. 常见题目 4.1 Bean的生命周期? >Spring官方解释在BeanDefinition接口的注释里 答:Bean完整的生命周期是: 设置一系列Aware接口的功能 实例化Bean 调用BeanPostProcessor的before方法 执行InitializingBean接口方法afterPropertiesSet 执行init方法 调用BeanPostProcessor的postProcessAfterInitialization方法 调用DestructionAwareBeanPostProcessors接口的postProcessBeforeDestruction方法 调用destory方法 4.2 FactoryBean和BeanFactory的区别 答:BeanFactory是Spring默认生产对象的工厂。 ​ FactoryBean是Spring提供的一个生产特定类型和特定对象的工厂。例如Mybatis-spring中的SqlSessionFactoryBean就是通过这种方法创建的。 4.3 什么是循环依赖?Spring如何处理循环依赖的? 答:循环依赖是指:在创建A对象的时候需要注入B对象;在创建B对象的时候需要注入A对象,两者互相依赖。 出现循环依赖有两种情况: 构造器依赖(无法解决) 属性注入(可以解决) 解决循环依赖,Spring引入了三级缓存的概念。上面的源码讲解中介绍过 singletonObjects存放了所有的单例Bean,此时所有的Bean信息都是完整的 earlySingletonObjects存放了早期的Bean,此时仅仅创建了一个Bean实例,未进行属性填充 singletonFactories存放了Bean的工厂 Spring通过将创建Bean的工厂暴露出来,然后在出现循环依赖的时候通过这个工厂常见一个bean,然后将这个Bean注入,由于对象是单例的,所以在接下来的属性填充中,可以保证为同一个对象,至此,循环依赖解除。 > 使用三太子敖丙的一句话:解决循环依赖的过程就是力扣中的第一题两数之和的过程 4.4 什么是IOC 答:IOC存在两个点: 控制反转。将常见对象的控制权交给第三方,这里的第三方就是Spring 依赖注入。在类中需要使用到的对象,全部通过反射从第三方容器注入而不是自己创建。这里的第三方容器即Spring 4.5 ApplicationContext和BeanFactory的区别 答: ApplicationContext采用了立即加载,即加载配置文件的时候就创建了对象。BeanFactory采用了延时加载的方式,使用的时候才创建。 对于BeanPostProcessor和BeanFactoryProcessor而言,BeanFactory是手动注册,ApplicationContext采用了自动注册。 4.6 Spring 框架中都用到了哪些设计模式? 答: 单例模式。这个不需要多说 代理模式。AOP使用到的 装饰着模式。BeanWrapper 工厂模式。BeanFactory,创建对象的时候 模板方法模式。JDBCTemplate 观察者模式。各种事件监听 …… </string></string,></string,></string,></applicationevent>

优秀的个人博客,低调大师

Java注解@Cacheable的工作原理

In order to avoid unnecessary query on database it is a common pattern to define a cache in application layer to cache the query result from database. See one example below. Here the application cache is maintained in a custom class CacheContext. public class AccountService1 { private final Logger logger = LoggerFactory.getLogger(AccountService1.class); private CacheContext<Account> accountCacheContext; public Account getAccountByName(String accountName) { Account result = accountCacheContext.get(accountName); if (result != null) { logger.info("get from cache... {}", accountName); return result; } Optional<Account> accountOptional = getFromDB(accountName); if (!accountOptional.isPresent()) { throw new IllegalStateException(String.format("can not find account by account name : [%s]", accountName)); } Account account = accountOptional.get(); accountCacheContext.addOrUpdateCache(accountName, account); return account; } In Spring there is an annotation @Cacheable which can make the cache managed by Spring instead of application developer. See improved version: public class AccountService2 { private final Logger logger = LoggerFactory.getLogger(AccountService2.class); @Cacheable(value="accountCache") public Account getAccountByName(String accountName) { logger.info("in method getAccountByName, querying account... {}", accountName); Optional<Account> accountOptional = getFromDB(accountName); if (!accountOptional.isPresent()) { throw new IllegalStateException(String.format("can not find account by account name : [%s]", accountName)); } return accountOptional.get(); } In this example, there is no more cache evaluation and cache fill logic. All such stuff are taken over by Spring under the hood and completely transparent to application developer, with the help of following bean configuration in xml: <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager"> <property name="caches"> <set> <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"> <property name="name" value="default" /> </bean> <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"> <property name="name" value="accountCache" /> </bean> </set> </property> </bean> And how to research what magic has been done by Spring to achieve this behavior?We use the following code to research. It is expected that the request sent by line 31 will directly reach database, while the second request in line 34 will be handled by Spring cache handler. Here in line 31, in debugger we can find that accountService2 is not an instance of application class com.sap.AccountService2, but a dynamic proxy class generated by Spring. As a result after pressing F5, the intercept method of this dynamic proxy class is called: In this method, the execution will be delegated to Spring cache handler class: In example 1, the logic of cache evaluation and fill is done by application, and now it is done in method execute below. First internal cache managed by Spring is checked in line 336 via method findCachedItem. Due to expected cache miss, our application method will be called as usual via reflection, as demonstrated below: After application method to retrieve account from database is done, the result, Account instance with id 2495 is filled to Spring cache, the variable contexts below. Below is the screenshot for Spring internal cache to store application data, which is based on ConcurrentHashMap. Our cached Account instance with id 2495 could be found there. For the second query request issued by application, the cached result will be returned by Spring handler: The last question is, how and when the dynamic proxy is generated?Again let’s go to the entry point of Beans initialization: Here the dynamic proxy is created based on configuration defined in xml with the help of CGlibAopProxy. For more detail about CGlibAopProxy, please refer to Spring official document. 本文来自云栖社区合作伙伴“汪子熙”,了解相关信息可以关注微信公众号"汪子熙"。

优秀的个人博客,低调大师

hbase shell实现原理简析

hbase的交互式命令行是通过jruby实现的,当我们输入hbase shell时,实际上最终执行的是org.jruby.Main,并以bin/hirb.rb作为参数,注意是根目录下bin目录中的hirb.rb,而不是hbase-shell中的irb/hirb.rb;这个类来自jruby的包,作用是把ruby编写的代码转换成java字节码,进而能够运行在JVM中; 实现逻辑大体可分为2个阶段:初始化阶段和命令执行阶段,前者是启动shell时的执行逻辑,后者是输入命令后的执行逻辑,以下分别简述其流程; 初始化阶段 1、创建HBaseConfiguration实例,并将启动时带的键值对参数设置进去;2、创建Hbase实例,初始化connection,代码在hbase.rb中;3、创建Shell实例,此时会执行一些load_command_group方法,这些方法实际上是初始化了commands和command_groups这2个map变量,commands中存放了各个命令的name与class的映射关系,代码在shell.rb中;4、接下来执行Shell实例的export_commands方法,通过instance_eval为commands中的所有命令动态添加一个方法到Shell实例中; 命令执行阶段(以list命令为例) 1、执行前述动态生成的list方法;2、执行Shell实例的command方法,参数为list;3、执行internal_command,该方法内部先调用command_instance按一定规则创建该命令对应class的实例:List,所有命令的class都会继承Command类;4、执行List的command_safe方法,这个方法在Command类中,该方法内部通过调用send(cmd, *args)来执行List的command方法,List类定义在list.rb中,Command类定义在commands.rb中;5、List的command方法先后调用了Command、Shell、Hbase等类中的admin方法,最后得到一个Admin实例,该类定义在admin.rb中;6、执行Admin实例的list方法,该方法内部实际上执行了HBaseAdmin的listTableNames来得到结果; 调试 如果希望在本地环节启动hbase shell,可参考如下配置; //Main class org.jruby.Main //VM Options -Dhbase.ruby.sources=E:\github\hbase\hbase-shell\src\main\ruby //Program argument E:\github\hbase\bin\hirb.rb //Use classpath of module hbase-shell 默认情况下连的是localhost的hbase,如果希望连远程集群,可以修改hbase-shell模块中hbase.rb的configuration,指定hbase.zookeeper.quorum参数即可;

优秀的个人博客,低调大师

看完就懂 webpack 打包原理

什么是 webpack ? 本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。 webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。 插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。 webpack 通过 Tapable 来组织这条复杂的生产线。 webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。 webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。 -- 深入浅出 webpack 吴浩麟 webpack 核心概念 Entry 入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。 进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。 每个依赖项随即被处理,最后输出到称之为 bundles 的文件中。 Output output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。 基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。 Module 模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。 Chunk 代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。 Loader loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。 loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。 本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。 Plugin loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。 插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。 webpack 构建流程 Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程 : 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数。 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译。 确定入口:根据配置中的 entry 找出所有的入口文件。 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。 完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系。 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会。 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。 在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。 实践加深理解,撸一个简易 webpack 1. 定义 Compiler 类 class Compiler { constructor(options) { // webpack 配置 const { entry, output } = options // 入口 this.entry = entry // 出口 this.output = output // 模块 this.modules = [] } // 构建启动 run() {} // 重写 require函数,输出bundle generate() {} } 2. 解析入口文件,获取 AST 我们这里使用@babel/parser,这是 babel7 的工具,来帮助我们分析内部的语法,包括 es6,返回一个 AST 抽象语法树。 // webpack.config.js const path = require('path') module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, './dist'), filename: 'main.js' } } // const fs = require('fs') const parser = require('@babel/parser') const options = require('./webpack.config') const Parser = { getAst: path => { // 读取入口文件 const content = fs.readFileSync(path, 'utf-8') // 将文件内容转为AST抽象语法树 return parser.parse(content, { sourceType: 'module' }) } } class Compiler { constructor(options) { // webpack 配置 const { entry, output } = options // 入口 this.entry = entry // 出口 this.output = output // 模块 this.modules = [] } // 构建启动 run() { const ast = Parser.getAst(this.entry) } // 重写 require函数,输出bundle generate() {} } new Compiler(options).run() 3. 找出所有依赖模块 Babel 提供了@babel/traverse(遍历)方法维护这 AST 树的整体状态,我们这里使用它来帮我们找出依赖模块。 const fs = require('fs') const path = require('path') const options = require('./webpack.config') const parser = require('@babel/parser') const traverse = require('@babel/traverse').default const Parser = { getAst: path => { // 读取入口文件 const content = fs.readFileSync(path, 'utf-8') // 将文件内容转为AST抽象语法树 return parser.parse(content, { sourceType: 'module' }) }, getDependecies: (ast, filename) => { const dependecies = {} // 遍历所有的 import 模块,存入dependecies traverse(ast, { // 类型为 ImportDeclaration 的 AST 节点 (即为import 语句) ImportDeclaration({ node }) { const dirname = path.dirname(filename) // 保存依赖模块路径,之后生成依赖关系图需要用到 const filepath = './' + path.join(dirname, node.source.value) dependecies[node.source.value] = filepath } }) return dependecies } } class Compiler { constructor(options) { // webpack 配置 const { entry, output } = options // 入口 this.entry = entry // 出口 this.output = output // 模块 this.modules = [] } // 构建启动 run() { const { getAst, getDependecies } = Parser const ast = getAst(this.entry) const dependecies = getDependecies(ast, this.entry) } // 重写 require函数,输出bundle generate() {} } new Compiler(options).run() 4. AST 转换为 code 将 AST 语法树转换为浏览器可执行代码,我们这里使用@babel/core 和 @babel/preset-env。 const fs = require('fs') const path = require('path') const options = require('./webpack.config') const parser = require('@babel/parser') const traverse = require('@babel/traverse').default const { transformFromAst } = require('@babel/core') const Parser = { getAst: path => { // 读取入口文件 const content = fs.readFileSync(path, 'utf-8') // 将文件内容转为AST抽象语法树 return parser.parse(content, { sourceType: 'module' }) }, getDependecies: (ast, filename) => { const dependecies = {} // 遍历所有的 import 模块,存入dependecies traverse(ast, { // 类型为 ImportDeclaration 的 AST 节点 (即为import 语句) ImportDeclaration({ node }) { const dirname = path.dirname(filename) // 保存依赖模块路径,之后生成依赖关系图需要用到 const filepath = './' + path.join(dirname, node.source.value) dependecies[node.source.value] = filepath } }) return dependecies }, getCode: ast => { // AST转换为code const { code } = transformFromAst(ast, null, { presets: ['@babel/preset-env'] }) return code } } class Compiler { constructor(options) { // webpack 配置 const { entry, output } = options // 入口 this.entry = entry // 出口 this.output = output // 模块 this.modules = [] } // 构建启动 run() { const { getAst, getDependecies, getCode } = Parser const ast = getAst(this.entry) const dependecies = getDependecies(ast, this.entry) const code = getCode(ast) } // 重写 require函数,输出bundle generate() {} } new Compiler(options).run() 5. 递归解析所有依赖项,生成依赖关系图 const fs = require('fs') const path = require('path') const options = require('./webpack.config') const parser = require('@babel/parser') const traverse = require('@babel/traverse').default const { transformFromAst } = require('@babel/core') const Parser = { getAst: path => { // 读取入口文件 const content = fs.readFileSync(path, 'utf-8') // 将文件内容转为AST抽象语法树 return parser.parse(content, { sourceType: 'module' }) }, getDependecies: (ast, filename) => { const dependecies = {} // 遍历所有的 import 模块,存入dependecies traverse(ast, { // 类型为 ImportDeclaration 的 AST 节点 (即为import 语句) ImportDeclaration({ node }) { const dirname = path.dirname(filename) // 保存依赖模块路径,之后生成依赖关系图需要用到 const filepath = './' + path.join(dirname, node.source.value) dependecies[node.source.value] = filepath } }) return dependecies }, getCode: ast => { // AST转换为code const { code } = transformFromAst(ast, null, { presets: ['@babel/preset-env'] }) return code } } class Compiler { constructor(options) { // webpack 配置 const { entry, output } = options // 入口 this.entry = entry // 出口 this.output = output // 模块 this.modules = [] } // 构建启动 run() { // 解析入口文件 const info = this.build(this.entry) this.modules.push(info) this.modules.forEach(({ dependecies }) => { // 判断有依赖对象,递归解析所有依赖项 if (dependecies) { for (const dependency in dependecies) { this.modules.push(this.build(dependecies[dependency])) } } }) // 生成依赖关系图 const dependencyGraph = this.modules.reduce( (graph, item) => ({ ...graph, // 使用文件路径作为每个模块的唯一标识符,保存对应模块的依赖对象和文件内容 [item.filename]: { dependecies: item.dependecies, code: item.code } }), {} ) } build(filename) { const { getAst, getDependecies, getCode } = Parser const ast = getAst(filename) const dependecies = getDependecies(ast, filename) const code = getCode(ast) return { // 文件路径,可以作为每个模块的唯一标识符 filename, // 依赖对象,保存着依赖模块路径 dependecies, // 文件内容 code } } // 重写 require函数,输出bundle generate() {} } new Compiler(options).run() 6. 重写 require 函数,输出 bundle const fs = require('fs') const path = require('path') const options = require('./webpack.config') const parser = require('@babel/parser') const traverse = require('@babel/traverse').default const { transformFromAst } = require('@babel/core') const Parser = { getAst: path => { // 读取入口文件 const content = fs.readFileSync(path, 'utf-8') // 将文件内容转为AST抽象语法树 return parser.parse(content, { sourceType: 'module' }) }, getDependecies: (ast, filename) => { const dependecies = {} // 遍历所有的 import 模块,存入dependecies traverse(ast, { // 类型为 ImportDeclaration 的 AST 节点 (即为import 语句) ImportDeclaration({ node }) { const dirname = path.dirname(filename) // 保存依赖模块路径,之后生成依赖关系图需要用到 const filepath = './' + path.join(dirname, node.source.value) dependecies[node.source.value] = filepath } }) return dependecies }, getCode: ast => { // AST转换为code const { code } = transformFromAst(ast, null, { presets: ['@babel/preset-env'] }) return code } } class Compiler { constructor(options) { // webpack 配置 const { entry, output } = options // 入口 this.entry = entry // 出口 this.output = output // 模块 this.modules = [] } // 构建启动 run() { // 解析入口文件 const info = this.build(this.entry) this.modules.push(info) this.modules.forEach(({ dependecies }) => { // 判断有依赖对象,递归解析所有依赖项 if (dependecies) { for (const dependency in dependecies) { this.modules.push(this.build(dependecies[dependency])) } } }) // 生成依赖关系图 const dependencyGraph = this.modules.reduce( (graph, item) => ({ ...graph, // 使用文件路径作为每个模块的唯一标识符,保存对应模块的依赖对象和文件内容 [item.filename]: { dependecies: item.dependecies, code: item.code } }), {} ) this.generate(dependencyGraph) } build(filename) { const { getAst, getDependecies, getCode } = Parser const ast = getAst(filename) const dependecies = getDependecies(ast, filename) const code = getCode(ast) return { // 文件路径,可以作为每个模块的唯一标识符 filename, // 依赖对象,保存着依赖模块路径 dependecies, // 文件内容 code } } // 重写 require函数 (浏览器不能识别commonjs语法),输出bundle generate(code) { // 输出文件路径 const filePath = path.join(this.output.path, this.output.filename) // 懵逼了吗? 没事,下一节我们捋一捋 const bundle = `(function(graph){ function require(module){ function localRequire(relativePath){ return require(graph[module].dependecies[relativePath]) } var exports = {}; (function(require,exports,code){ eval(code) })(localRequire,exports,graph[module].code); return exports; } require('${this.entry}') })(${JSON.stringify(code)})` // 把文件内容写入到文件系统 fs.writeFileSync(filePath, bundle, 'utf-8') } } new Compiler(options).run() 7. 看完这节,彻底搞懂 bundle 实现 我们通过下面的例子来进行讲解,先死亡凝视 30 秒 ;(function(graph) { function require(moduleId) { function localRequire(relativePath) { return require(graph[moduleId].dependecies[relativePath]) } var exports = {} ;(function(require, exports, code) { eval(code) })(localRequire, exports, graph[moduleId].code) return exports } require('./src/index.js') })({ './src/index.js': { dependecies: { './hello.js': './src/hello.js' }, code: '"use strict";\n\nvar _hello = require("./hello.js");\n\ndocument.write((0, _hello.say)("webpack"));' }, './src/hello.js': { dependecies: {}, code: '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.say = say;\n\nfunction say(name) {\n return "hello ".concat(name);\n}' } }) step 1 : 从入口文件开始执行 // 定义一个立即执行函数,传入生成的依赖关系图 ;(function(graph) { // 重写require函数 function require(moduleId) { console.log(moduleId) // ./src/index.js } // 从入口文件开始执行 require('./src/index.js') })({ './src/index.js': { dependecies: { './hello.js': './src/hello.js' }, code: '"use strict";\n\nvar _hello = require("./hello.js");\n\ndocument.write((0, _hello.say)("webpack"));' }, './src/hello.js': { dependecies: {}, code: '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.say = say;\n\nfunction say(name) {\n return "hello ".concat(name);\n}' } }) step 2 : 使用 eval 执行代码 // 定义一个立即执行函数,传入生成的依赖关系图 ;(function(graph) { // 重写require函数 function require(moduleId) { ;(function(code) { console.log(code) // "use strict";\n\nvar _hello = require("./hello.js");\n\ndocument.write((0, _hello.say)("webpack")); eval(code) // Uncaught TypeError: Cannot read property 'code' of undefined })(graph[moduleId].code) } // 从入口文件开始执行 require('./src/index.js') })({ './src/index.js': { dependecies: { './hello.js': './src/hello.js' }, code: '"use strict";\n\nvar _hello = require("./hello.js");\n\ndocument.write((0, _hello.say)("webpack"));' }, './src/hello.js': { dependecies: {}, code: '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.say = say;\n\nfunction say(name) {\n return "hello ".concat(name);\n}' } }) 可以看到,我们在执行"./src/index.js"文件代码的时候报错了,这是因为 index.js 里引用依赖 hello.js,而我们没有对依赖进行处理,接下来我们对依赖引用进行处理。 step 3 : 依赖对象寻址映射,获取 exports 对象 // 定义一个立即执行函数,传入生成的依赖关系图 ;(function(graph) { // 重写require函数 function require(moduleId) { // 找到对应moduleId的依赖对象,调用require函数,eval执行,拿到exports对象 function localRequire(relativePath) { return require(graph[moduleId].dependecies[relativePath]) // {__esModule: true, say: ƒ say(name)} } // 定义exports对象 var exports = {} ;(function(require, exports, code) { // commonjs语法使用module.exports暴露实现,我们传入的exports对象会捕获依赖对象(hello.js)暴露的实现(exports.say = say)并写入 eval(code) })(localRequire, exports, graph[moduleId].code) // 暴露exports对象,即暴露依赖对象对应的实现 return exports } // 从入口文件开始执行 require('./src/index.js') })({ './src/index.js': { dependecies: { './hello.js': './src/hello.js' }, code: '"use strict";\n\nvar _hello = require("./hello.js");\n\ndocument.write((0, _hello.say)("webpack"));' }, './src/hello.js': { dependecies: {}, code: '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.say = say;\n\nfunction say(name) {\n return "hello ".concat(name);\n}' } }) 这下应该明白了吧 ~ 可以直接复制上面代码到控制台输出哦~ 总结 Webpack 是一个庞大的 Node.js 应用,如果你阅读过它的源码,你会发现实现一个完整的 Webpack 需要编写非常多的代码。 但你无需了解所有的细节,只需了解其整体架构和部分细节即可。 对 Webpack 的使用者来说,它是一个简单强大的工具; 对 Webpack 的开发者来说,它是一个扩展性的高系统。 Webpack 之所以能成功,在于它把复杂的实现隐藏了起来,给用户暴露出的只是一个简单的工具,让用户能快速达成目的。 同时整体架构设计合理,扩展性高,开发扩展难度不高,通过社区补足了大量缺失的功能,让 Webpack 几乎能胜任任何场景。 参考 webpack 中文文档 深入浅出 webpack

优秀的个人博客,低调大师

SAP CRM Relationship API设计原理

Unlike settype, relationship does not have a dedicated read function module maintained in its metadata table. Instead, the generic read function module COM_IL_DB_READ is used. Below is a simple explanation about each parameter of FM COM_IL_DB_READ, using read on relationship PRDCPN for example. IV_RELTYPE PRDCPN - relationship name IV_ATTR_TYPE COMT_IL_PRDCPN_ATTR_TYPE - contains relationship specific business data, in this example, the customer product id is stored in field PRID_VENDOR IT_LINK_IDENTS sourceguid or destiguid contains product guid. This will be used by the generic read API to select against DB table using OPEN SQL. The exporting parameter: ET_INTERLINKAGE - relationship header data - generic data ET_IL_ATTR Relationship specific data, in this example, PRID_VENDOR, stores the detail value. Approach1 If we can enhance COM_IL_DB_READ, we then redirect the read from CRM relationship storage table to S4 relationship storage table.Since it is not allowed to enhance SAP_ABA function module, we have to consider CDS view redirect.Further research is needed here: compare the structure of both storage table in CRM and S4 and evaluate whether view direct is feasible or not. Approach2 Since we can only make changes on BBPCRM, we have to copy the whole implementation which are in SAP_ABAP listed below into new function & subroutine, make needed changes ( table redirect ) and let FORM UI_GETDETAIL call those new implementations. This approach takes huge effort. 本文来自云栖社区合作伙伴“汪子熙”,了解相关信息可以关注微信公众号"汪子熙"。

优秀的个人博客,低调大师

HashMap原理及内部存储结构

本文将通过如下简单的代码来分析HashMap的内部数据结构的变化过程。 public static void main(String[] args) { Map<String, String> map = new HashMap<>(); for (int i = 0; i < 50; i++) { map.put("key" + i, "value" + i); } } 1 数据结构说明 HashMap中本文需要用到的几个字段如下: 下面说明一下几个字段的含义 1.1 table // HashMap内部使用这个数组存储所有键值对 transient Node<K,V>[] table; Node的结构如下: 可以发现,Node其实是一个链表,通过next指向下一个元素。 1.2 size 记录了HashMap中键值对的数量 1.3 modCount 记录了HashMap在结构上更改的次数,包括可以更改键值对数量的操作,例如put、remove,还有可以修改内部结构的操作,例如rehash。 1.4 threshold 记录一个临界值,当已存储键值对的个数大于这个临界值时,需要扩容。 1.5 loadFactor 负载因子,通常用于计算threshold,threshold=总容量*loadFactor。 2 new HashMap new HashMap的源码如下: /** * The load factor used when none specified in constructor. * 负载因子,当 已使用容量 > 总容量 * 负载因子 时,需要扩容 */ static final float DEFAULT_LOAD_FACTOR = 0.75f; public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } 此时,HashMap只初始化了负载因子(使用默认值0.75),并没有初始化table数组。其实HashMap使用的是延迟初始化策略,当第一次put的时候,才初始化table(此时table是null)。 3 table数组的初始化 当第一次put的时候,HashMap会判断当前table是否为空,如果是空,会调用resize方法进行初始化。resize方法会初始化一个容量大小为16 的数组,并赋值给table。 并计算threshold=16*0.75=12。 此时table数组的状态如下: 4 put过程 map.put("key0", "value0"); 首先计算key的hash值,hash("key0") = 3288451 计算这次put要存入数组位置的索引值:index=(数组大小 - 1) & hash = 3 判断 if (table[index] == null) 就new一个Node放到这里,此时为null,所以直接new Node放到3上,此时table如下: 然后判断当前已使用容量大小(size)是否已经超过临界值threshold,此时size=1,小于12,不做任何操作,put方法结束(如果超过临界值,需要resize扩容)。 继续put。。。 map.put("key1", "value1"); map.put("key1", "value1"); map.put("key2", "value2"); map.put("key3", "value3"); map.put("key4", "value4"); map.put("key5", "value5"); map.put("key6", "value6"); map.put("key8", "value7"); map.put("key9", "value9"); map.put("key10", "value10"); map.put("key11", "value11"); 此时size=12,下一次put后size为13,大于当前threshold,将触发扩容(resize) map.put("key12", "value12"); 计算Key的hash值,hash("key12")=101945043,计算要存入table位置的索引值 = (总大小 - 1) & hash = (16 - 1) & 101945043 = 3 从目前的table状态可知,table[3] != null,但此时要put的key与table[3].key不相等,我们必须要把他存进去,此时就产生了哈希冲突(哈希碰撞)。 这时链表就派上用场了,HashMap就是通过链表解决哈希冲突的。 HashMap会创建一个新的Node,并放到table[3]链表的最后面。 此时table状态如下: 5 resize扩容 此时table中一共有13个元素,已经超过了threshold(12),需要对table调用resize方法扩容。 HashMap会创建一个容量为之前两倍(162=32)的table,并将旧的Node复制到新的table中,新的临界值(threshold)为24(320.75)。 下面主要介绍一下复制的过程(并不是原封不动的复制,Node的位置可能发生变化) 先来看源码: for (int j = 0; j < oldCap; ++j) { // oldCap:旧table的大小 =16 Node<K,V> e; if ((e = oldTab[j]) != null) { // oldTab:旧table的备份 oldTab[j] = null; // 如果数组中的元素没有后继节点,直接计算新的索引值,并将Node放到新数组中 if (e.next == null) newTab[e.hash & (newCap - 1)] = e; // 忽略这个else if。其实,如果链表的长度超过8,HashMap会把这个链表变成一个树结构,树结构中的元素是TreeNode else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); // 有后继节点的情况 else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; // 【说明1】 if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; //【说明2】 newTab[j + oldCap] = hiHead; } } } } 【说明1】遍历链表,计算链表每一个节点在新table中的位置。 计算位置的方式如下: 1)如果节点的 (hash & oldCap) == 0,那么该节点还在原来的位置上,为什么呢? 因为oldCap=16,二进制的表现形式为0001 0000,任何数&16,如果等于0,那么这个数的第五个二进制位必然为0。 以当前状态来说,新的容量是32,那么table的最大index是31,31的二进制表现形式是00011111。 计算index的方式是 hash & (容量 - 1),也就是说,新index的计算方式为 hash & (32 - 1) 假设Node的hash = 01101011,那么 01101011 & 00011111 ---------- 00001011 = 11 2)下面再对比(hash & oldCap) != 0的情况 如果节点的(hash & oldCap) != 0,那么该节点的位置=旧index + 旧容量大小 假设Node的hash = 01111011,那么 01111011 & 00011111 ---------- 00011011 = 27 上一个例子的hash值01101011跟这个例子的hash值01111011只是在第5位二进制上不同,可以发现,这两个值在旧的table中,是在同一个index中的,如下: 01101011 & 00001111 ---------- 00001011 = 11 01111011 & 00001111 ---------- 00001011 = 11 由于扩容总是以2倍的方式进行,也就是:旧容量 << 1,这也就解释了【说明2】,当(hash & oldCap) != 0时,这个Node的新index = 旧index + 旧容量大小。 扩容后,table状态如下所示: 最终,重新分配完所有的Node后,扩容结束。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册