spring 的 IOC 流程(源码流程)

本贴最后更新于 2143 天前,其中的信息可能已经时移世易

先从源码来看 spring 的 IOC 都经历了哪些方法:
1.开始加载 xml 配置文件

ApplicationContext context = new ClassPathXmlApplicationContext("test.xml");

2.ApplicationContext 的构造方法

this(new String[] {configLocation}, true, null);
public ClassPathXmlApplicationContext(  
      String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)  
      throws BeansException {  
  
   super(parent);  
   setConfigLocations(configLocations);  
   if (refresh) {  
      refresh();  
   }  
}

3.父类构造方法
一直跳转到 AbstractApplicationContext 文件

public AbstractApplicationContext(@Nullable ApplicationContext parent) {  
   this();  
   setParent(parent);  
}

其中 this()方法是创建一个新的 AbstractApplicationContext,并生成一个默认的资源模式解析器。因为 parent 为 null,所以 setParent 没有实质作用。

4.设置配置文件路径
返回 ClassPathXmlApplicationContext 查看 setConfigLocations 方法。此方法是将传入的资源路径保存下来。

public void setConfigLocations(@Nullable String... locations) {  
   if (locations != null) {  
      Assert.noNullElements(locations, "Config locations must not be null");  
      this.configLocations = new String[locations.length];  
      for (int i = 0; i < locations.length; i++) {  
         this.configLocations[i] = resolvePath(locations[i]).trim();  
      }  
   }  
   else {  
      this.configLocations = null;  
   }  
}

5.refresh 方法
主要的 IOC 方法。主要看 spring 的 beanfactory 怎么生成的,以及保存了什么。

@Override  
public void refresh() throws BeansException, IllegalStateException {  
   synchronized (this.startupShutdownMonitor) {  
      // 准备此上下文以进行刷新。
  prepareRefresh();  
  
      // 告诉子类刷新内部bean工厂。
  ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();  
  
      // 准备在这种情况下使用的Bean工厂。
  prepareBeanFactory(beanFactory);  
  
      try {  
         // 允许在上下文子类中对bean工厂进行后处理。
  postProcessBeanFactory(beanFactory);  
  
         // 调用工厂处理器在上下文中注册为bean。 
  invokeBeanFactoryPostProcessors(beanFactory);  
  
         // 注册bean处理器,拦截bean创建。
  registerBeanPostProcessors(beanFactory);  
  
         // Initialize message source for this context.  
  initMessageSource();  
  
         // 初始化此上下文的消息源。
  initApplicationEventMulticaster();  
  
         // 在特定上下文子类中初始化其他特殊bean。
  onRefresh();  
  
         // Check for listener beans and register them.  
  registerListeners();  
  
         // 检查侦听器bean并注册它们。
  finishBeanFactoryInitialization(beanFactory);  
  
         // 最后一步:发布相应的事件。
  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.  
  destroyBeans();  
  
         // Reset 'active' flag.  
  cancelRefresh(ex);  
  
         // Propagate exception to caller.  
  throw ex;  
      }  
  
      finally {  
         // Reset common introspection caches in Spring's core, since we  
 // might not ever need metadata for singleton beans anymore...  resetCommonCaches();  
      }  
   }  
}

6.obtainFreshBeanFactory 方法
在 refreshBeanFactory 生成 beanFactory,然后通过 getBeanFactory 获取

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {  
   refreshBeanFactory();  
   ConfigurableListableBeanFactory beanFactory = getBeanFactory();  
   if (logger.isDebugEnabled()) {  
      logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);  
   }  
   return beanFactory;  
}

7.refreshBeanFactory 的实现方法
在 idea 中可以使用 ctrl+alt+b 的快捷键查看方法的实现方法。该方法在 AbstractRefreshableApplicationContext 中,
可以看到,方法中先销毁了之前的 beanFactory(如果有的话),然后生成了一个默认的 DefaultListableBeanFactory。然后将 beanFactory 保存,使得上一步的 getBeanFactory 可以获取。然后看 loadBeanDefinitions 方法。

protected final void refreshBeanFactory() throws BeansException {  
   if (hasBeanFactory()) {  
      destroyBeans();  
      closeBeanFactory();  
   }  
   try {  
      DefaultListableBeanFactory beanFactory = createBeanFactory();  
      beanFactory.setSerializationId(getId());  
      customizeBeanFactory(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);  
   }  
}

8.loadBeanDefinitions 方法
这里有很多的 loadBeanDefinitions 在互相调用,这些 loadBeanDefinitions 的作用就是将最开始传入的资源文件路径加载,封装成 InputSource,方便后续解析文件。

//生成一个xml读取器
@Override  
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {  
   // Create a new XmlBeanDefinitionReader for the given BeanFactory.  
  XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);  
  
   // Configure the bean definition reader with this context's  
 // resource loading environment.  beanDefinitionReader.setEnvironment(this.getEnvironment());  
//传入一个资源加载器,就是AbstractXmlApplicationContext
   beanDefinitionReader.setResourceLoader(this);  
   beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));  
  
   // Allow a subclass to provide custom initialization of the reader,  
 // then proceed with actually loading the bean definitions.  initBeanDefinitionReader(beanDefinitionReader);  
   loadBeanDefinitions(beanDefinitionReader);  
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {  
   Resource[] configResources = getConfigResources();  
   if (configResources != null) {  
      reader.loadBeanDefinitions(configResources);  
   }  
//用reader读取最开始传入的资源文件路径
   String[] configLocations = getConfigLocations();  
   if (configLocations != null) {  
      reader.loadBeanDefinitions(configLocations);  
   }  
}
@Override  
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {  
   Assert.notNull(locations, "Location array must not be null");  
   int counter = 0;  
   for (String location : locations) {  
      counter += loadBeanDefinitions(location);  
   }  
   return counter;  
}
@Override  
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {  
   return loadBeanDefinitions(location, null);  
}
public int loadBeanDefinitions(String location, @Nullable Set actualResources) throws BeanDefinitionStoreException {  
   ResourceLoader resourceLoader = getResourceLoader();  
   if (resourceLoader == null) {  
      throw new BeanDefinitionStoreException(  
            "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");  
   }  
  //前面传入了一个资源加载器,可以通过继承树看出AbstractXmlApplicationContext是ResourcePatternResolver的子类
   if (resourceLoader instanceof ResourcePatternResolver) {  
      // Resource pattern matching available.  
  try {  
//将资源文件的路径封装成Resource
         Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);  
         int loadCount = loadBeanDefinitions(resources);  
         if (actualResources != null) {  
            for (Resource resource : resources) {  
               actualResources.add(resource);  
            }  
         }  
         if (logger.isDebugEnabled()) {  
            logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");  
         }  
         return loadCount;  
      }  
      catch (IOException ex) {  
         throw new BeanDefinitionStoreException(  
               "Could not resolve bean definition resource pattern [" + location + "]", ex);  
      }  
   }  
   else {  
      // Can only load single resources by absolute URL.  
  Resource resource = resourceLoader.getResource(location);  
      int loadCount = loadBeanDefinitions(resource);  
      if (actualResources != null) {  
         actualResources.add(resource);  
      }  
      if (logger.isDebugEnabled()) {  
         logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");  
      }  
      return loadCount;  
   }  
}
@Override  
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {  
   Assert.notNull(resources, "Resource array must not be null");  
   int counter = 0;  
   for (Resource resource : resources) {  
      counter += loadBeanDefinitions(resource);  
   }  
   return counter;  
}
@Override  
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {  
   return loadBeanDefinitions(new EncodedResource(resource));  
}
//封装资源文件和inputstream
public 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());  
   }  
  
   Set currentResources = this.resourcesCurrentlyBeingLoaded.get();  
   if (currentResources == null) {  
      currentResources = new HashSet<>(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();  
      }  
   }  
}

8.doLoadBeanDefinitions 方法

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)  
      throws BeanDefinitionStoreException {  
   try {  
//使用前面封装的资源文件和inputSource读取资源文件,并封装为Document对象
      Document doc = doLoadDocument(inputSource, resource);  
      return registerBeanDefinitions(doc, resource);  
   }  
   catch (BeanDefinitionStoreException ex) {  
      throw ex;  
   }  
   catch (SAXParseException ex) {  
      throw new XmlBeanDefinitionStoreException(resource.getDescription(),  
            "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);  
   }  
   catch (SAXException ex) {  
      throw new XmlBeanDefinitionStoreException(resource.getDescription(),  
            "XML document from " + resource + " is invalid", ex);  
   }  
   catch (ParserConfigurationException ex) {  
      throw new BeanDefinitionStoreException(resource.getDescription(),  
            "Parser configuration exception parsing XML from " + resource, ex);  
   }  
   catch (IOException ex) {  
      throw new BeanDefinitionStoreException(resource.getDescription(),  
            "IOException parsing XML document from " + resource, ex);  
   }  
   catch (Throwable ex) {  
      throw new BeanDefinitionStoreException(resource.getDescription(),  
            "Unexpected exception parsing XML document from " + resource, ex);  
   }  
}

9.registerBeanDefinitions 方法

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {  
//创建一个BeanDefinition读取器
   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();  
   int countBefore = getRegistry().getBeanDefinitionCount();  
   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));  
   return getRegistry().getBeanDefinitionCount() - countBefore;  
}
@Override  
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {  
   this.readerContext = readerContext;  
   logger.debug("Loading bean definitions");  
   Element root = doc.getDocumentElement();  
   doRegisterBeanDefinitions(root);  
}

10.doRegisterBeanDefinitions

protected void doRegisterBeanDefinitions(Element root) {  
   // Any nested  elements will cause recursion in this method. In  
 // order to propagate and preserve  default-* attributes correctly, // keep track of the current (parent) delegate, which may be null. Create // the new (child) delegate with a reference to the parent for fallback purposes, // then ultimately reset this.delegate back to its original (parent) reference. // this behavior emulates a stack of delegates without actually necessitating one.  BeanDefinitionParserDelegate parent = this.delegate;  
   this.delegate = createDelegate(getReaderContext(), root, parent);  
  
   if (this.delegate.isDefaultNamespace(root)) {  
      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);  
//开始解析Document中的Element信息
   parseBeanDefinitions(root, this.delegate);  
   postProcessXml(root);  
  
   this.delegate = parent;  
}

11.parseBeanDefinitions

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)) { 
//             使用默认的命名空间解析element 
               parseDefaultElement(ele, delegate);  
            }  
            else {  
               delegate.parseCustomElement(ele);  
            }  
         }  
      }  
   }  
   else {  
      delegate.parseCustomElement(root);  
   }  
}

12.parseDefaultElement

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {  
//注册import标签信息
   if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {  
      importBeanDefinitionResource(ele);  
   }  
//注册alias标签信息
   else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {  
      processAliasRegistration(ele);  
   }  
//注册Bean标签信息
   else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {  
      processBeanDefinition(ele, delegate);  
   }  
//解析beans标签信息
   else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {  
      // recurse  
  doRegisterBeanDefinitions(ele);  
   }  
}

13.processBeanDefinition

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {  
//这里已经将配置文件的信息封装为BeanDefinitionHolder,该类保存了BeanDefinition,BeanName和BeanAlices
   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));  
   }  
}

14.registerBeanDefinition

public static void registerBeanDefinition(  
      BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)  
      throws BeanDefinitionStoreException {  
  
   // Register bean definition under primary name.  
  String beanName = definitionHolder.getBeanName();  
   registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());  
  
   // Register aliases for bean name, if any.  
  String[] aliases = definitionHolder.getAliases();  
   if (aliases != null) {  
      for (String alias : aliases) {  
         registry.registerAlias(beanName, alias);  
      }  
   }  
}

15.registerBeanDefinition

@Override  
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)  
      throws BeanDefinitionStoreException {  
  
   Assert.hasText(beanName, "Bean name must not be empty");  
   Assert.notNull(beanDefinition, "BeanDefinition must not be null");  
  
   if (beanDefinition instanceof AbstractBeanDefinition) {  
      try {  
         ((AbstractBeanDefinition) beanDefinition).validate();  
      }  
      catch (BeanDefinitionValidationException ex) {  
         throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,  
               "Validation of bean definition failed", ex);  
      }  
   }  
  
   BeanDefinition oldBeanDefinition;  
  
   oldBeanDefinition = this.beanDefinitionMap.get(beanName);  
   if (oldBeanDefinition != null) {  
      if (!isAllowBeanDefinitionOverriding()) {  
         throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,  
               "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +  
               "': There is already [" + oldBeanDefinition + "] bound.");  
      }  
      else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {  
         // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE  
  if (this.logger.isWarnEnabled()) {  
            this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +  
                  "' with a framework-generated bean definition: replacing [" +  
                  oldBeanDefinition + "] with [" + beanDefinition + "]");  
         }  
      }  
      else if (!beanDefinition.equals(oldBeanDefinition)) {  
         if (this.logger.isInfoEnabled()) {  
            this.logger.info("Overriding bean definition for bean '" + beanName +  
                  "' with a different definition: replacing [" + oldBeanDefinition +  
                  "] with [" + beanDefinition + "]");  
         }  
      }  
      else {  
         if (this.logger.isDebugEnabled()) {  
            this.logger.debug("Overriding bean definition for bean '" + beanName +  
                  "' with an equivalent definition: replacing [" + oldBeanDefinition +  
                  "] with [" + beanDefinition + "]");  
         }  
      }  
//将前面封装的BeanDefinitionHolder中的beanName和beanDefinition保存到一个ConcurrentHashMap中
      this.beanDefinitionMap.put(beanName, beanDefinition);  
   }  
   else {  
      if (hasBeanCreationStarted()) {  
         // Cannot modify startup-time collection elements anymore (for stable iteration)  
  synchronized (this.beanDefinitionMap) {  
            this.beanDefinitionMap.put(beanName, beanDefinition);  
            List updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);  
            updatedDefinitions.addAll(this.beanDefinitionNames);  
            updatedDefinitions.add(beanName);  
            this.beanDefinitionNames = updatedDefinitions;  
            if (this.manualSingletonNames.contains(beanName)) {  
               Set updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);  
               updatedSingletons.remove(beanName);  
               this.manualSingletonNames = updatedSingletons;  
            }  
         }  
      }  
      else {  
         // Still in startup registration phase  
  this.beanDefinitionMap.put(beanName, beanDefinition);  
         this.beanDefinitionNames.add(beanName);  
         this.manualSingletonNames.remove(beanName);  
      }  
      this.frozenBeanDefinitionNames = null;  
   }  
  
   if (oldBeanDefinition != null || containsSingleton(beanName)) {  
      resetBeanDefinition(beanName);  
   }  
}

