文档章节

Spring 启动记录(6)

zswitos
 zswitos
发布于 2017/01/13 11:08
字数 2326
阅读 17
收藏 0

XmlWebApplicationContext的loadBeanDefinitions

此时到了真正的加载xml到spring过程

大概分为加载xml资源,然后xmlreader去解析相应的配置生成BeanDefinition,注册到spring工程内存中

1、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(getEnvironment());
   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);
}

具体的XmlBeanDefinitionReader继承图

 

 

这个类需要两个对象的功能,即正合了资源加载和注册的两个功能

注册就是实现了BeanDifinitionRegisitry DafaultListableBeanFactory工厂类,

和实现了资源加载的AbstractRefreshableApplicationContext工厂类,

可见spring的工厂类实质是资源加载获取跟获取BeanDefinie并注册两个类

 

 

具体的XmlBeanDefinitionReader的初始化如上

因为DefaultListableBeanFactory没有实现ResourceLoader,所以设置ResourceLoader是

this.resourceLoader = new PathMatchingResourcePatternResolver();

同理Envrioment也

this.environment = new StandardEnvironment();

但是到

loadBeanDefinitions时候
// resource loading environment.
beanDefinitionReader.setEnvironment(getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

又设置成当前Factory的Enviroment和ResourceLoader

到loadBeanDefinitions(beanDefinitionReader)开始执行加载Resource

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
   String[] configLocations = getConfigLocations();
   if (configLocations != null) {
      for (String configLocation : configLocations) {
         reader.loadBeanDefinitions(configLocation);
      }
   }
}

AbstractBeanDefinitionReader

public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
   return loadBeanDefinitions(location, null);
}
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
   ResourceLoader resourceLoader = getResourceLoader();
   if (resourceLoader == null) {
      throw new BeanDefinitionStoreException(
            "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
   }

   if (resourceLoader instanceof ResourcePatternResolver) {
      // Resource pattern matching available.
      try {
         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;
   }
}

AbstractApplicationContext:

@Override
public Resource[] getResources(String locationPattern) throws IOException {
   return this.resourcePatternResolver.getResources(locationPattern);
}

AbstractApplicationContext的resourcePatternResolver是

PathMatchingResourcePatternResolver

public Resource[] getResources(String locationPattern) throws IOException {
   Assert.notNull(locationPattern, "Location pattern must not be null");
   //处理以classpath*开头的路径:
   if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
      // a class path resource (multiple resources for same name possible)
      if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
         // a class path resource pattern
         return findPathMatchingResources(locationPattern);
      }
      else {
         // all class path resources with the given name
         return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
      }
   }
   else {
      //classpath:开头的路径
      // Only look for a pattern after a prefix here
      // (to not get fooled by a pattern symbol in a strange prefix).
      int prefixEnd = locationPattern.indexOf(":") + 1;
      if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
         // a file pattern
         return findPathMatchingResources(locationPattern);
      }
      else {
         // a single resource with the given name
         return new Resource[] {getResourceLoader().getResource(locationPattern)};
      }
   }
}
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
   String rootDirPath = determineRootDir(locationPattern);
   String subPattern = locationPattern.substring(rootDirPath.length());
   Resource[] rootDirResources = getResources(rootDirPath);
   Set<Resource> result = new LinkedHashSet<Resource>(16);
   for (Resource rootDirResource : rootDirResources) {
      rootDirResource = resolveRootDirResource(rootDirResource);
      if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
         result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
      }
      else if (isJarResource(rootDirResource)) {
         result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
      }
      else {
         result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
      }
   }
   if (logger.isDebugEnabled()) {
      logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
   }
   return result.toArray(new Resource[result.size()]);
}
public boolean isPattern(String path) {
  //判断路径中是否包含:* ?
   return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
}

以classpath:*.xml为例子

然后获取到rootDirPath:classpath: , subPattern:*.xml

然后再次调用 findPathMatchingResources 走

return new Resource[] {getResourceLoader().getResource(locationPattern)};

调用到DefaultResourceLoader 

public Resource getResource(String location) {
   Assert.notNull(location, "Location must not be null");
   if (location.startsWith("/")) {
      return getResourceByPath(location);
   }
   else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
      return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
   }
   else {
      try {
         // Try to parse the location as a URL...
         URL url = new URL(location);
         return new UrlResource(url);
      }
      catch (MalformedURLException ex) {
         // No URL -> resolve as resource path.
         return getResourceByPath(location);
      }
   }
}

然后为“”所以走

new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());

 

最后回到AbstractBeanDefinitionReader的

 

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
   ResourceLoader resourceLoader = getResourceLoader();
   if (resourceLoader == null) {
      throw new BeanDefinitionStoreException(
            "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
   }

   if (resourceLoader instanceof ResourcePatternResolver) {
      // Resource pattern matching available.
      try {
         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;
   }
}
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;
}

XmlBeanDefinitionReader

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
   return loadBeanDefinitions(new EncodedResource(resource));
}
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<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
   //讲Resource放到本地的ThreadLocal对象中去
   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 { 
        //读取资源获取流传入doLoadBeanDefinitions
         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();
      }
   }
}

 

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
      throws BeanDefinitionStoreException {
   try {
      Document doc = doLoadDocument(inputSource, resource);
      return registerBeanDefinitions(doc, resource);
   }
}
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
   return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
         getValidationModeForResource(resource), isNamespaceAware());
}
protected EntityResolver getEntityResolver() {
   if (this.entityResolver == null) {
      // Determine default EntityResolver to use.
      ResourceLoader resourceLoader = getResourceLoader();
      if (resourceLoader != null) {
         this.entityResolver = new ResourceEntityResolver(resourceLoader);
      }
      else {
         this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
      }
   }
   return this.entityResolver;
}


继而出现了新的几个类:

EntityResolver : 加载dtd、xsd文件的资源加载类

ErrorHandler : 在xml解析过程出错处理报告错误的处理类

 

首先看下EntityResolver:

ResourceEntityResolver

 

这个是负责获取dtd、xsd的文件inputsource流的,寻找spring xml中的引用的dtd、xsd的文件

 

DelegatingEntityResolver中引入了这个两个对象做处理

ErrorHandler:

 

public class SimpleSaxErrorHandler implements ErrorHandler {

   private final Log logger;


   /**
    * Create a new SimpleSaxErrorHandler for the given
    * Commons Logging logger instance.
    */
   public SimpleSaxErrorHandler(Log logger) {
      this.logger = logger;
   }


   @Override
   public void warning(SAXParseException ex) throws SAXException {
      logger.warn("Ignored XML validation warning", ex);
   }

   @Override
   public void error(SAXParseException ex) throws SAXException {
      throw ex;
   }

   @Override
   public void fatalError(SAXParseException ex) throws SAXException {
      throw ex;
   }

根据xml内容去决定是否dtd的还是xsd的验证模式

public static final int VALIDATION_XSD = 3; //xsd验证
public static final int VALIDATION_DTD = 2; //dtd验证
protected int detectValidationMode(Resource resource) {
   if (resource.isOpen()) {
      throw new BeanDefinitionStoreException(
            "Passed-in Resource [" + resource + "] contains an open stream: " +
            "cannot determine validation mode automatically. Either pass in a Resource " +
            "that is able to create fresh streams, or explicitly specify the validationMode " +
            "on your XmlBeanDefinitionReader instance.");
   }

   InputStream inputStream;
   try {
      inputStream = resource.getInputStream();
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException(
            "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
            "Did you attempt to load directly from a SAX InputSource without specifying the " +
            "validationMode on your XmlBeanDefinitionReader instance?", ex);
   }

   try {
      return this.validationModeDetector.detectValidationMode(inputStream);
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
            resource + "]: an error occurred whilst reading from the InputStream.", ex);
   }
}

验证xml的XmlValidationModeDetector 如下:

/*
 * Copyright 2002-2014 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.util.xml;

import java.io.BufferedReader;
import java.io.CharConversionException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.springframework.util.StringUtils;

/**
 * Detects whether an XML stream is using DTD- or XSD-based validation.
 *
 * @author Rob Harrop
 * @author Juergen Hoeller
 * @since 2.0
 */
public class XmlValidationModeDetector {

   /**
    * Indicates that the validation should be disabled.
    */
   public static final int VALIDATION_NONE = 0;

   /**
    * Indicates that the validation mode should be auto-guessed, since we cannot find
    * a clear indication (probably choked on some special characters, or the like).
    */
   public static final int VALIDATION_AUTO = 1;

   /**
    * Indicates that DTD validation should be used (we found a "DOCTYPE" declaration).
    */
   public static final int VALIDATION_DTD = 2;

   /**
    * Indicates that XSD validation should be used (found no "DOCTYPE" declaration).
    */
   public static final int VALIDATION_XSD = 3;


   /**
    * The token in a XML document that declares the DTD to use for validation
    * and thus that DTD validation is being used.
    */
   private static final String DOCTYPE = "DOCTYPE";

   /**
    * The token that indicates the start of an XML comment.
    */
   private static final String START_COMMENT = "<!--";

   /**
    * The token that indicates the end of an XML comment.
    */
   private static final String END_COMMENT = "-->";


   /**
    * Indicates whether or not the current parse position is inside an XML comment.
    */
   private boolean inComment;


   /**
    * Detect the validation mode for the XML document in the supplied {@link InputStream}.
    * Note that the supplied {@link InputStream} is closed by this method before returning.
    * @param inputStream the InputStream to parse
    * @throws IOException in case of I/O failure
    * @see #VALIDATION_DTD
    * @see #VALIDATION_XSD
    */
   public int detectValidationMode(InputStream inputStream) throws IOException {
      // Peek into the file to look for DOCTYPE.
      BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
      try {
         boolean isDtdValidated = false;
         String content;
         while ((content = reader.readLine()) != null) {
            content = consumeCommentTokens(content);
            if (this.inComment || !StringUtils.hasText(content)) {
               continue;
            }
            if (hasDoctype(content)) {
               isDtdValidated = true;
               break;
            }
            if (hasOpeningTag(content)) {
               // End of meaningful data...
               break;
            }
         }
         return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
      }
      catch (CharConversionException ex) {
         // Choked on some character encoding...
         // Leave the decision up to the caller.
         return VALIDATION_AUTO;
      }
      finally {
         reader.close();
      }
   }


   /**
    * Does the content contain the the DTD DOCTYPE declaration?
    */
   private boolean hasDoctype(String content) {
      return content.contains(DOCTYPE);
   }

   /**
    * Does the supplied content contain an XML opening tag. If the parse state is currently
    * in an XML comment then this method always returns false. It is expected that all comment
    * tokens will have consumed for the supplied content before passing the remainder to this method.
    */
   private boolean hasOpeningTag(String content) {
      if (this.inComment) {
         return false;
      }
      int openTagIndex = content.indexOf('<');
      return (openTagIndex > -1 && (content.length() > openTagIndex + 1) &&
            Character.isLetter(content.charAt(openTagIndex + 1)));
   }

   /**
    * Consumes all the leading comment data in the given String and returns the remaining content, which
    * may be empty since the supplied content might be all comment data. For our purposes it is only important
    * to strip leading comment content on a line since the first piece of non comment content will be either
    * the DOCTYPE declaration or the root element of the document.
    */
   private String consumeCommentTokens(String line) {
      if (!line.contains(START_COMMENT) && !line.contains(END_COMMENT)) {
         return line;
      }
      while ((line = consume(line)) != null) {
         if (!this.inComment && !line.trim().startsWith(START_COMMENT)) {
            return line;
         }
      }
      return line;
   }

   /**
    * Consume the next comment token, update the "inComment" flag
    * and return the remaining content.
    */
   private String consume(String line) {
      int index = (this.inComment ? endComment(line) : startComment(line));
      return (index == -1 ? null : line.substring(index));
   }

   /**
    * Try to consume the {@link #START_COMMENT} token.
    * @see #commentToken(String, String, boolean)
    */
   private int startComment(String line) {
      return commentToken(line, START_COMMENT, true);
   }

   private int endComment(String line) {
      return commentToken(line, END_COMMENT, false);
   }

   /**
    * Try to consume the supplied token against the supplied content and update the
    * in comment parse state to the supplied value. Returns the index into the content
    * which is after the token or -1 if the token is not found.
    */
   private int commentToken(String line, String token, boolean inCommentIfPresent) {
      int index = line.indexOf(token);
      if (index > - 1) {
         this.inComment = inCommentIfPresent;
      }
      return (index == -1 ? index : index + token.length());
   }

}

然后 DefaultDocumentLoader 的loadDocument获取Document对象

public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
      ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
   //创建DocumentBuilderFactory
   DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
   if (logger.isDebugEnabled()) {
      logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
   }
  //创建DocumentBuilder
   DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
   //创建Document
   return builder.parse(inputSource);
}
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
      throws BeanDefinitionStoreException {
   try {
      Document doc = doLoadDocument(inputSource, resource);
      return registerBeanDefinitions(doc, resource);
   }
  
}

注册BeanDefinitions:XmlBeanDefinitionReader

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
   int countBefore = getRegistry().getBeanDefinitionCount();
   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
   return getRegistry().getBeanDefinitionCount() - countBefore;
}

注册BeanDefinitionDocumentReader

private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
   return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
}

 

public XmlReaderContext createReaderContext(Resource resource) {
   return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
         this.sourceExtractor, this, getNamespaceHandlerResolver());
}
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
   if (this.namespaceHandlerResolver == null) {
      this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
   }
   return this.namespaceHandlerResolver;
}
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
   return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
}

 

 

DefaultBeanDefinitionDocumentReader 的registerBeanDefinitions

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   this.readerContext = readerContext;
   logger.debug("Loading bean definitions");
   Element root = doc.getDocumentElement();
   doRegisterBeanDefinitions(root);
}

 

© 著作权归作者所有

zswitos
粉丝 4
博文 60
码字总数 55712
作品 0
海淀
程序员
私信 提问
Spring Batch 之 Spring Batch 简介(一)

Spring Batch是一个轻量级的,完全面向Spring的批处理框架,可以应用于企业级大量的数据处理系统。Spring Batch以POJO和大家熟知的Spring框架为基础,使开发者更容易的访问和利用企业级服务。...

长平狐
2012/08/27
985
1
SpringBoot学习之基础篇

在前面的博文中,已经演示过springboot与Mybatis集成的实例,本篇再来探讨一下SpringBoot的基础。 一。关于SpringBoot   SpringBoot可以基于Spring轻松创建可以“运行”的、独立的、生产级...

java~nick
2017/10/31
0
0
Spring Boot整合MyBatis学习总结

公司的很多项目都陆陆续续引入了Spring Boot,通过对Spring Boot的接触了解发现其真的是大大地简化了开发、简化了依赖配置,很多功能注解一下就可以实现,真的是太方便了。下面记录了一个Spr...

zhuwensheng
2018/06/29
0
0
Spring Boot开发之流水无情(二)

上篇散仙写了一个很简单的入门级的Spring Boot的例子,没啥技术含量,不过,其实学任何东西只要找到第一个突破口,接下来的事情就好办了,人最怕什么? 我想莫过于干一件事情,没有下手的地方...

九劫散仙
2015/04/26
0
22
Spring4+Springmvc+quartz实现多线程动态定时调度

scheduler定时调度系统是大多行业项目都需要的,传统的spring-job模式,个人感觉已经out了,因为存在很多的问题,特别是定时调度的追加、修改、删除等,需要修改xml,xml的配置生效无非是热部...

SpringCloud关注者
2017/11/02
1K
8

没有更多内容

加载失败,请刷新页面

加载更多

基础工具类

package com.atguigu.util;import java.sql.Connection;import java.sql.SQLException;import java.util.Properties;import javax.sql.DataSource;import com.alibaba.druid......

architect刘源源
今天
43
0
P30 Pro劲敌!DxO官宣新机:排行榜又要变

5月26日晚间,DxOMark官方推特预告,将在5月27日公布一款新机型的DxOMark评分,猜猜是哪款? 网友猜想的机型有:红米K20、谷歌Pixel 3a、索尼Xperia 1、诺基亚9 PureView等。 DxOMark即将公布...

linux-tao
昨天
15
0
Ubuntu18.04.2窗口过小不能自适应(二次转载)

解决Ubuntu在虚拟机窗口不能自适应 2018年09月06日 16:20:08 起不了名儿 阅读数 855 此博文转载:https://blog.csdn.net/nuddlle/article/details/77994080(原地址) 试了很多办法这个好用 ...

tahiti_aa
昨天
2
0
死磕 java同步系列之CountDownLatch源码解析

问题 (1)CountDownLatch是什么? (2)CountDownLatch具有哪些特性? (3)CountDownLatch通常运用在什么场景中? (4)CountDownLatch的初始次数是否可以调整? 简介 CountDownLatch,可以...

彤哥读源码
昨天
6
0
Nginx提供下载apk服务

有时候我们可能需要提供文件或者其他apk下载链接,通过 nginx 配置可以很简单地实现。 server {    listen 80;    server_name download.xxx.com;    root app;    locati...

Jack088
昨天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部