spring 的 IOC 流程(源码流程)

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

先从源码来看 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 引用 • 1459 回帖 • 31 关注

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • VirtualBox

    VirtualBox 是一款开源虚拟机软件,最早由德国 Innotek 公司开发,由 Sun Microsystems 公司出品的软件,使用 Qt 编写,在 Sun 被 Oracle 收购后正式更名成 Oracle VM VirtualBox。

    10 引用 • 2 回帖 • 13 关注
  • 智能合约

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

    1 引用 • 11 回帖 • 8 关注
  • 倾城之链
    23 引用 • 66 回帖 • 138 关注
  • 职场

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

    127 引用 • 1705 回帖
  • 互联网

    互联网(Internet),又称网际网络,或音译因特网、英特网。互联网始于 1969 年美国的阿帕网,是网络与网络之间所串连成的庞大网络,这些网络以一组通用的协议相连,形成逻辑上的单一巨大国际网络。

    98 引用 • 344 回帖
  • Netty

    Netty 是一个基于 NIO 的客户端-服务器编程框架,使用 Netty 可以让你快速、简单地开发出一个可维护、高性能的网络应用,例如实现了某种协议的客户、服务端应用。

    49 引用 • 33 回帖 • 19 关注
  • Notion

    Notion - The all-in-one workspace for your notes, tasks, wikis, and databases.

    6 引用 • 29 回帖
  • Redis

    Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。从 2010 年 3 月 15 日起,Redis 的开发工作由 VMware 主持。从 2013 年 5 月开始,Redis 的开发由 Pivotal 赞助。

    286 引用 • 248 回帖 • 76 关注
  • 又拍云

    又拍云是国内领先的 CDN 服务提供商,国家工信部认证通过的“可信云”,乌云众测平台认证的“安全云”,为移动时代的创业者提供新一代的 CDN 加速服务。

    21 引用 • 37 回帖 • 541 关注
  • 电影

    这是一个不能说的秘密。

    120 引用 • 599 回帖
  • jsDelivr

    jsDelivr 是一个开源的 CDN 服务,可为 npm 包、GitHub 仓库提供免费、快速并且可靠的全球 CDN 加速服务。

    5 引用 • 31 回帖 • 54 关注
  • HHKB

    HHKB 是富士通的 Happy Hacking 系列电容键盘。电容键盘即无接点静电电容式键盘(Capacitive Keyboard)。

    5 引用 • 74 回帖 • 465 关注
  • CongSec

    本标签主要用于分享网络空间安全专业的学习笔记

    1 引用 • 1 回帖 • 10 关注
  • Thymeleaf

    Thymeleaf 是一款用于渲染 XML/XHTML/HTML5 内容的模板引擎。类似 Velocity、 FreeMarker 等,它也可以轻易的与 Spring 等 Web 框架进行集成作为 Web 应用的模板引擎。与其它模板引擎相比,Thymeleaf 最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个 Web 应用。

    11 引用 • 19 回帖 • 354 关注
  • 博客

    记录并分享人生的经历。

    273 引用 • 2388 回帖
  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    334 引用 • 323 回帖
  • Q&A

    提问之前请先看《提问的智慧》,好的问题比好的答案更有价值。

    7929 引用 • 36268 回帖 • 169 关注
  • Gzip

    gzip (GNU zip)是 GNU 自由软件的文件压缩程序。我们在 Linux 中经常会用到后缀为 .gz 的文件,它们就是 Gzip 格式的。现今已经成为互联网上使用非常普遍的一种数据压缩格式,或者说一种文件格式。

    9 引用 • 12 回帖 • 136 关注
  • C

    C 语言是一门通用计算机编程语言,应用广泛。C 语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。

    85 引用 • 165 回帖
  • LeetCode

    LeetCode(力扣)是一个全球极客挚爱的高质量技术成长平台,想要学习和提升专业能力从这里开始,充足技术干货等你来啃,轻松拿下 Dream Offer!

    209 引用 • 72 回帖
  • 工具

    子曰:“工欲善其事,必先利其器。”

    285 引用 • 728 回帖
  • uTools

    uTools 是一个极简、插件化、跨平台的现代桌面软件。通过自由选配丰富的插件,打造你得心应手的工具集合。

    6 引用 • 14 回帖 • 1 关注
  • Hibernate

    Hibernate 是一个开放源代码的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,使得 Java 程序员可以随心所欲的使用对象编程思维来操纵数据库。

    39 引用 • 103 回帖 • 705 关注
  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    22002 引用 • 87712 回帖
  • 域名

    域名(Domain Name),简称域名、网域,是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位(有时也指地理位置)。

    43 引用 • 208 回帖
  • NGINX

    NGINX 是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 NGINX 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日。

    311 引用 • 546 回帖
  • Elasticsearch

    Elasticsearch 是一个基于 Lucene 的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于 RESTful 接口。Elasticsearch 是用 Java 开发的,并作为 Apache 许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

    117 引用 • 99 回帖 • 224 关注