终于结束了,可以看到,最后将 beanDefinition 存放在了 beanDefinitionMap 中,而 beanDefinitionMap 就是一个 ConcurrentHashMap 集合。

  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    942 引用 • 1458 回帖 • 118 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • JetBrains

    JetBrains 是一家捷克的软件开发公司,该公司位于捷克的布拉格,并在俄国的圣彼得堡及美国麻州波士顿都设有办公室,该公司最为人所熟知的产品是 Java 编程语言开发撰写时所用的集成开发环境:IntelliJ IDEA

    18 引用 • 54 回帖
  • Swift

    Swift 是苹果于 2014 年 WWDC(苹果开发者大会)发布的开发语言,可与 Objective-C 共同运行于 Mac OS 和 iOS 平台,用于搭建基于苹果平台的应用程序。

    34 引用 • 37 回帖 • 506 关注
  • SVN

    SVN 是 Subversion 的简称,是一个开放源代码的版本控制系统,相较于 RCS、CVS,它采用了分支管理系统,它的设计目标就是取代 CVS。

    29 引用 • 98 回帖 • 689 关注
  • 智能合约

    智能合约(Smart contract)是一种旨在以信息化方式传播、验证或执行合同的计算机协议。智能合约允许在没有第三方的情况下进行可信交易,这些交易可追踪且不可逆转。智能合约概念于 1994 年由 Nick Szabo 首次提出。

    1 引用 • 11 回帖 • 9 关注
  • frp

    frp 是一个可用于内网穿透的高性能的反向代理应用,支持 TCP、UDP、 HTTP 和 HTTPS 协议。

    16 引用 • 7 回帖 • 1 关注
  • 996
    13 引用 • 200 回帖 • 3 关注
  • 微信

    腾讯公司 2011 年 1 月 21 日推出的一款手机通讯软件。用户可以通过摇一摇、搜索号码、扫描二维码等添加好友和关注公众平台,同时可以将自己看到的精彩内容分享到微信朋友圈。

    130 引用 • 793 回帖 • 1 关注
  • 职场

    找到自己的位置,萌新烦恼少。

    126 引用 • 1699 回帖
  • GitHub

    GitHub 于 2008 年上线,目前,除了 Git 代码仓库托管及基本的 Web 管理界面以外,还提供了订阅、讨论组、文本渲染、在线文件编辑器、协作图谱(报表)、代码片段分享(Gist)等功能。正因为这些功能所提供的便利,又经过长期的积累,GitHub 的用户活跃度很高,在开源世界里享有深远的声望,并形成了社交化编程文化(Social Coding)。

    207 引用 • 2031 回帖
  • Telegram

    Telegram 是一个非盈利性、基于云端的即时消息服务。它提供了支持各大操作系统平台的开源的客户端,也提供了很多强大的 APIs 给开发者创建自己的客户端和机器人。

    5 引用 • 35 回帖
  • H2

    H2 是一个开源的嵌入式数据库引擎,采用 Java 语言编写,不受平台的限制,同时 H2 提供了一个十分方便的 web 控制台用于操作和管理数据库内容。H2 还提供兼容模式,可以兼容一些主流的数据库,因此采用 H2 作为开发期的数据库非常方便。

    11 引用 • 54 回帖 • 649 关注
  • Vditor

    Vditor 是一款浏览器端的 Markdown 编辑器,支持所见即所得、即时渲染(类似 Typora)和分屏预览模式。它使用 TypeScript 实现,支持原生 JavaScript、Vue、React 和 Angular。

    328 引用 • 1706 回帖
  • Swagger

    Swagger 是一款非常流行的 API 开发工具,它遵循 OpenAPI Specification(这是一种通用的、和编程语言无关的 API 描述规范)。Swagger 贯穿整个 API 生命周期,如 API 的设计、编写文档、测试和部署。

    26 引用 • 35 回帖 • 11 关注
  • ReactiveX

    ReactiveX 是一个专注于异步编程与控制可观察数据(或者事件)流的 API。它组合了观察者模式,迭代器模式和函数式编程的优秀思想。

    1 引用 • 2 回帖 • 141 关注
  • Linux

    Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 Unix 的多用户、多任务、支持多线程和多 CPU 的操作系统。它能运行主要的 Unix 工具软件、应用程序和网络协议,并支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

    921 引用 • 934 回帖 • 1 关注
  • Markdown

    Markdown 是一种轻量级标记语言,用户可使用纯文本编辑器来排版文档,最终通过 Markdown 引擎将文档转换为所需格式(比如 HTML、PDF 等)。

    165 引用 • 1471 回帖
  • IPFS

    IPFS(InterPlanetary File System,星际文件系统)是永久的、去中心化保存和共享文件的方法,这是一种内容可寻址、版本化、点对点超媒体的分布式协议。请浏览 IPFS 入门笔记了解更多细节。

    20 引用 • 245 回帖 • 234 关注
  • 百度

    百度(Nasdaq:BIDU)是全球最大的中文搜索引擎、最大的中文网站。2000 年 1 月由李彦宏创立于北京中关村,致力于向人们提供“简单,可依赖”的信息获取方式。“百度”二字源于中国宋朝词人辛弃疾的《青玉案·元夕》词句“众里寻他千百度”,象征着百度对中文信息检索技术的执著追求。

    63 引用 • 785 回帖 • 248 关注
  • HTML

    HTML5 是 HTML 下一个的主要修订版本,现在仍处于发展阶段。广义论及 HTML5 时,实际指的是包括 HTML、CSS 和 JavaScript 在内的一套技术组合。

    103 引用 • 294 回帖
  • 服务器

    服务器,也称伺服器,是提供计算服务的设备。由于服务器需要响应服务请求,并进行处理,因此一般来说服务器应具备承担服务并且保障服务的能力。

    124 引用 • 580 回帖
  • iOS

    iOS 是由苹果公司开发的移动操作系统,最早于 2007 年 1 月 9 日的 Macworld 大会上公布这个系统,最初是设计给 iPhone 使用的,后来陆续套用到 iPod touch、iPad 以及 Apple TV 等产品上。iOS 与苹果的 Mac OS X 操作系统一样,属于类 Unix 的商业操作系统。

    84 引用 • 139 回帖
  • 锤子科技

    锤子科技(Smartisan)成立于 2012 年 5 月,是一家制造移动互联网终端设备的公司,公司的使命是用完美主义的工匠精神,打造用户体验一流的数码消费类产品(智能手机为主),改善人们的生活质量。

    4 引用 • 31 回帖 • 4 关注
  • Firefox

    Mozilla Firefox 中文俗称“火狐”(正式缩写为 Fx 或 fx,非正式缩写为 FF),是一个开源的网页浏览器,使用 Gecko 排版引擎,支持多种操作系统,如 Windows、OSX 及 Linux 等。

    7 引用 • 30 回帖 • 446 关注
  • IDEA

    IDEA 全称 IntelliJ IDEA,是一款 Java 语言开发的集成环境,在业界被公认为最好的 Java 开发工具之一。IDEA 是 JetBrains 公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主。

    180 引用 • 400 回帖 • 1 关注
  • AngularJS

    AngularJS 诞生于 2009 年,由 Misko Hevery 等人创建,后为 Google 所收购。是一款优秀的前端 JS 框架,已经被用于 Google 的多款产品当中。AngularJS 有着诸多特性,最为核心的是:MVC、模块化、自动化双向数据绑定、语义化标签、依赖注入等。2.0 版本后已经改名为 Angular。

    12 引用 • 50 回帖 • 437 关注
  • DNSPod

    DNSPod 建立于 2006 年 3 月份,是一款免费智能 DNS 产品。 DNSPod 可以为同时有电信、网通、教育网服务器的网站提供智能的解析,让电信用户访问电信的服务器,网通的用户访问网通的服务器,教育网的用户访问教育网的服务器,达到互联互通的效果。

    6 引用 • 26 回帖 • 524 关注
  • 七牛云

    七牛云是国内领先的企业级公有云服务商,致力于打造以数据为核心的场景化 PaaS 服务。围绕富媒体场景,七牛先后推出了对象存储,融合 CDN 加速,数据通用处理,内容反垃圾服务,以及直播云服务等。

    26 引用 • 222 回帖 • 169 关注