java refresh,spring boot refresh

  java refresh,spring boot refresh

  

目录

spring的刷新方法,预知,创建Bean容器前的准备,创建Bean容器,加载注册Bean摘要

 

  

spring的refresh方法

 

  

前置知识

方法入口

 

  //org . spring framework . context . support . abstractapplicationcontext #刷新类的结构图

  BeanDefinition 接口定义

  接口Bean定义扩展属性访问器,BeanmetadataElement {//我们可以看到默认只提供了sington和prototype。//很多读者可能知道,还有请求、会话、全局会话、应用、WebSocket等。//但是,它们是基于web的扩展。string SCOPE _ SINGLETON=configurable bean factory。SCOPE _ SINGLETONstring SCOPE _ PROTOTYPE=configurable beanfactory。范围_原型;//不重要,跳过就好。int ROLE _ APPLICATION=0;int ROLE _ SUPPORT=1;int ROLE _ infra structure=2;//设置父bean,涉及Bean继承,不涉及java继承。详细介绍请参考附录。//总之,继承父Bean的配置信息:void set parent name(string parent name);//获取父Bean字符串Get parent name();//设置Bean的类名。以后通过反射生成实例的将是void setbean class name(string bean class name);//获取Bean的类名字符串getbean class name();//设置bean的作用域void setScope(字符串作用域);string get scope();//设置是否懒加载void setlazy nit(boolean lazy nit);布尔islazyint();//设置此bean依赖的所有bean-on=。注意这里的依赖不是指属性依赖(如@Autowire所标注的),//是dependencies-on= 属性设置的值。void setDependsOn(字符串.dependsOn);//返回此Bean String[] getDependsOn()的所有依赖关系;//设置该Bean是否可以注入其他Bean,只对根据类型注入有效。//如果根据名称注入,即使这里设置了false,也有可能void setautowireCANDATE(布尔自动连线候选);//该Bean是否可以注入其他Bean boolean isAutowireCandidate();//主要的那个。对于同一个接口的多个实现,如果没有指定名称,Spring会优先考虑bean void set primary(Boolean primary),primary设置为true//是否为Primary的布尔is primary();//如果Bean是由工厂方法生成的,请指定工厂名称。不熟悉工厂的读者,请参加附录//。总之,有些实例不是反射生成的,而是工厂模式生成的Void SetFactoryBean Name(string factory bean Name);//获取工厂名称字符串getFactoryBeanName();//在工厂类中指定工厂方法名void setfactorymethodname(字符串工厂方法名);//获取工厂类中的工厂方法名称字符串getFactoryMethodName();//构造函数参数constructorargumentValues getconstructorargumentValues();//bean中的属性值,稍后将注入到bean中

  说到 MutablePropertyValues getPropertyValues(); // 是否 singleton boolean isSingleton(); // 是否 prototype boolean isPrototype(); // 如果这个 Bean 是被设置为 abstract,那么不能实例化, // 常用于作为 父bean 用于继承,其实也很少用...... boolean isAbstract(); int getRole(); String getDescription(); String getResourceDescription(); BeanDefinition getOriginatingBeanDefinition();}refresh 这里简单说下为什么是 refresh(),而不是 init() 这种名字的方法。因为 ApplicationContext 建立起来以后,其实我们是可以通过调用 refresh() 这个方法重建的,refresh() 会将原来的 ApplicationContext 销毁,然后再重新执行一次初始化操作。

  

@Overridepublic void refresh() throws BeansException, IllegalStateException { // 来个锁,不然 refresh() 还没结束,你又来个启动或销毁容器的操作,那不就乱套了嘛 synchronized (this.startupShutdownMonitor) { // 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符 prepareRefresh(); // 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中, // 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了, // 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map) ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean // 这块待会会展开说 prepareBeanFactory(beanFactory); try { // 【这里需要知道 BeanFactoryPostProcessor 这个知识点,Bean 如果实现了此接口, // 那么在容器初始化以后,Spring 会负责调用里面的 postProcessBeanFactory 方法。】 // 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化 // 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事 postProcessBeanFactory(beanFactory); // 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法 invokeBeanFactoryPostProcessors(beanFactory); // 注册 BeanPostProcessor 的实现类,注意看和 BeanFactoryPostProcessor 的区别 // 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization // 两个方法分别在 Bean 初始化之前和初始化之后得到执行。注意,到这里 Bean 还没初始化 registerBeanPostProcessors(beanFactory); // 初始化当前 ApplicationContext 的 MessageSource,国际化这里就不展开说了,不然没完没了了 initMessageSource(); // 初始化当前 ApplicationContext 的事件广播器,这里也不展开了 initApplicationEventMulticaster(); // 从方法名就可以知道,典型的模板方法(钩子方法), // 具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前) onRefresh(); // 注册事件监听器,监听器需要实现 ApplicationListener 接口。这也不是我们的重点,过 registerListeners(); // 重点,重点,重点 // 初始化所有的 singleton beans //(lazy-init 的除外) finishBeanFactoryInitialization(beanFactory); // 最后,广播事件,ApplicationContext 初始化完成 finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. // 销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源 destroyBeans(); // Reset active flag. cancelRefresh(ex); // 把异常往外抛 throw ex; } finally { // Reset common introspection caches in Springs core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } }}

 

  

创建 Bean 容器前的准备工作

// 准备工作protected void prepareRefresh() { // 记录启动时间, // 将 active 属性设置为 true,closed 属性设置为 false,它们都是 AtomicBoolean 类型 this.startupDate = System.currentTimeMillis(); this.closed.set(false); this.active.set(true); if (logger.isInfoEnabled()) { logger.info("Refreshing " + this); } // Initialize any placeholder property sources in the context environment 初始化上下文环境中的任何占位符属性源。 initPropertySources(); // 校验 xml 配置文件 getEnvironment().validateRequiredProperties(); // 初始化事件集合 this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();}

 

  

创建 Bean 容器,加载并注册 Bean

// 最重要的方法之一,这里将会初始化 BeanFactory、加载 Bean、注册 Bean 等等。protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { // 关闭旧的 BeanFactory (如果有),创建新的 BeanFactory,加载 Bean 定义、注册 Bean 等等 refreshBeanFactory(); // 返回刚刚创建的 BeanFactory ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); } return beanFactory;}// refreshBeanFactory@Overrideprotected final void refreshBeanFactory() throws BeansException { // 如果 ApplicationContext 中已经加载过 BeanFactory 了,销毁所有 Bean,关闭 BeanFactory // 注意,应用中 BeanFactory 本来就是可以多个的,这里可不是说应用全局是否有 BeanFactory,而是当前 // ApplicationContext 是否有 BeanFactory if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { // 初始化一个 DefaultListableBeanFactory,为什么用这个,我们马上说。 DefaultListableBeanFactory beanFactory = createBeanFactory(); // 用于 BeanFactory 的序列化,我想大部分人应该都用不到 beanFactory.setSerializationId(getId()); // 下面这两个方法很重要,别跟丢了,具体细节之后说 // 设置 BeanFactory 的两个配置属性:是否允许 Bean 覆盖、是否允许循环引用 customizeBeanFactory(beanFactory); // 加载 Bean 到 BeanFactory 中 loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); }}// customizeBeanFactory 配置是否允许 BeanDefinition 覆盖、是否允许循环引用。protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { if (this.allowBeanDefinitionOverriding != null) { // 是否允许 Bean 定义覆盖 在配置文件中定义 bean 时使用了相同的 id 或 name,默认情况下,allowBeanDefinitionOverriding 属性为 null,如果在同一配置文件中重复了,会抛错,但是如果不是同一配置文件中,会发生覆盖。 beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } if (this.allowCircularReferences != null) { // 是否允许 Bean 间的循环依赖 如:A 依赖 B,而 B 依赖 A。或 A 依赖 B,B 依赖 C,而 C 依赖 A。 beanFactory.setAllowCircularReferences(this.allowCircularReferences); }}// loadBeanDefinitions 加载bean,放入 BeanFactory 中 org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)/** 我们可以看到,此方法将通过一个 XmlBeanDefinitionReader 实例来加载各个 Bean。*/@Overrideprotected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // 给这个 BeanFactory 实例化一个 XmlBeanDefinitionReader XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this contexts // resource loading environment. beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // 初始化 BeanDefinitionReader,其实这个是提供给子类覆写的, // 我看了一下,没有类覆写这个方法,我们姑且当做不重要吧 initBeanDefinitionReader(beanDefinitionReader); // 重点来了,继续往下 loadBeanDefinitions(beanDefinitionReader);}protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { Resource[] configResources = getConfigResources(); if (configResources != null) { // 往下看 reader.loadBeanDefinitions(configResources); } String[] configLocations = getConfigLocations(); if (configLocations != null) { // 2 reader.loadBeanDefinitions(configLocations); }}// 上面虽然有两个分支,不过第二个分支很快通过解析路径转换为 Resource 以后也会进到这里@Overridepublic int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { Assert.notNull(resources, "Resource array must not be null"); int counter = 0; // 注意这里是个 for 循环,也就是每个文件是一个 resource for (Resource resource : resources) { // 继续往下看 counter += loadBeanDefinitions(resource); } // 最后返回 counter,表示总共加载了多少的 BeanDefinition return counter;}// XmlBeanDefinitionReader 303@Overridepublic int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource));}// XmlBeanDefinitionReader 314public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isInfoEnabled()) { logger.info("Loading XML bean definitions from " + encodedResource.getResource()); } // 用一个 ThreadLocal 来存放配置文件资源 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<EncodedResource>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } // 核心部分是这里,往下面看 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } }}// 还在这个文件中,第 388 行protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { // 这里就不看了,将 xml 文件转换为 Document 对象 Document doc = doLoadDocument(inputSource, resource); // 继续 return registerBeanDefinitions(doc, resource); } catch (...}// 还在这个文件中,第 505 行// 返回值:返回从当前配置文件加载了多少数量的 Beanpublic int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); // 这里 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore;}// DefaultBeanDefinitionDocumentReader 90@Overridepublic void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); // 从 xml 根节点开始解析文件 doRegisterBeanDefinitions(root);} // doRegisterBeanDefinitions 经过漫长的链路,一个配置文件终于转换为一颗 DOM 树了,注意,这里指的是其中一个配置文件,不是所有的,读者可以看到上面有个 for 循环的。下面开始从根节点开始解析:// DefaultBeanDefinitionDocumentReader 116protected void doRegisterBeanDefinitions(Element root) { // 我们看名字就知道,BeanDefinitionParserDelegate 必定是一个重要的类,它负责解析 Bean 定义, // 这里为什么要定义一个 parent? 看到后面就知道了,是递归问题, // 因为 <beans /> 内部是可以定义 <beans /> 的,所以这个方法的 root 其实不一定就是 xml 的根节点,也可以是嵌套在里面的 <beans /> 节点,从源码分析的角度,我们当做根节点就好了 BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) { // 这块说的是根节点 <beans ... profile="dev" /> 中的 profile 是否是当前环境需要的, // 如果当前环境配置的 profile 不包含此 profile,那就直接 return 了,不对此 <beans /> 解析 // 不熟悉 profile 为何物,不熟悉怎么配置 profile 读者的请移步附录区 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isInfoEnabled()) { logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource()); } return; } } } preProcessXml(root); // 钩子 preProcessXml(root) 和 postProcessXml(root) 是给子类用的钩子方法,鉴于没有被使用到,也不是我们的重点,我们直接跳过。 // 往下看 parseBeanDefinitions(root, this.delegate); postProcessXml(root); // 钩子 this.delegate = parent;}// default namespace 涉及到的就四个标签 <import />、<alias />、<bean /> 和 <beans />,// 其他的属于 custom 的protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { // 解析 default namespace 下面的几个元素 parseDefaultElement(ele, delegate); } else { // 解析其他 namespace 的元素 delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); }} private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { // 处理 <import /> 标签 importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // 处理 <alias /> 标签定义 // <alias name="fromName" alias="toName"/> processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { // 处理 <bean /> 标签定义,这也算是我们的重点吧 processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // 如果碰到的是嵌套的 <beans /> 标签,需要递归 doRegisterBeanDefinitions(ele); }} // DefaultBeanDefinitionDocumentReader 298protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // 将 <bean /> 节点中的信息提取出来,然后封装到一个 BeanDefinitionHolder 中,细节往下看 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); // 下面的几行先不要看,跳过先,跳过先,跳过先,后面会继续说的 if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name " + bdHolder.getBeanName() + "", ele, ex); } // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); }} // BeanDefinitionParserDelegate 428public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) { return parseBeanDefinitionElement(ele, null);}public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) { String id = ele.getAttribute(ID_ATTRIBUTE); String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); List<String> aliases = new ArrayList<String>(); // 将 name 属性的定义按照 “逗号、分号、空格” 切分,形成一个 别名列表数组, // 当然,如果你不定义 name 属性的话,就是空的了 // 我在附录中简单介绍了一下 id 和 name 的配置,大家可以看一眼,有个20秒就可以了 if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } String beanName = id; // 如果没有指定id, 那么用别名列表的第一个名字作为beanName if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { beanName = aliases.remove(0); if (logger.isDebugEnabled()) { logger.debug("No XML id specified - using " + beanName + " as bean name and " + aliases + " as aliases"); } } if (containingBean == null) { checkNameUniqueness(beanName, aliases, ele); } // 根据 <bean ...>...</bean> 中的配置创建 BeanDefinition,然后把配置中的信息都设置到实例中, // 细节后面细说,先知道下面这行结束后,一个 BeanDefinition 实例就出来了。 AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); // 到这里,整个 <bean /> 标签就算解析结束了,一个 BeanDefinition 就形成了。 if (beanDefinition != null) { // 如果都没有设置 id 和 name,那么此时的 beanName 就会为 null,进入下面这块代码产生 // 如果读者不感兴趣的话,我觉得不需要关心这块代码,对本文源码分析来说,这些东西不重要 if (!StringUtils.hasText(beanName)) { try { if (containingBean != null) {// 按照我们的思路,这里 containingBean 是 null 的 beanName = BeanDefinitionReaderUtils.generateBeanName( beanDefinition, this.readerContext.getRegistry(), true); } else { // 如果我们不定义 id 和 name,那么我们引言里的那个例子: // 1. beanName 为:com.javadoop.example.MessageServiceImpl#0 // 2. beanClassName 为:com.javadoop.example.MessageServiceImpl beanName = this.readerContext.generateBeanName(beanDefinition); String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { // 把 beanClassName 设置为 Bean 的别名 aliases.add(beanClassName); } } if (logger.isDebugEnabled()) { logger.debug("Neither XML id nor name specified - " + "using generated bean name [" + beanName + "]"); } } catch (Exception ex) { error(ex.getMessage(), ele); return null; } } String[] aliasesArray = StringUtils.toStringArray(aliases); // 返回 BeanDefinitionHolder return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null;}
看到这里的时候,我觉得读者就应该站在高处看 ApplicationContext 了,ApplicationContext 继承自 BeanFactory,但是它不应该被理解为 BeanFactory 的实现类,而是说其内部持有一个实例化的 BeanFactory(DefaultListableBeanFactory)。以后所有的 BeanFactory 相关的操作其实是委托给这个实例来处理的。

 

  

我们说说为什么选择实例化DefaultListableBeanFactory?前面我们说了有个很重要的接口 ConfigurableListableBeanFactory,它实现了 BeanFactory 下面一层的所有三个接口,我把之前的继承图再拿过来大家再仔细看一下:

 

  

 

  我们可以看到 ConfigurableListableBeanFactory 只有一个实现类DefaultListableBeanFactory,而且实现类 DefaultListableBeanFactory 还通过实现右边的 AbstractAutowireCapableBeanFactory 通吃了右路。所以结论就是,最底下这个家伙 DefaultListableBeanFactory 基本上是最牛的 BeanFactory 了,这也是为什么这边会使用这个类来实例化的原因。

  循环依赖

  

 

  

 

  

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注盛行IT的更多内容!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

相关文章阅读

  • spring编程式事务处理,spring编程事务
  • spring编程式事务处理,spring编程事务,详解Spring学习之编程式事务管理
  • spring的核心功能模块有几个,列举一些重要的spring模块
  • spring的核心功能模块有几个,列举一些重要的spring模块,七个Spring核心模块详解
  • spring注解和springmvc的注解,SpringMVC常用注解
  • spring注解和springmvc的注解,SpringMVC常用注解,详解springmvc常用5种注解
  • spring实现ioc的四种方法,spring的ioc的三种实现方式
  • spring实现ioc的四种方法,spring的ioc的三种实现方式,简单实现Spring的IOC原理详解
  • spring事务失效问题分析及解决方案怎么做,spring 事务失效情况
  • spring事务失效问题分析及解决方案怎么做,spring 事务失效情况,Spring事务失效问题分析及解决方案
  • spring5.0新特性,spring4新特性
  • spring5.0新特性,spring4新特性,spring5新特性全面介绍
  • spring ioc以及aop原理,springmvc aop原理
  • spring ioc以及aop原理,springmvc aop原理,深入浅析Spring 的aop实现原理
  • Spring cloud网关,spring cloud zuul作用
  • 留言与评论(共有 条评论)
       
    验证码: