Spring源碼系列(二)–bean組件的源碼分析

簡介

spring-bean 組件是 Spring IoC 的核心,我們可以使用它的 beanFactory 來獲取所需的對象,對象的實例化、屬性裝配和初始化等都可以交給 spring 來管理。 本文將從DefaultListableBeanFactory.getBean(Class)方法開始分析獲取 bean 的過程,主要內容如下,由於篇幅較長,可以根據需要選擇閱讀:

  1. beanFactory 的設計
  2. 多個 beanName 的處理
  3. 獲取單例 bean
  4. 創建單例 bean
  5. bean 的實例化
  6. bean 的屬性裝配
  7. bean 的初始化(省略)

spring-bean 的源碼比較多,有些不影響整體分析思路的代碼會被省略掉(代碼中會註明),另外,想要分析所有的代碼可能不大現實,所以,針對部分內容,我會點到為止,例如,本文只分析單例 bean 而不分析多例 bean。

前篇回顧

上篇博客Spring源碼系列(一)–詳細介紹bean組件介紹了 bean 組件的一些重要理論概念,並通過例子演示如何使用 bean 組件。這裏回顧下,這幾個概念非常重要,是 bean 組件的理論基礎:

  1. 實例化、屬性裝配和初始化的概念。 實例化指創建出一個新的對象;屬性裝配指給對象的成員屬性賦值; 初始化指調用對象的初始化方法。
  2. 什麼是 bean:某個類的實例或描述對象,被註冊到了 Spring IoC 容器,這時通過 Spring IoC 容器獲取的這個類的對象就是 bean。
  3. 什麼是 beanFactory:一個工廠,用於註冊 bean 和獲取 bean。
  4. 什麼是 beanDefinition:一個描述對象,用來描述 bean 的實例化、屬性裝配、初始化等信息。

beanFactory的設計

從客戶端來看,一個完整的 beanFactory 工廠包含以下基本功能:

  1. 註冊別名。對應下圖的AliasRegistry接口。
  2. 註冊單例對象。對應下圖的SingletonBeanRegistry接口。
  3. 註冊BeanDefinition對象。對應下圖的BeanDefinitionRegistry接口。
  4. 獲取 bean。對應下圖的BeanFactory接口。

在 spring-bean 組件中,DefaultListableBeanFactory就是一個完整的 beanFactory 工廠,也可以說是一個 IoC 容器。

BeanFactory還有幾個擴展接口,用的比較多的可能是ConfigurableBeanFactoryAutowireCapableBeanFactory

  1. HierarchicalBeanFactory用於提供父子工廠的支持。例如,當前 beanFactory 找不到 bean 時,會嘗試從 parent beanFactory 中獲取。
  2. ConfigurableBeanFactory用於提供配置 beanFactory 的支持。例如,註冊BeanPostProcessor、註冊 TypeConverter、註冊OrderComparator等。
  3. ListableBeanFactory用於提供批量獲取 bean 的支持(不包含父工廠的 bean)。例如,我們可以根據類型獲取 beanName-bean 的 map。
  4. AutowireCapableBeanFactory用於提供實例化、屬性裝配、初始化等一系列管理 bean 生命周期的支持。 例如,該接口包含了 createBean、autowireBean、initializeBean、destroyBean 等方法。

當我們註冊 bean 時,根據註冊方式的不同,bean 的註冊信息會被放入兩個不同的地方。

class DefaultSingletonBeanRegistry {
	// beanName=singletonObject鍵值對
    // 除了registerSingleton的會放在這裏,registerBeanDefinition生成的單例bean實例也會放在這裏
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
}
class DefaultListableBeanFactory {
	// beanName=beanDefination鍵值對
	private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
}

接下來開始分析源碼,註冊 bean 比較簡單,這裏就不看了,我們直接看 getBean(Class) 的代碼。

從getBean(requiredType)方法開始

進入到 DefaultListableBeanFactory.getBean(Class)方法,並逐漸展開。在DefaultListableBeanFactory.resolveBean(ResolvableType, Object[], boolean)方法中,如果當前 beanFactory 中獲取不到這個 bean,將嘗試從 parent beanFactory 中獲取,這也說明了一點:父子 beanFactory 中允許存在相同 beanName 的 bean,只是獲取時當前 beanFactory 的優先級更高一些

	public <T> T getBean(Class<T> requiredType) throws BeansException {
        // 適配入參
        // 可以看到,我們獲取bean時還可以指定構造參數
		return getBean(requiredType, (Object[]) null);
	}
	public <T> T getBean(Class<T> requiredType, @Nullable Object... args) throws BeansException {
		Assert.notNull(requiredType, "Required type must not be null");
        // 繼續適配入參
        // 這裏的第三個參數表示,如果指定類型對應的beanName不唯一時,true為返回null, false為拋出異常
		Object resolved = resolveBean(ResolvableType.forRawClass(requiredType), args, false);
        // 如果獲取不到這個bean,拋出異常
		if (resolved == null) {
			throw new NoSuchBeanDefinitionException(requiredType);
		}
		return (T) resolved;
	}
	private <T> T resolveBean(ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) {
        // 這裏的NamedBeanHolder就是簡單的對bean實例封裝了一層,不用太關注
		NamedBeanHolder<T> namedBean = resolveNamedBean(requiredType, args, nonUniqueAsNull);
        // 如果獲取得到bean實例,則返回
		if (namedBean != null) {
			return namedBean.getBeanInstance();
		}
        // 如果沒有,嘗試從parent beanFactory中獲取
        // 這部分代碼省略······
		return null;
	}

存在多個beanName怎麼辦

通過 beanType 來獲取 bean,可能會存在一個類型關聯了多個 beanName 的情況,使用例子中我們說過,可以通過指定 beanDefination 的 isPrimary = true 或者註冊比較器的方式來解決。接下來我們看下具體的處理過程。

進入到DefaultListableBeanFactory.resolveNamedBean(ResolvableType, Object[], boolean)方法。如果指定類型匹配到了多個 beanName,會進行以下處理:

  1. 如果存在通過registerSingleton註冊的 beanName,或者通過registerBeanDefinition註冊且 autowireCandidate = true 的 beanName,則僅保留它們,並剔除其他的 beanName;
  2. 如果還是存在多個 beanName,檢查是否存在唯一一個通過registerBeanDefinitionisPrimary = true的(存在多個會報錯),存在的話將它作為匹配到的唯一 beanName;
  3. 如果還是存在多個 beanName,通過我們註冊的OrderComparator來確定優先值最小的作為唯一 beanName,注意,通過registerSingleton註冊的和通過registerBeanDefinition註冊的,比較的對象是不一樣的;
  4. 如果還是存在多個 beanName,根據 nonUniqueAsNull,為 true 是返回 null,為 false 拋出NoUniqueBeanDefinitionException異常。
	private <T> NamedBeanHolder<T> resolveNamedBean(
			ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) throws BeansException {

		Assert.notNull(requiredType, "Required type must not be null");
        // 獲取指定類型的所有beanName,可能匹配到多個
		String[] candidateNames = getBeanNamesForType(requiredType);
        
		// 如果指定類型匹配到了多個beanName,進行以下操作:
        // 如果存在通過registerSingleton註冊的beanName,或者通過registerBeanDefinition註冊且 autowireCandidate = true的beanName,則僅保留它們,並剔除其他的beanName;
		if (candidateNames.length > 1) {
			List<String> autowireCandidates = new ArrayList<>(candidateNames.length);
			for (String beanName : candidateNames) {
				if (!containsBeanDefinition(beanName) || getBeanDefinition(beanName).isAutowireCandidate()) {
					autowireCandidates.add(beanName);
				}
			}
			if (!autowireCandidates.isEmpty()) {
				candidateNames = StringUtils.toStringArray(autowireCandidates);
			}
		}
        
		// 如果只剩下一個beanName,那就根據beanName和beanType獲取bean
		if (candidateNames.length == 1) {
			String beanName = candidateNames[0];
			return new NamedBeanHolder<>(beanName, (T) getBean(beanName, requiredType.toClass(), args));
		}
        
        // 如果存在多個,則還要進一步處理
		else if (candidateNames.length > 1) {
			Map<String, Object> candidates = new LinkedHashMap<>(candidateNames.length);
            // 遍歷候選的beanName
			for (String beanName : candidateNames) {
                // 如果該beanName是通過registerSingleton註冊的,且傳入構造參數為空
                // 則獲取該bean實例,並放入candidates
				if (containsSingleton(beanName) && args == null) {
					Object beanInstance = getBean(beanName);
					candidates.put(beanName, (beanInstance instanceof NullBean ? null : beanInstance));
				}
				else {
                    // 其他情況下,則獲取該beanName對應的類型,並放入candidates
                    // 注意,這裏的類型不一定是我們入參指定的類型,例如,如果我指定的是UserServiceFactoryBean.class,這裏返回的卻是UserService.class
					candidates.put(beanName, getType(beanName));
				}
			}
            // 如果裏面存在唯一一個通過registerBeanDefinition註冊的且isPrimary=true(存在多個會報錯),則將它作為匹配到的唯一beanName
			String candidateName = determinePrimaryCandidate(candidates, requiredType.toClass());
            // 如果還是確定不了,則通過我們註冊的OrderComparator來判斷candidates中value的優先數,挑選優先數最小的value對應的key作為唯一的beanName
			if (candidateName == null) {
				candidateName = determineHighestPriorityCandidate(candidates, requiredType.toClass());
			}
			if (candidateName != null) {
				Object beanInstance = candidates.get(candidateName);
                // 如果candidates中的value本身就是一個bean實例,那麼直接返回就好了
                // 如果不是,則根據beanName和beanType獲取bean
				if (beanInstance == null || beanInstance instanceof Class) {
					beanInstance = getBean(candidateName, requiredType.toClass(), args);
				}
				return new NamedBeanHolder<>(candidateName, (T) beanInstance);
			}
            // 如果還是確定不了唯一beanName,且設置了nonUniqueAsNull=false(默認為false),則會拋錯
			if (!nonUniqueAsNull) {
				throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet());
			}
		}
		
		return null;
	}

根據beanName和beanType獲取bean

進入AbstractBeanFactory.getBean(String, Class<T>, Object...)。這個方法里包括四個步驟:

  1. 轉義name。主要指的是當 name 是別名或者是 “&” + factory beanName 形式時進行轉義;
  2. 如果是單例 bean 且構造參數為空,則會從 singletonObjects 中獲取已生成的 bean,或者從 earlySingletonObjects/singletonFactories 中獲取已經實例化但可能還沒裝配或初始化的 bean。如果獲取到的不是 null,直接返回對應的 bean 實例;
  3. 如果當前 beanFactory 沒有指定的 beanName,則會去 parent beanFactory 中獲取;
  4. 如果當前 bean 需要依賴其他 bean,則會先獲取依賴的 bean;
  5. 根據 scope 選擇生成單例 bean 還是多例 bean;
  6. 進行類型檢查,如果獲取的 bean 不匹配,會先用我們註冊的類型轉換器轉換,如果還是不匹配就拋出BeanNotOfRequiredTypeException
	public <T> T getBean(String name, @Nullable Class<T> requiredType, @Nullable Object... args)
			throws BeansException {
		// 適配入參
        // 這裏最後一個參數指獲取的bean是否純粹用於類型檢查,如果是的話,beanFactory不會標記這個bean正在生成中,僅對單例bean有用
		return doGetBean(name, requiredType, args, false);
	}
	@SuppressWarnings("unchecked")
	protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
		// 轉義我們傳入的name,這裏包括兩個內容:
        // 1. 如果是別名,需要轉換為別名對象的beanName;
        // 2. 如果是“&”+factoryBeanName,則需要去掉前面的“&”
		final String beanName = transformedBeanName(name);
		Object bean;

		// 獲取單例
        // 注意,這裏獲取到的有可能是已經初始化,也有可能是還沒初始化,甚至還沒裝配的bean
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			// 省略日誌部分······
            
            // 獲取bean,因為sharedInstance有可能是factoryBean,如果我們要的是factoryBean對應的bean,則還要getObject
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}

		else {
			// 如果當前線程已經在生成beanName對應的bean,就會拋錯
			if (isPrototypeCurrentlyInCreation(beanName)) {
				throw new BeanCurrentlyInCreationException(beanName);
			}

			// 如果當前beanFactory沒有指定的beanName,則會去parent beanFactory中獲取
            // 這部分省略······
			
			// 這裏標記指定bean正在創建中,一般對單例bean才有意義
			if (!typeCheckOnly) {
				markBeanAsCreated(beanName);
			}

			try {
                // 獲取指定beanName對應的RootBeanDefinition對象
				final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                // 檢查RootBeanDefinition,目前就是檢查是否對應的類型為抽象類,是的話拋錯
				checkMergedBeanDefinition(mbd, beanName, args);

				// 如果當前bean需要依賴其他bean,則會先獲取依賴的bean
				// 這部分省略······

				// 創建單例bean
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
                            // 進入創建bean或factoryBean
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							destroySingleton(beanName);
							throw ex;
						}
					});
                    // 獲取bean實例
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}
				// 創建多例bean
				else if (mbd.isPrototype()) {
					Object prototypeInstance = null;
					try {
                        // 標記當前線程正在創建這個bean
						beforePrototypeCreation(beanName);
                        // 進入創建bean或factoryBean
						prototypeInstance = createBean(beanName, mbd, args);
					}
					finally {
                        // 去掉當前線程中這個bean正在創建的標記
						afterPrototypeCreation(beanName);
					}
					bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				}
				// 接下來這種一般是自定義Scope的情況,這裏省略不討論
				else {
					// ·······
				}
			}
			catch (BeansException ex) {
				cleanupAfterBeanCreationFailure(beanName);
				throw ex;
			}
		}

		// 如果獲取到的bean實例不是我們指定的類型
		if (requiredType != null && !requiredType.isInstance(bean)) {
			try {
                // 使用我們註冊的類型轉換器進行轉換
				T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
                // 如果轉換不了,則會拋錯
				if (convertedBean == null) {
					throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
				}
				return convertedBean;
			}
			catch (TypeMismatchException ex) {
				throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
			}
		}
		return (T) bean;
	}

由於單例 bean 和多例 bean 的創建差不多,本文只選擇單例的來分析。

獲取單例bean

進入DefaultSingletonBeanRegistry.getSingleton(String, ObjectFactory)。這個方法包括幾個過程,主要就是處理一些多線程問題:

  1. 獲取指定 beanName 的 bean,如果已經存在,就不去創建,這時為了處理多線程同時創建 bean 的問題;
  2. 如果當前 bean 已經在創建中,會拋出 BeanCurrentlyInCreationException,創建單例 bean 之前是有加鎖的,按理不會出現這種情況;
  3. 創建單例 bean;
  4. 如果創建成功,將 bean 實例加入 singletonObjects,並且刪除掉 singletonFactories 和 earlySingletonObjects 中對應的鍵值對。
	public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "Bean name must not be null");
        // 這裏我不是很理解,為什麼使用singletonObjects作為鎖
        // 因為從earlySingletonObjects/singletonFactories中獲取已經實例化但可能還沒裝配或初始化的 bean時,用的鎖也是singletonObjects,這樣的話,提前暴露的機制好像就廢掉了啊???TODO
		synchronized (this.singletonObjects) {
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
                // 如果當前beanFactory的單例正在銷毀,則不允許創建單例
				if (this.singletonsCurrentlyInDestruction) {
					// 省略拋錯······
				}
				
				// 判斷當前bean是不是已經在創建中,是的話拋出BeanCurrentlyInCreationException
                // 由於加了鎖,這種情況應該是不會發生的
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
                
                // 省略部分代碼······
                
				try {
                    // 這裏的執行的是createBean方法
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
                // 這種情況我不是很理解,singletonObjects的操作不應該被鎖住了嗎?TODO
				catch (IllegalStateException ex) {
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						throw ex;
					}
				}
                // 如果拋出的是BeanCreationException,
				catch (BeanCreationException ex) {
                    // 省略部分代碼······
                        
					throw ex;
				}
				finally {
                    // 省略部分代碼······
                    
                    // 如果當前bean不處於創建狀態中,會拋出IllegalStateException
					afterSingletonCreation(beanName);
				}
            	// 如果創建成功,將bean實例加入singletonObjects,並且刪除掉singletonFactories和earlySingletonObjects中對應的鍵值對
				if (newSingleton) {
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}

以上方法中,如果獲取不到已生成的單例 bean,則會開始創建 bean。

創建單例bean

進入AbstractAutowireCapableBeanFactory.createBean(String, RootBeanDefinition, Object[])。這個方法包括以下過程:

  1. 解析 beanType,並且再次包裝RootBeanDefinition
  2. 執行我們註冊的InstantiationAwareBeanPostProcessorpostProcessBeforeInstantiation方法,如果返回了非空對象,則將其返回。也就是說我們可以在該方法中自定義完成 bean 的實例化、裝配和初始化。
  3. 創建 bean。
	protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		RootBeanDefinition mbdToUse = mbd;

		// 解析當前RootBeanDefinition對應生成的bean類型,並進行再次包裝
		Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
		if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
			mbdToUse = new RootBeanDefinition(mbd);
			mbdToUse.setBeanClass(resolvedClass);
		}

		// 省略部分代碼······

		try {
			// 執行我們註冊的InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation方法。也就是說我們可以在該方法中自定義完成 bean 的實例化、裝配和初始化。
			Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
            // 如果該方法返回bean,那就直接返回
			if (bean != null) {
				return bean;
			}
		}
		catch (Throwable ex) {
			throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
					"BeanPostProcessor before instantiation of bean failed", ex);
		}

		try {
            // 創建bean
			Object beanInstance = doCreateBean(beanName, mbdToUse, args);
			return beanInstance;
		}
		catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
			throw ex;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
		}
	}

進入AbstractAutowireCapableBeanFactory.doCreateBean(String, RootBeanDefinition, Object[])。這個方法主要包含以下過程:

  1. 實例化 bean;
  2. 執行我們註冊的MergedBeanDefinitionPostProcessorpostProcessMergedBeanDefinition方法;
  3. 如果是單例,將還沒裝配和初始化的 bean 先暴露出去,即放在singletonFactories中,如果其他線程進來獲取,可以將這個 bean 或 factoryBean 返回,而不需要等待;
  4. 屬性裝配;
  5. 初始化;
  6. 將生成的 bean 放入 disposableBeans 中。
	protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {

		BeanWrapper instanceWrapper = null;
        // 實例化
        // 如果是單例,嘗試從factoryBeanInstanceCache中獲取
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
        // 實例化bean
		if (instanceWrapper == null) {
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		final Object bean = instanceWrapper.getWrappedInstance();
		Class<?> beanType = instanceWrapper.getWrappedClass();
		if (beanType != NullBean.class) {
			mbd.resolvedTargetType = beanType;
		}

		// 執行我們註冊的MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition方法
		synchronized (mbd.postProcessingLock) {
			if (!mbd.postProcessed) {
				try {
					applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Post-processing of merged bean definition failed", ex);
				}
				mbd.postProcessed = true;
			}
		}

		// 單例的可以將還沒裝配和初始化的bean先暴露出去,即放在singletonFactories中
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}
        
		Object exposedObject = bean;
		try {
            // 屬性裝配
			populateBean(beanName, mbd, instanceWrapper);
            // 初始化
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}

		// 省略部分代碼······

		// 將生成的bean或factoryBean放入disposableBeans中
		try {
			registerDisposableBeanIfNecessary(beanName, bean, mbd);
		}
		catch (BeanDefinitionValidationException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
		}

		return exposedObject;
	}

接下來將展開 bean 的實例化、屬性裝配和初始化。其中,實例化和屬性裝配的代碼比較複雜,我們重點分析,至於初始化部分,則留給讀者自行閱讀。

實例化

進入AbstractAutowireCapableBeanFactory.createBeanInstance(String, RootBeanDefinition, Object[])。這個方法主要過程如下:

  1. 解析 beanType,並對 beanType 進行一些必要的檢查;
  2. 通過我們設置的 InstanceSupplier 或 FactoryMethod 來直接獲取 bean,如果有的話,直接返回該對象;
  3. 如果構造參數為空,則可以復用已經解析好的構造對象(如果有的話);
  4. 執行我們註冊的SmartInstantiationAwareBeanPostProcessordetermineCandidateConstructors獲取構造對象數組;
  5. 如果得到的數組不是空,或者 beanDefination 的裝配模式為構造注入,或者 beanDefination 包含構造參數,或者我們傳入的構造參數非空,則進入實例化 bean
  6. 其他情況,使用無參構造來實例化。
	protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
		// 解析bean類型
		Class<?> beanClass = resolveBeanClass(mbd, beanName);
		
        // 如果bean類型不是public的,則拋錯
		if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
		}
		
        // 通過RootBeanDefinition中定義的Supplier來獲取實例化bean
		Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
		if (instanceSupplier != null) {
			return obtainFromSupplier(instanceSupplier, beanName);
		}
		// 通過RootBeanDefinition中定義FactoryMethod來實例化bean
		if (mbd.getFactoryMethodName() != null) {
			return instantiateUsingFactoryMethod(beanName, mbd, args);
		}

		// 如果構造參數為空,則可以復用已經解析好的構造對象(如果有的話)
		boolean resolved = false;
		boolean autowireNecessary = false;
		if (args == null) {
			synchronized (mbd.constructorArgumentLock) {
				if (mbd.resolvedConstructorOrFactoryMethod != null) {
					resolved = true;
					autowireNecessary = mbd.constructorArgumentsResolved;
				}
			}
		}
		if (resolved) {
			if (autowireNecessary) {
				return autowireConstructor(beanName, mbd, null, null);
			}
			else {
				return instantiateBean(beanName, mbd);
			}
		}

		// 執行我們註冊的SmartInstantiationAwareBeanPostProcessor的determineCandidateConstructors獲取Constructor對象數組(如果有的話)
		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
        // 如果得到的數組不是空,或者beanDefination的裝配模式為構造注入,或者beanDefination包含構造參數,或者我們傳入的構造參數非空,則進入實例化bean或factoryBean
		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
			return autowireConstructor(beanName, mbd, ctors, args);
		}
		
		// 省略部分代碼······

		// 使用無參構造實例化bean或factoryBean
		return instantiateBean(beanName, mbd);
	}

實例化的方法包括有參構造實例化和無參構造實例化兩種,本文只討論有參構造實例化的情況。

ConstructorArgumentValues和ArgumentsHolder

在繼續分析之前,有必要了解下ConstructorArgumentValuesArgumentsHolder這兩個類。

首先,ConstructorArgumentValues用於定義構造方法的參數列表的值。spring 中,ConstructorArgumentValues一般被定義在 BeanDefinition對象中,它影響着 bean 的實例化,是 bean 實例化時選擇構造對象的依據。

public class ConstructorArgumentValues {
	// 索引+參數值
    // 例如,對應new User(int age, String name, String address)的構造方法,可以包含元素:0=new ValueHolder(18),2=new ValueHolder("北京")
	private final Map<Integer, ValueHolder> indexedArgumentValues = new LinkedHashMap<>();
	// 通用參數值
    // 例如,對應new User(int age, String name, String address)的構造方法,如果indexedArgumentValues中不包含name的值,則可以在genericArgumentValues中查找,我們只需要添加元素:new ValueHolder("zzs001", String.class)
	private final List<ValueHolder> genericArgumentValues = new ArrayList<>();
    
    // 內部類,代表一個參數的值
    public static class ValueHolder implements BeanMetadataElement {

		@Nullable
		private Object value;

		@Nullable
		private String type;

		@Nullable
		private String name;

		@Nullable
		private Object source;

		private boolean converted = false;

}

ArgumentsHolderConstructorResolver的內部類,和ConstructorArgumentValues一樣,它也是用來定義構造方法的參數列表的值,區別在於,ConstructorArgumentValues的值是“未解析的”,而ArgumentsHolder包含了“未解析”(preparedArguments)、“解析未完成”(rawArguments)和”解析完成”(arguments)三種值。

為什麼會這樣呢?因為ConstructorArgumentValues中的參數值的類型不一定和構造方法中的匹配,包括兩種情況:

  1. 類型不同,但可以通過TypeConverter轉換的類型。例如,在new User(int age, String name, Address address)的構造方法中,我可以在ConstructorArgumentValues添加2=new AddressVO(),這個時候只要 spring 能找到合適的轉換器就能轉換,這個轉換過程為:“解析未完成”(rawArguments) –》 “解析完成”(arguments)
  2. 類型不同,參數的值指向其他 bean ,當然也可以是其他 spring 可識別的引用。例如,new User(int age, String name, Address address)的構造方法中,我可以在ConstructorArgumentValues添加2=new RootBeanDefinition(Address.class),這個轉換過程為:“未解析”(preparedArguments) –》“解析未完成”(rawArguments)
private static class ArgumentsHolder {

    public final Object[] rawArguments;

    public final Object[] arguments;

    public final Object[] preparedArguments;

    public boolean resolveNecessary = false;

}

理解完這兩個類之後,我們繼續分析實例化的源碼。

有參構造實例化

進入到AbstractAutowireCapableBeanFactory.autowireConstructor(String, RootBeanDefinition, Constructor<?>[], Object[])方法。這裏創建了一個ConstructorResolver對象並直接調用它的 autowireConstructor 方法。

	protected BeanWrapper autowireConstructor(
			String beanName, RootBeanDefinition mbd, @Nullable Constructor<?>[] ctors, @Nullable Object[] explicitArgs) {

		return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs);
	}

進入ConstructorResolver.autowireConstructor(String, RootBeanDefinition, Constructor<?>[], Object[])。這個方法代碼比較多,為了更好地理解,可以分成兩種場景來看:

  1. 入參里顯式指定構造參數。這種場景的參數值默認都是解析過的,所以不需要解析,該場景要求對應的構造對象的參數數量必須和指定的一樣。
  2. BeanDefinition對象中指定ConstructorArgumentValues。這種場景的參數值需要經過兩步轉換,該場景要求對應的構造對象的參數數量不小於指定的數量。
	public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
			@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {

		BeanWrapperImpl bw = new BeanWrapperImpl();
		this.beanFactory.initBeanWrapper(bw);
		
        // 定義最終用於實例化對象的構造器
		Constructor<?> constructorToUse = null;
        // 定義存放(“未解析”、“解析未完成”、“解析完成”)構造參數的對象
		ArgumentsHolder argsHolderToUse = null;
        // 定義最終用於實例化對象的構造參數
		Object[] argsToUse = null;
		
        // 入參顯式聲明了構造參數(場景一),則不需要解析參數列表值,但需解析構造對象
		if (explicitArgs != null) {
			argsToUse = explicitArgs;
		}
		else {
			Object[] argsToResolve = null;
            // BeanDefinition對象中指定ConstructorArgumentValues(場景二),如果參數列表值或構造對象已經解析,則不需要再解析
			synchronized (mbd.constructorArgumentLock) {
				constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;
				if (constructorToUse != null && mbd.constructorArgumentsResolved) {
					// Found a cached constructor...
					argsToUse = mbd.resolvedConstructorArguments;
					if (argsToUse == null) {
						argsToResolve = mbd.preparedConstructorArguments;
					}
				}
			}
			if (argsToResolve != null) {
				argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve, true);
			}
		}
		
        // 進入解析參數列表值和構造對象
		if (constructorToUse == null || argsToUse == null) {
			// 如果入參里沒有顯式指定構造對象的數組,使用反射方式獲取
			Constructor<?>[] candidates = chosenCtors;
			if (candidates == null) {
				Class<?> beanClass = mbd.getBeanClass();
				try {
                    // BeanDefinition中可以定義是否包括非public的方法
					candidates = (mbd.isNonPublicAccessAllowed() ?
							beanClass.getDeclaredConstructors() : beanClass.getConstructors());
				}
				catch (Throwable ex) {
					// 省略代碼······
				}
			}
			
            // 如果數組中只有一個無參構造,且入參和BeanDefinition中都未指定參數列表值,則標記該BeanDefinition對象的構造參數已解析,並實例化bean
			if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
				// 省略代碼······
			}

			// 判斷是否需要解析構造
			boolean autowiring = (chosenCtors != null ||
					mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
            // 這裏存放“解析未完成”的參數列表值
			ConstructorArgumentValues resolvedValues = null;
			
            // 獲取要求構造參數的最小數量
			int minNrOfArgs;
            // 入參顯式聲明了構造參數(場景一),minNrOfArgs即為指定數組的長度
			if (explicitArgs != null) {
				minNrOfArgs = explicitArgs.length;
			}
			else {
                // BeanDefinition對象中指定ConstructorArgumentValues(場景二),則需要計算minNrOfArgs,並進行“未解析” --> “解析未完成”的轉換
				ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
				resolvedValues = new ConstructorArgumentValues();
				minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
			}
			// 根據參數數量從小到大排列
			AutowireUtils.sortConstructors(candidates);
			int minTypeDiffWeight = Integer.MAX_VALUE;
			Set<Constructor<?>> ambiguousConstructors = null;
			LinkedList<UnsatisfiedDependencyException> causes = null;
			
            // 遍歷候選的構造對象
			for (Constructor<?> candidate : candidates) {
				// 獲取當前構造對象的參數數量
				int parameterCount = candidate.getParameterCount();
				//  如果上一個循環已經找到匹配的構造對象,則跳出循環1
				if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) {
					break;
				}
                
                // 如果當前構造對象的參數數量小於minNrOfArgs,則遍歷下一個
                // 注意,入參里顯式指定構造參數(場景一)要求對應的構造對象的參數數量必須和指定的一樣。BeanDefinition對象中指定ConstructorArgumentValues(場景二)要求對應的構造對象的參數數量不小於指定的數量
				if (parameterCount < minNrOfArgs) {
					continue;
				}

				ArgumentsHolder argsHolder;
                // 獲取當前構造對象的參數類型數組
				Class<?>[] paramTypes = candidate.getParameterTypes();
                // BeanDefinition對象中指定ConstructorArgumentValues(場景二)的情況
				if (resolvedValues != null) {
                    // 進行“解析未完成”->“解析完成”的轉換
					try {
                        // 這裡是為了處理JDK6的ConstructorProperties註解,其他情況都會返回null。
						String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount);
						if (paramNames == null) {
							ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
							if (pnd != null) {
                                // 獲取當前構造對象的參數名數組
								paramNames = pnd.getParameterNames(candidate);
							}
						}
                        // 創建ArgumentsHolder對象
						argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
								getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
					}
					catch (UnsatisfiedDependencyException ex) {
						// 省略代碼······
						continue;
					}
				}
                // 入參里顯式指定構造參數(場景一)的情況
				else {
                    // 如果當前構造參數的數量小於指定參數的數量,則繼續循環
					if (parameterCount != explicitArgs.length) {
						continue;
					}
                    // 創建ArgumentsHolder對象,因為不需要解析參數,所以,這種情況raw、prepared、resolved都是一樣的
					argsHolder = new ArgumentsHolder(explicitArgs);
				}
				// 計算指定參數和當前構造的參數類型的差異值
				int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
						argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
				// 差異值小於閾值
				if (typeDiffWeight < minTypeDiffWeight) {
					// 得到匹配的構造對象和構造參數
					constructorToUse = candidate;
					argsHolderToUse = argsHolder;
					argsToUse = argsHolder.arguments;
					minTypeDiffWeight = typeDiffWeight;
					ambiguousConstructors = null;
				}
                // 差異值大於閾值,這種不考慮
				else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
					// 省略代碼······
				}
			}
			// 如果找不到合適的構造對象,則會拋錯
			if (constructorToUse == null) {
				// 省略代碼······
			}
			else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) {
				// 省略代碼······
			}
			// BeanDefinition對象中指定ConstructorArgumentValues(場景二),為了復用解析好的構造和參數列表,需要標記當前BeanDefinition的構造參數已解析
			if (explicitArgs == null && argsHolderToUse != null) {
				argsHolderToUse.storeCache(mbd, constructorToUse);
			}
		}

		Assert.state(argsToUse != null, "Unresolved constructor arguments");
        // 接下來就是使用構造對象和參數來實例化對象,就不往下看了。
		bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
		return bw;
	}

實例化部分比較難,主要還得先理解一些抽象概念,例如:兩個場景、參數的轉換等。

屬性裝配

進入AbstractAutowireCapableBeanFactory.populateBean(String, RootBeanDefinition, BeanWrapper)。這個方法包括以下過程:

  1. 執行我們註冊的InstantiationAwareBeanPostProcessorpostProcessAfterInstantiation方法,如果返回了 false,則不進行屬性裝配,直接返回;
  2. 獲取 beanDefinition 中的PropertyValues對象,根據 beanDefinition 設置的注入類型,填充PropertyValues對象;
  3. 執行我們註冊的InstantiationAwareBeanPostProcessorpostProcessProperties方法,可以對PropertyValues對象進行修改;
  4. 依賴檢查(如果設置了);
  5. 進行屬性裝配。
	protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
        // 如果實例對象為空,則拋出異常或直接返回
		if (bw == null) {
			if (mbd.hasPropertyValues()) {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
			}
			else {
				return;
			}
		}

		// 執行我們註冊的InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation方法,如果返回了false,則不進行屬性裝配,直接返回
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof InstantiationAwareBeanPostProcessor) {
					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
					if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
						return;
					}
				}
			}
		}
		
        // 獲取BeanDefinition對象中的PropertyValues,包含了name=value的PropertyValue對象的列表
		PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
		
        // 根據我們設置的注入方式,填充
		int resolvedAutowireMode = mbd.getResolvedAutowireMode();
		if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
			MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
			// 按名字裝配
			if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
				autowireByName(beanName, mbd, bw, newPvs);
			}
			// 按類型裝配
			if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
				autowireByType(beanName, mbd, bw, newPvs);
			}
			pvs = newPvs;
		}
		// beanFactory中是否註冊了InstantiationAwareBeanPostProcessors
		boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
        // BeanDefinition對象中是否設置了依賴檢查
		boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);

		PropertyDescriptor[] filteredPds = null;
		if (hasInstAwareBpps) {
			if (pvs == null) {
                // 如果為空,再次從BeanDefinition對象中獲取,TODO?
				pvs = mbd.getPropertyValues();
			}
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof InstantiationAwareBeanPostProcessor) {
					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                    // 執行我們註冊的InstantiationAwareBeanPostProcessor的postProcessProperties方法,可以對PropertyValues對象進行修改
					PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
					// 省略部分代碼······
					pvs = pvsToUse;
				}
			}
		}
        // 如果BeanDefinition對象中設置了依賴檢查,則需要檢查依賴設置
		if (needsDepCheck) {
			if (filteredPds == null) {
				filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
			}
			checkDependencies(beanName, mbd, filteredPds, pvs);
		}

		if (pvs != null) {
            // 執行屬性裝配
			applyPropertyValues(beanName, mbd, bw, pvs);
		}
	}

這個方法中主要涉及autowireByNameautowireByTypeapplyPropertyValues三個方法,前兩個暫時不展開,只講最後一個方法。

幾個重要的知識點

在分析applyPropertyValues方法之前,我們需要知道一下幾個知識點。這裏以User這個類來展開例子。

public class User {
    
    private String name;
    
    private int age;
    
    private Address address;
    
    private List<String> hobbies;
}
class Address {
    private String region;
    private String desc;
}

propertyName的幾種形式

當我們給 beanDefinition設置屬性值時,一般都會這樣採用這樣的賦值,這裏成為“普通形式”。

rootBeanDefinition.getPropertyValues().add("name", "zzs001");
rootBeanDefinition.getPropertyValues().add("age", 18);
rootBeanDefinition.getPropertyValues().add("address", new Address("", ""));
rootBeanDefinition.getPropertyValues().add("hobbies", new ArrayList());

針對類型為 object、list、array、map 等成員屬性,spring 還支持其他的賦值方式,如下,分別成為“嵌套對象形式”和“索引形式”:

// 嵌套對象形式
rootBeanDefinition.getPropertyValues().add("address.region", "");
rootBeanDefinition.getPropertyValues().add("address.desc", "");
// 索引形式
rootBeanDefinition.getPropertyValues().add("hobbies[0]", "");

正是由於 propertyName 引入了多種的形式,所以,原本簡單的賦值行為被搞得非常複雜。例如,嵌套對象形式還可以是這樣:foo.user.address.region,幾乎可以一直嵌套下去。

PropertyAccessor

propertyAccessor 對象一般綁定了一個實例對象,通過PropertyAccessor接口的方法可以對對象的屬性進行存取操作。屬性裝配中最終對成員屬性賦值就是調用它的setPropertyValue方法。AbstractNestablePropertyAccessor中維護了一個 map,key 為當前綁定對象的屬性名(不包含嵌套和索引),value 就是對於的PropertyAccessor對象。

public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyAccessor {
    private Map<String, AbstractNestablePropertyAccessor> nestedPropertyAccessors;
}

在上面的例子中,

rootBeanDefinition.getPropertyValues().add("name", "zzs001");
rootBeanDefinition.getPropertyValues().add("age", 18);

這種形式共用一個綁定了User類型實例的PropertyAccessor對象。

// 嵌套對象形式
rootBeanDefinition.getPropertyValues().add("address.region", "");
rootBeanDefinition.getPropertyValues().add("address.desc", "");

這種形式共用一個綁定了Address類型實例的PropertyAccessor對象,該對象和”address”這個名字關聯起來維護在 nestedPropertyAccessors 中。

// 索引形式
rootBeanDefinition.getPropertyValues().add("hobbies[0]", "");

這種形式也是一個綁定了User類型實例的PropertyAccessor對象,該對象和”hobbies”這個名字關聯起來維護在 nestedPropertyAccessors 中。

PropertyTokenHolder

PropertyTokenHolderAbstractNestablePropertyAccessor的內部類,它更多的是針對“索引形式”的 propertyName。例如,”hobbies[0]”對於的PropertyTokenHolder中,actualName = hobbies,canonicalName = [0],keys = {0}。

	protected static class PropertyTokenHolder {

		public PropertyTokenHolder(String name) {
			this.actualName = name;
			this.canonicalName = name;
		}

		public String actualName;

		public String canonicalName;

		@Nullable
		public String[] keys;
	}

接下來繼續分析屬性裝配的代碼。

applyPropertyValues

進入AbstractAutowireCapableBeanFactory.applyPropertyValues(String, BeanDefinition, BeanWrapper, PropertyValues)方法。和構造參數一樣,設置成員屬性的參數也需要經過“兩次轉換”,這裏就不詳細講解。這個方法主要包括以下過程:

  1. 獲取屬性對象列表,如果這個列表的屬性對象都已經完成“兩次轉換”,則直接裝配屬性;
  2. 遍歷屬性對象列表,分別進行兩次轉換,如果列表中沒有類似BeanDefinitionBeanDefinitionHolder等的對象,則設置PropertyValues對象已經轉換完成,下次調用這個方法不用再進行轉換;
  3. 屬性裝配。
	protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
        // 如果沒有需要注入的屬性,直接返回
		if (pvs.isEmpty()) {
			return;
		}

		// 省略部分代碼······

		MutablePropertyValues mpvs = null;
        
        // 獲取屬性對象列表
		List<PropertyValue> original;
		if (pvs instanceof MutablePropertyValues) {
			mpvs = (MutablePropertyValues) pvs;
            // 如果所有屬性對象已經完成“兩次轉換”,則直接裝配屬性
			if (mpvs.isConverted()) {
				try {
					bw.setPropertyValues(mpvs);
					return;
				}
				catch (BeansException ex) {
					throw new BeanCreationException(
							mbd.getResourceDescription(), beanName, "Error setting property values", ex);
				}
			}
			original = mpvs.getPropertyValueList();
		}
		else {
			original = Arrays.asList(pvs.getPropertyValues());
		}
		
        // 獲取我們註冊的類型轉換器
		TypeConverter converter = getCustomTypeConverter();
		if (converter == null) {
			converter = bw;
		}
        // 創建第一次轉換所用的解析器
		BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);
		
        // 定義一個列表,用於存放完成“兩次轉換”的屬性對象
		// 這注意,這裏並沒有進行所謂的複製,不要被命名迷惑了
		List<PropertyValue> deepCopy = new ArrayList<>(original.size());
		boolean resolveNecessary = false;
        // 遍歷屬性對象
		for (PropertyValue pv : original) {
            // 當前屬性對象已經完成“兩次轉換”,直接添加到列表
			if (pv.isConverted()) {
				deepCopy.add(pv);
			}
			else {
				String propertyName = pv.getName();
				Object originalValue = pv.getValue();
				// 省略部分代碼······
                // 第一次轉換
				Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
				Object convertedValue = resolvedValue;
                // 如果當前屬性為可寫屬性,且屬性名不是類似於foo.bar或addresses[0]的形式,則需要進行第二次轉換
				boolean convertible = bw.isWritableProperty(propertyName) &&
						!PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
				if (convertible) {
					convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
				}
				// 如果轉換后的屬性對象和初始對象一樣,一般指的是普通對象,而不是BeanDefinition、BeanDefinitionHolder等
				if (resolvedValue == originalValue) {
                    // 如果需要第二次轉換,則設置復用的目標對象
					if (convertible) {
						pv.setConvertedValue(convertedValue);
					}
                    // 將原屬性對象添加到列表
					deepCopy.add(pv);
				}
                // 這種情況不考慮
				else if (convertible && originalValue instanceof TypedStringValue &&
						!((TypedStringValue) originalValue).isDynamic() &&
						!(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) {
					pv.setConvertedValue(convertedValue);
					deepCopy.add(pv);
				}
                // 其他情況
				else {
                    // 標記每次都需要解析
					resolveNecessary = true;
                    // 將原屬性對象添加到列表
					deepCopy.add(new PropertyValue(pv, convertedValue));
				}
			}
		}
        // 如果不包含BeanDefinition、BeanDefinitionHolder等對象,則設置PropertyValues為已轉換,這樣下次調用這個方法,就不需要進行任何的轉換了
		if (mpvs != null && !resolveNecessary) {
			mpvs.setConverted();
		}

		// 屬性裝配
		try {
			bw.setPropertyValues(new MutablePropertyValues(deepCopy));
		}
		catch (BeansException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Error setting property values", ex);
		}
	}

進入AbstractPropertyAccessor.setPropertyValues(PropertyValues)方法。這裏遍歷屬性對象列表,逐個進賦值操作。

	public void setPropertyValues(PropertyValues pvs) throws BeansException {
        // 入參適配
        // 後面兩個參數分別代表:是否忽略NotWritablePropertyException異常、是否忽略NullValueInNestedPathException異常
		setPropertyValues(pvs, false, false);
	}
	public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
			throws BeansException {

        // 獲取屬性對象列表
		List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
				((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
		for (PropertyValue pv : propertyValues) {
            	// 省略try-catch的代碼和其他異常相關的代碼······
				setPropertyValue(pv);
            }
	}

setPropertyValue

進入AbstractNestablePropertyAccessor.setPropertyValue(PropertyValue)。這個方法包括以下過程:

  1. 獲取 propertyName 對應的PropertyAccessor對象,這裏將解析“嵌套對象形式”的 propertyName;
  2. 創建PropertyTokenHolder對象,這裏將解析“索引形式”的 propertyName;
  3. 使用PropertyAccessor對象進行賦值操作。
	public void setPropertyValue(PropertyValue pv) throws BeansException {
        // 適配入參
		setPropertyValue(pv.getName(), pv.getValue());
	}	
	public void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException {
		AbstractNestablePropertyAccessor nestedPa;
		try {
            // 獲取propertyName對應的PropertyAccessor對象,這裏將解析“嵌套對象形式”的propertyName
            // 如果緩存里有的話,將復用
			nestedPa = getPropertyAccessorForPropertyPath(propertyName);
		}
		catch (NotReadablePropertyException ex) {
			throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
					"Nested property in path '" + propertyName + "' does not exist", ex);
		}
        // 創建PropertyTokenHolder對象,這裏將解析“索引形式”的propertyName
		PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
        // 使用PropertyAccessor對象進行賦值操作
		nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value));
	}

進入AbstractNestablePropertyAccessor.setPropertyValue(PropertyTokenHolder, PropertyValue)方法。這裏根據 propertyName 是否為“索引形式”調用不同的方法。

	protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
		if (tokens.keys != null) {
			processKeyedProperty(tokens, pv);
		}
		else {
			processLocalProperty(tokens, pv);
		}
	}

這裏我們不看 propertyName 為“索引形式”的方法,只看processLocalProperty

	private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {
        // 獲取actualName對應的PropertyHandler對象,如果有緩存則復用
		PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
		if (ph == null || !ph.isWritable()) {
			// 省略部分代碼······
		}

		Object oldValue = null;
		try {
			Object originalValue = pv.getValue();
			Object valueToApply = originalValue;
			if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
                // 因為我們的屬性參數都是轉換過的,所以這裏不再看轉換的代碼
				if (pv.isConverted()) {
					valueToApply = pv.getConvertedValue();
				}
				else {
					// 省略部分代碼······
				}
				pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
			}
            // 接下來就是通過反射方式給屬性賦值,後續再展開
			ph.setValue(valueToApply);
		}
		catch (Exception ex) {
			// 省略部分代碼······
        }
	}

屬性裝配的代碼分析就點到為止吧。

最後補充

以上基本看完 spring-bean 的源碼。針對 getBean 的過程,本文未展開的內容包括:

  1. 獲取和創建多例 bean;
  2. 無參構造實例化;
  3. 屬性裝配中,屬性值列表的填充(autowireByName和autowireByType)、屬性名為索引形式的屬性裝配
  4. bean 的初始化。

感興趣的讀者可以自行分析。另外,以上內容如有錯誤,歡迎指正。

最後,感謝閱讀。

相關源碼請移步: spring-beans

本文為原創文章,轉載請附上原文出處鏈接:https://www.cnblogs.com/ZhangZiSheng001/p/13196228.html

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

.NET 5.0預覽版6發布:支持Windows ARM64設備

2020年6月25日,微軟dotnet團隊在博客宣布了第六個 .NET 5.0 的預覽版:https://devblogs.microsoft.com/dotnet/announcing-net-5-0-preview-6/,在改進性能的同時增加了一些新的功能。ASP.NET Core和 EF Core也將於今日發布了。注意:EF Core 5.0 不支持 .NET Standard 2.0 或 .NET Framework,但是EF Core 5.0是支持.NET Core 3.1。 將所有內容組合在一起的里程碑式版本離預覽版 8 中完成功能只有兩步之遙,最終的 GA 版本定於 11 月發布,2019年Build大會上宣布.NET 5時,微軟就明確說了,”未來將只有一個.NET,您將能夠使用它來定位Windows、Linux、macOS、iOS、Android、tvOS、watchOS和WebAssembly等等。 微軟在4 月份宣布預覽版 2時宣布,它已經處理了其 .NET 站點上 50% 的流量。

 

自那時以來,一系列穩定的預覽版本一直在逐步修復Issue,完善現有功能和添加新的功能,雖然通常是一個小範圍,不斷敏捷迭代,小步快跑。 今天的預覽版 6 依然如此,Microsoft 將其描述為包含”一小組新功能和性能改進”。

本次更新的主要功能是在 Windows ARM64 上支持 Windows Forms 應用程序。此前.NET 5 Preview 4,Windows ARM64 上只支持控制台和 ASP.NET Core 應用程序。 通過這項支持,開發者可以在 Surface Pro X 等 Windows ARM64 設備上構建和運行 Windows Forms 應用。微軟還透露,他們仍在努力為 Windows ARM64 設備提供 WPF支持。 同時.NET 5移除了對WinRT 的內置支持,通過外部工具鏈進行支持,這麼做的最直接的好處是簡化 .NET 運行時代碼庫(一下就刪除 6 萬行代碼),而且這麼多代碼和跨平台無關。這次版本更新的內容如下:

  • Windows 窗體改進:開發人員可以強制其應用程序是單實例的,這意味着一次啟動一個實例。
  • RyuJIT 代碼質量改進:其中大量涉及常規改進(結構處理等)、ARM64 硬件內部功能和 ARM64 生成的代碼改進,從而大大減少了 ARM64 代碼大小。
  • 單個文件應用改進:添加了在單個文件中包含本機二進制文件和任何其他內容(如圖片)的新選項,.NET 5目標是為 Windows、macOS 和 Linux 啟用將應用作為一個文件發布。這個裡程碑進一步接近了。
  • 本機託管應用程序改進:一位貢獻者提供了一種新的模型,用於在本機應用程序中的 .NET 託管模型。
  • 突破性的改變 — 刪除內置 WinRT 支持: “已經用 Windows 團隊在 .NET 5.0 中提供的C#/WinRT工具鏈取代了內置 WinRT 支持。WinRT 互操作中的此更改是一個突破性的變化,使用 WinRT 的 .NET Core 3.x 應用將需要重新編譯。我們將在即將到來的預覽版中提供有關此內容的更多信息。

開發工具支持上需要Visual Studio 16.7 預覽版才支持.NET 5, Visual Studio For Mac 也支持.NET 5, 當然Visual Studio Code 安裝最新版的C#擴展插件也支持.NET 5. Visual Studio .NET遠程調試器對Windows ARM64的支持將在Visual Studio 16.7版本中提供。Visual Studio Code .NET遠程調試器支持將在以後推出。

Richard 在博客結束時說:”我們現在已經過了這個發布周期的一半。”事實上,我們開始’關閉發布’。如果您密切關注我們的代碼倉庫,您會看到我們正開始更仔細地管理問題的里程碑。 在發布了多個.NET 5 版本之後,我可以告訴你,這是一個偉大的時間。 是時候在一組我們構建的功能上取得勝利,並把它們打磨到您樂於使用它們。 這就是我們現在在家裡遠程工作所做的。 儘管 11 月首次亮相仍有望實現,但 .NET 5 不會擁有 Microsoft 最初計劃的所有特性和功能。這是因為COVID-19大流行引起的併發症。因此,完整的包將會在 2021 年 11 月與 .NET 6 一起發布。

您可以使用以下鏈接下載新的 .NET 5.0 的第六個預覽版,適用于于Windows、macOS和Linux: 

  • Windows and macOS installers
  • Binaries
  • Docker images
  • Snap installer

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

數據結構與算法(七):迷宮回溯和八皇后問題

一、迷宮回溯問題

1.問題

一個7*8的數組模擬迷宮,障礙用1表示,通路使用0表示,給定起點(1,1)和終點(6,5),要求給出起點到終點的通路

2.解題思路

  1. 首先,我們需要給程序一個尋向的基本策略,我們先假定尋向順序為“下-右-上-左”,也就是說從起點出發,先往下走,往下走不通就往右…..以此類推
  2. 然後我們需要給走過的路一個標記,暫記為2
  3. 而當從一個方向走到一個只能原路返回的死衚衕時,就給這段路標記為3
  4. 當抵達終點坐標(6,5)時程序結束

3.代碼實現

3.1生成地圖

/**
 * 創建一個二維數組,用於模擬8*7迷宮
 * 使用1表示不可通過的實心方塊,0表示可通過磚塊
 * (6,5)為默認終點,(1,1)為默認起點
 * @return
 */
public static int[][] getMap(){
    int[][] map = new int[8][7];
    //上下全置為1
    for(int i = 0;i <7 ;i++){
        map[0][i] = 1;
        map[7][i] = 1;
    }
    //左右全置為1
    for(int i = 0;i < 8;i++){
        map[i][0] = 1;
        map[i][6] = 1;
    }
    //設置擋板
    map[3][1] = 1;
    map[3][2] = 1;

    //輸出地圖
    System.out.println("地圖的初始情況:");
    showMap(map);

    return map;
}

/**
 * 展示地圖
 * @param map
 */
public static void showMap(int[][] map) {
    for(int i = 0;i < 8;i++){
        for(int j = 0;j < 7;j++){
            System.out.print(map[i][j] + " ");
        }
        System.out.println();
    }
}

3.2 尋路邏輯的實現

對於這個尋路程序,我們可以看見,往四個方向走的過程實際上除了方向外動作上是一樣的;而具體分析同一個方向,每走過一個坐標的動作也是一樣的,我們對流程進行分析:

  1. 出發,先往下走,判斷下一格有沒有障礙(int[x][y]==1
  2. 如果沒有障礙,就繼續往下走,然後重複步驟1到碰到障礙為止
  3. 如果有障礙,就按“下-右-上-左”的順序,換個方向,然後重複步驟1到碰到障礙為止
  4. 如果找到了(6,5)就結束

表現為代碼實際上就是一個遞歸的過程:

  • 找路是方法體
  • 找到了(6,5)或者死衚衕是終止條件
/**
 * 給定起始點,根據地圖找路
 * 使用2表示可以走通的路,使用3表示走過但是不通的路
 * @param map 地圖二維數組
 * @param x 起始點橫坐標
 * @param y 起始點縱坐標
 * @return
 */
public static boolean findWay(int[][] map, int x, int y) {
    //如果走到了終點就終止
    if (map[6][5] == 2){
        return true;
    }else {
        //只有為0的路才能通過
        if (map[y][x] == 0) {
            //如果該點可以走通就打上標記
            map[y][x] = 2;
            if (findWay(map, x, y + 1)) {
                //向下遞歸
                return true;
            } else if (findWay(map, x + 1, y)) {
                //向右遞歸
                return true;
            } else if (findWay(map, x, y - 1)) {
                //向上遞歸
                return true;
            } else if (findWay(map, x - 1, y)) {
                //向左遞歸
                return true;
            } else {
                //都走不通說明是死衚衕
                map[y][x] = 3;
                return false;
            }
        }else {
            //不為0說明要麼是死路要麼是障礙
            return false;
        }
    }
}

3.3 運行結果

findWay()方法中的終止條件從map[6][5] == 2換成其他坐標即可更換終點位置,

棋盤大小和障礙物位置不影響findWay()方法尋路。

二、八皇后問題

1.問題

皇后問題,一個古老而著名的問題,是回溯算法的典型案例。該問題由國際西洋棋棋手馬克斯·貝瑟爾於 1848 年提出:

在 8×8 格的國際象棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,求有多少種擺法?

2.解題思路

  1. 首先,我們先使用一個長度為8數組來表示八皇后的擺放位置,數組下標+1即表示棋盤的第幾行數組下標對應的存放的数字+1即為棋盤的第幾列。舉個例子:

    arr = {0,2,3,8,4,6,2,7}

    其中,元素0下標為0,即表示第一行第一列;元素2下標為1,即表示第二行第三列……以此類推。

  2. 任意假設任意坐標分標為(x1,y1),(x2,y2),也就是用數組表示為arr[x1]=y1,arr[x2]=y2的兩個皇后不允許在同一列,我們可以理解為:

    arr[x1] != arr[x2];

    而任意坐標的皇后不允許在同一斜線,即(x2-x1)=(y2-y1),也就是斜率不應當相同,我們可以理解為:

    Math.abs(x2-x1) != Math.abs(arr[x2]-arr[x1])

    (注:Math.abs()為求絕對值方法)

3.代碼實現

3.1 檢查擺放位置的代碼實現

在前面明確了如何用數組表示位置,以及如何檢查皇后是否允許擺放后,我們有如下代碼:

//表示皇后位置的數組
int[] arr = new int[8];

/**
 * 檢查第n個皇后是否與前面擺放的皇后衝突
 * @param n
 * @return
 */
public boolean check(int n) {
    //檢查第n層之前的皇后位置
    for (int i = 0; i < n; i++) {
        // arr[i] == arr[n] 檢查是否同一列
        // Math.abs(n - i) == Math.abs(arr[n] - arr[i]) 檢查是否同一斜線
        if (arr[i] == arr[n] ||
            Math.abs(n - i) == Math.abs(arr[n] - arr[i])) {
            return false;
        }
    }
    return true;
}

3.2 完整代碼

接着我們需要考慮如何使用遞歸方法來做到以下效果:

使用一個方法遍歷第n行的每一列,檢查每一列是否可以放置皇后:

  1. 如果可以放置皇后,將位置出入arr[n]中,然後遞歸調用自己,傳入n+1開始遍歷下一行…..以此類推
  2. 如果不可以放置皇后,就跳過該列檢查下一列,如果可以就重複步驟1
  3. 若n行中全部位置都不合適,則結束本層返回上一層n-1層,重複步驟1
  4. 如果最後n=8,即八個皇后全部放置完畢,記一次完成擺放,然後結束遞歸返回第一層,繼續檢查第一層的下一列

最終代碼實現結果如下:

/**
 * @Author:黃成興
 * @Date:2020-06-26 20:53
 * @Description:八皇后問題
 */
public class EightQueens {

    public static void main(String[] args) {
        EightQueens eightQueens = new EightQueens();
        eightQueens.set(0);
        System.out.println("共有擺法:" + eightQueens.count);
    }

    //記錄八皇後有幾種擺法
    int count = 0;

    //表示皇后位置的數組
    int[] arr = new int[8];

    /**
     * 擺放皇后
     * @param n 第幾個皇后
     */
    private void set(int n) {
        //如果放置好了第8個皇后
        if (n == 8){
            show();
            //記錄一種擺放方式
            count++;
            //回到第一層繼續遞歸
            return;
        }

        //遍歷第n行的每一列
        for (int i = 0; i < 8; i++) {
            //將該皇後放置在第n行第i列
            arr[n] = i;
            //檢查放置位置是否合適
            if (check(n)){
                //如果位置合適,就遞歸找下一個(n+1)皇后的擺放位置
                set(n + 1);
            }
            //如果位置不合適,就跳過這一列檢查下一列
        }
    }

    /**
     * 檢查第n個皇后是否與前面擺放的皇后衝突
     * @param n
     * @return
     */
    public boolean check(int n) {
        //檢查第n層之前的皇后位置
        for (int i = 0; i < n; i++) {
            // arr[i] == arr[n] 檢查是否同一列
            // Math.abs(n - i) == Math.abs(arr[n] - arr[i]) 檢查是否同一斜線
            if (arr[i] == arr[n] ||
                Math.abs(n - i) == Math.abs(arr[n] - arr[i])) {
                return false;
            }
        }
        return true;
    }
    

    /**
     * 展示某一擺法中八皇后的擺放位置
     */
    public void show() {
        for (int i : arr) {
            System.out.print(i + " ");
        }
        System.out.println();
    }
}

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

狀態機模式 與 ajax 的結合運用

太神奇了,昨晚做了個夢,夢中我悟出一個道理:凡是涉及到異步操作而且需要返回值的函數,一定要封裝成 Promise 的形式,假如返回值取決於多個異步操作的結果,那麼需要對每個異步操作進行狀態的設計,而且需要封裝一個 next 函數。,到了晚上才覺到很有意思,所以結合 ajax 設置最短返回時間和最大返回時間進行實踐:

  const PENDING = 'PENDING'
  const RESOLVED = 'RESOLVED'
  const REJECTED = 'REJECTED'
  const FULLFILLED = 'FULLFILLED'

  /**
   * @desc 異步操作模擬
   * @param time 響應時間
   * @param isError 是否拋錯
   */
  const mock = (time, isError) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (!isError) {
          resolve({ user: 'ManbaX' })
        } else {
          reject('request error')
        }
      }, time)
    })
  }

  /**
   * @desc 生產不同類型請求的工廠函數
   * @param time 響應時間
   * @param isError 是否拋錯
   */
  var RequestFactory = function (time, isError) {
    var request = function () {
      return new Promise((resolve, reject) => {
        var min = PENDING
        var max = PENDING
        var state = PENDING
        var res = null

        var next = function (name) {
          const cb = function () {
            if (state === RESOLVED) {
              resolve(res)
            } else {
              reject(res)
            }
          }
          if (name === 'res' && min === FULLFILLED) {
            cb()
          }

          if (name === 'min' && (state === RESOLVED || state === REJECTED)) {
            cb()
          }

          if (name === 'max' && state === PENDING) {
            reject('timeout')
          }
        }

        setTimeout(() => {
          min = FULLFILLED
          next('min')
        }, 500)

        setTimeout(() => {
          max = FULLFILLED
          next('max')
        }, 1000)

        mock(time, isError).then(data => {
          res = data
          state = RESOLVED
          next('res')
        }).catch(error => {
          res = error
          state = REJECTED
          next('res')
        })
      })
    }

    return request
  }

  // 不超時, 不返回錯誤
  console.time('r1')
  RequestFactory(200)().then(res => {
    console.log('data: ', res)
  }).finally(() => {
    console.timeEnd('r1')
  })
  
  // 不超時, 返回錯誤
  console.time('r2')
  RequestFactory(200, true)().catch(err => {
    console.log('error', err)
  }).finally(() => {
    console.timeEnd('r2')
  })

  // 超時
  console.time('r3')
  RequestFactory(2000)().catch(res => {
    console.log('error: ', res)
  }).finally(() => {
    console.timeEnd('r3')
  })

上面的運行結果符合預期,本來夢中還有另外一個有意思的東西,但是太模糊了就搞忘記了,下次一定早點記錄。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

互聯網是如何連接的:計網概述

當你在瀏覽器輸入網址之後會發生什麼

最直觀的感受當然是跳轉到網址所指向的頁面啦,但在網絡比較卡的時候,你可能注意到過,瀏覽器的左下角通常會有一些等待什麼什麼請求之類的小字。這時候,一個問題讓你搜索到了這篇博文,我輸入網址之後,瀏覽器到底幹了什麼?更要命的是,我想知道互聯網到底是如何把每個人連接起來的?

我們先來認識一下你的瀏覽器,雖然大多數時間你都在使用它愉快的網上衝浪。可能在你的印象里,這個玩意兒就是只能開開網頁,看看視頻,功能強大,但略顯單一。現在,隨便找個PDF文件然後打開,你的默認方式應該是你心愛的瀏覽器。如果不是,可以右鍵選擇打開方式,再用瀏覽器打開。

應該都能打開吧?這麼一說,好像我們從來沒注意過這傢伙居然還有別的用處。為什麼潛意思里覺得它就是看看網頁呢?這裏要更新一下名詞,實際上我們輸入的網址,更準確的叫法應該是URL。

URL: Uniform Resource Locator,統一資源定位符。詞聽起來有點繞口,你可以看看現在打開PDF標籤頁的地址欄,看看它的開頭是不是跟網址不一樣

URL有各種各樣的寫法“http:”、“https:”、“ftp:”、“file:”、“mailto:”,開頭部分表示了瀏覽器應當採取的訪問方式。那麼輸入網址之後,首先發生了什麼事就呼之欲出了。

瀏覽器先得解析URL

比如我們要訪問博客園的首頁,那麼應該在地址欄輸入https://www.cnblogs.com。瀏覽器看到URL的開頭是https,就會明白這是一個web請求,後面的www.cnblogs.com指的是Web服務器域名。

域名可以看成是IP地址的別名,你可以在搜索引擎里輸入IP,你會看到一連串的数字。沒有人會希望上個網還要先拿小本本把經常用到的IP地址記下來,再說,輸入這一連串的数字也很麻煩,一不小心就輸錯了。所以我們使用域名來代替IP地址,幫助記憶。

IP地址:你可以類比於現實中的住房地址,因特網上的計算機也擁有一個惟一標識符來區別它的網上鄰居(其他的電腦)。

APP支持IPv6網絡是怎麼回事?你可能在APP啟動界面的底部看到過這行小字,實際上IPv6之前是IPv4(互聯網通信協議第四版),就是一個給大家分配地址的協議,在這個版本里IP用32位的整數表示。32位?看起來很長的樣子,然而2011年的時候,IANA就宣布IPv4地址43億IP地址已經分配完畢。

後續的IPv6可謂是超級加強版,32位擴展到了128位意味着能給地球上每一粒沙子編號,他能分配大約3.4×10^38個IP地址!

向DNS查詢Web服務器的IP地址

域名對應着IP地址,那麼如何才能知道這個域名對應的IP地址是什麼呢?自然是得問拿小本本記下這些東西的人啦。

DNS : 一個可以將域名和IP地址相互映射的一個分佈式數據庫,能夠使人更方便的訪問互聯網,而不用去記住能夠被機器直接讀取的IP數串。

只用一台DNS服務器去存儲數不勝數的IP地址是不可能的,所以在分散保存的情況下,一台DNS是如何處理它查詢不到的域名信息呢?

實際上,如同域名一樣,DNS也是分級的。域名的最左邊部分表示機器名字,它隨後的部分則是該計算機所屬的組織,可能還會有子組織。最右邊則代表頂級域名,指定該計算機所屬組織的類型。大部分的頂級域都被用於美國的公司和組織,其他國家的計算機一般是用國家的特有頂級域,比如cn(中國)。

頂級域 主要應用
edu 教育機構
com 商業公司
org 非盈利組織
net 網絡供應商
cn 中國國家頂級域名

負責管理下級域的DNS服務器找不到該域名對應的IP地址就會發送一個請求到它們的上級DNS服務器中去尋找,然後上級DNS還找不到就到更上一級的DNS服務器中,以此類推。最上一級叫做根域,根域服務器的地址在配置DNS服務器的時候就已經寫入其配置文件裏面了。

分配給根域DNS服務器的IP地址在全世界僅有13個,很遺憾中國沒有。日本佔據一個,其他的全是歐美。

雖然IP地址只有13個,但實際上服務器的數量遠遠多於13個,因為根域DNS是使用多台服務器對應一個IP地址的。

不知道你是否有過瀏覽器突然不能訪問網頁的經歷,更離譜的是QQ居然能用,就他瀏覽器不行。這是為什麼呢?多半是DNS服務器地址的問題,在 控制面板\網絡和 Internet\網絡連接 右鍵屬性選擇IPv4 改成下圖一般就好了

為什麼QQ沒問題呢?因為QQ是直接通過IP連接的。

你訪問的其實是文件

現在不如點開一下我首頁的第一篇博客吧,或者直接訪問https://www.cnblogs.com/AD-milk/p/13171059.html然後按下F12打開開發者模式,讓我們看看都有些什麼?

我們請求了一個html文件,1317…好像就是輸入的網址里的那個。這麼說的話,這一個一個斜桿好像確實是像文件路徑的結構!

但如果你訪問博客園的首頁,你會發現路徑名那裡只有一個/,這表示根目錄,好像並沒有指定文件呀。這種省略文件名的情況下,一般是默認訪問根目錄下的index.html或者default.html這樣的文件。

html:超文本標記語言,你可以簡單的認為是可以在文本中嵌入音頻,圖片和鏈接的語言

按理說我們只是請求了13171059.html這個文件,但從圖中來看我們好像請求了不止一個文件,這是為什麼?回想一下,你會有過因為網卡而導致加載的頁面只有文字的情況?這一般是因為網絡問題,css文件以及圖片沒有被傳輸過來。瀏覽器在加載文件之前並不知道這裏還需要什麼圖片或者文件,只有加載之後才會繼續發送請求。

css文件是一種用來指定html樣式的文件。缺少了它頁面就沒有好看的風格了。

1 條請求消息中只能寫 1 個 URI。如果需要獲取多個文件,必須對每個文件單獨發送 1 條請求。

URI:Uniform Resource Identifier,統一資源標識符,URL是特殊的URI

眨眼間的旅途

你說的我都知道了,可你還是沒有回答我,互聯網到底是如何互聯的?

首先來考慮一個簡單的場景:兩台電腦之間如何互相通訊?當然是接跟線把兩個都連上啦,那再來五台怎麼辦?往電腦上再打五個接口?這顯然不現實,我們可以讓集線器幫我們的忙。它具有多個端口,可以實現其內部所有網絡設備的連通。不過集線器是個榆木腦袋,它唯一知道的事情就是:當某一個端口發出數據時,它就把這個數據複製到與其連接的其他端口,這樣所有連接到集線器的設備都可以獲得這個數據包。這跟個大喇叭一樣,太煩人了,說點悄悄話都不行。

之所以電纜不如光纖通信速率高,是因為電信號在提升通信速率的同時,其衰減率也會提高(信號在傳播過程中減弱),導致信號無法傳到目的地。相對地,光信號本來的衰減率就很低,提高通信速率也並不會提高衰減率。此外,光纖還不受電磁噪聲的影響,因此光纖能夠進行高速通信。

不是光信號本來就比電信號快啦!!!

為了解決廣播所帶來的安全問題,以及減少不必要的流量,我們可以換個聰明點的傢伙:交換機。交換機比集線器智能的地方在於,它記下了與其相連的主機的物理地址MAC,他把這些存儲在地址表裡面。那麼現在,當數據報從一個端口發出時,它能根據數據報提供的目標MAC,直接將數據發往預期的目的端口。終於可以和喜歡的女同學說悄悄話了,呼~~。

但集線器和交換機只能在局域網中交換信息,無法在外部網絡交換。因為要在互聯網上通訊需要識別IP地址,顯然集線器和交換機沒有這個功能。所以我們需要用到路由器:它是一個能根據IP地址,將數據報從一個路由轉發到另外一個路由的設備。這意味着它具有過濾功能,但一個數據報達到該路由時,它會檢查這是否是發給自己的,如果不是的話就將它轉發給其他路由。所以它相當於一個網絡的網關。

現在的路由器大多集成了三者的功能,成了“瑞士軍刀”。讓人想不到的是,儘管交換機比路由器簡單,但實際上路由器早於交換機問世。

現在從我們電腦發出的請求數據報就沿着電路,被多個路由器轉發,最後到達目的地。

至此,這眨眼間的漫長旅途迎來了終點。但其實網絡通訊遠不止這些,這裏省去了很多東西,其中最重要的就是各式各樣的協議了。缺少協議會使得網絡世界通訊困難,就好比現實生活中你覺得點頭表示OK,搖頭表示NO,但另外一個人卻持相反的認識,這時候你們倆的交流就會遇上大麻煩!

協議:定義了在兩個或多個通信實體之間交換的報文格式和順序,以及報文發送和接收一條報文或其他事件採取的動作

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

Mariadb之顯式使用表鎖和行級鎖

  首先我們來看看mariadb的鎖定概念,所謂鎖就是當一個進程或事務在操作某一資源時,為了防止其他用戶或者進程或事務對其進行資源操作,導致資源搶佔而發生衝突,通常在A進程操作該資源時,會對該資源進行加鎖,實現多進程或多用戶操作同一資源時,不會發生衝突;通常情況鎖的類型分讀鎖和寫鎖,所謂讀鎖就是共享鎖,它可以實現多個讀操作共享;而寫鎖就是排它鎖,獨佔鎖,一旦加了寫鎖,其他用戶的讀寫操作將被阻塞,直到該寫鎖被釋放或者因超時而被釋放,在其他用戶進行的讀寫操作,此時就會被執行;對於鎖定範圍來講,它又可以分為,表鎖和行鎖,從字面意思就可以理解到,表鎖就是針對整張表所施加的鎖,而這種鎖定力度相當粗糙,併發相對就比較低,但是維持鎖狀態鎖消耗的成本資源就較小;對於行鎖來說,它針對的範圍就是行級別所施加的鎖,這種鎖的粒度就相對要精細,同時併發相對較高,但是維護鎖狀態消耗的成本資源就相對要大;對於mysql來講又鎖分為存儲引擎級別的鎖和mysql server級別的鎖,存儲引擎級別的鎖指的是對於何時施加鎖或者釋放鎖由存儲引擎自行決定;mysql server級別鎖指的是用戶使用命令可自行決定施加鎖或釋放鎖;簡單講就是允許用戶顯式請求加鎖或釋放鎖;顯式鎖就是用戶手動用命令施加的鎖,隱式鎖指的是由存儲引擎根據需要自行施加的鎖;對於innodb存儲引擎來講,它支持事務,行級鎖;而早期的MyISAM存儲引擎它不支持事務,對鎖的粒度是表級鎖,不支持行級鎖;

  显示鎖的使用

  1)LOCK TABLES

    指令使用語法:

    LOCK TABLES  tbl_name  read|write, tbl_name read|write, …

  示例:

  提示:以上語句表示對test_tb這張表施加讀鎖操作,這意味着其他用戶或進程都不能對該表進行寫的操作,只能讀,因為讀鎖是共享鎖;

  測試:對test_tb表進行寫操作,看看是否能夠寫進去?

  提示:從上面的提示,它告訴我們test_tb這張表施加了讀鎖,不允許更新;這說明施加讀鎖,對於寫的操作就不能進行;

  測試:對test_tb表進行讀操作,看看是否能夠進行呢?

  提示:可以看到加了讀鎖的表,對於讀操作上可以繼續進行的;

  測試:對test_tb表施加寫鎖

  提示:釋放鎖用unlock tables即可釋放剛才的讀鎖;

  測試:對test_tb進行寫操作,看看是否能夠進行?

  提示:在當前終端(加寫鎖所在終端)上是可以進行讀寫操作的;

  測試:在其他終端看看是否能夠對test_tb表進行讀寫操作呢?

  提示:從上面的截圖可以看到當我們重新啟動一個終端對test_tb進行寫操作,它一直處於阻塞狀態;

  提示:對於對操作也是同樣的效果;一直阻塞着;

  總結:對於施加讀鎖的表,是可以進行讀操作的,但是不能進行寫操作,包括當前終端也不能寫操作;對於施加寫鎖的表,在當前施加鎖的終端上是可以對其進行讀寫操作的,但是在別的終端讀寫操作都將阻塞;

  除了以上指令來對錶進行加鎖外,還可以使用 flush tables指令來加讀鎖,具體語法請看下面;

  FLUSH TABLES tbl_name,… [WITH READ LOCK];

  測試:加讀鎖

  提示:以上flush tables 只能加讀鎖,不能加寫鎖;

  行級鎖:SELECT cluase [FOR UPDATE | LOCK IN SHARE MODE]

  行級排它鎖

  提示:以上紅框中的內容就是給第一行加了一個排它鎖,這意味着該事務沒有提交,其他事務就不能再獲取該行的其他鎖,包括共享鎖和排它鎖,但是獲取排它鎖的事務是可以對數據就行讀取和修改。

  提示:可以看到我們重新啟動一個事務,然後對第一行進行更新操作,語句就阻塞在哪裡了;說明行級排它鎖對其他事務來講是不允許對加鎖的行進行寫操作;默認情況updeate更新會默認加上排它鎖,因為對於第一行來講,已經有一個排它鎖了,所以其他事務就不能對其在加其他鎖;而對於select語句來講,它執行時默認會加任何鎖的,所以我們執行select語句是可以正常的查看第一行數據;如果我們在select後面手動加鎖,它也會阻塞的;如下

  提示:從上面的截圖信息可以看到,我們手動加上排它鎖,查詢語句也不會順利執行;從上面信息還可以了解到,我們對第二行也沒法進行操作,這又是為什麼呢?

  提示:我們查看test_tb這張表上的索引信息,發現沒有索引,然後我們在上面創建了一個索引;創建索引時,需要把前面的事務提交了,才可創建成功,否則一直鎖在哪裡的;接下來我們在創建一個事務,把第一行加上排它鎖,然後在對第二行操作看看是否還會一直阻塞呢?

  提示:可以看到當我們創建就了索引后,再對第一行加鎖,然後更新第二行就可以正常更新了 ,對第一行還是處於阻塞狀態;這說明innodb存儲引擎的行級鎖的實現其實是依靠其對應的索引,所以如果操作的行並沒有用到索引,那麼用的還是表級鎖。施加行級排它鎖后,其他事務將不能對其在施加任何鎖;那麼對於獲取到排它鎖的是否能夠正常操作呢?

  提示:對於獲取到排它鎖的事務,是可以正常更新修改的;也可以給對應行施加其他鎖;

  行級共享鎖

  提示:以上紅框中的內容表示給第一行施加共享鎖,這意味着在其他事務鎖可以共享這把鎖看到數據,但是不能更新修改數據;

  測試:在當前事務中更新數據,看看是否可更新?

  提示:在當前事務中是可以正常修改數據的;也能正常查看數據;

  在其他事務中修改數據,看看是否可修改?

  提示:可以看到在其他事務中,就不能對有共享鎖的行進行修改操作,但是可以正常讀;

  總結:innodb存儲引擎的行級鎖依賴索引,如果沒有索引,就相當於表級鎖;對於排它鎖來講,獲取到排它鎖的事務是可以正常修改更新以及加共享鎖,對於沒有獲取到排它鎖的事務,是不能夠對有鎖的行進行修改更新以及加鎖的操作;對於共享鎖來講,對於當前事務(加鎖操作的事務)是可以正常修改更新有鎖的行,對於其他事務,是不可修改和更新有鎖的行;

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

LG Chem 開發新技術 電動車充電後可行駛 500 公里

電動車續航力有望加倍,南韓電池巨擘 LG Chem 宣布開發出新技術,電動車充電一次可行駛 400 至 500 公里,預計不久後就能量產。   韓國先驅報(Korea Herald)1 日報導,目前一般電動車充電後僅能行駛不到 200 公里。LG Chem 副會長兼執行長朴鎮洙表示,已研發出新技術,電動車行駛里程能增至 400至 500 公里。他說,產品不久就能投產,但拒絕透露更多細節。   LG Chem 砸錢投入開發,宣稱 2018 年研發費用將由當前的 6,000 億韓圜,提高到 9,000 億韓圜;未來 3 年研發人員也將增至 4,100 人。  

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

新北市:電動車牌照稅再延長 3 年

新北市政府稅捐稽徵處表示,為提高民眾購置電動汽車的意願,加速建立新北市成為綠色交通城市,凡民眾購買完全以電能為動力的電動汽車並於該市完成車籍登記,免徵使用牌照稅優惠延至 107 年 1 月 5 日止。   該處說,「完全以電能為動力的車輛」享有免徵使用牌照的優惠自 101 年 1 月 5 日施行起至今已滿 3 年,原優惠至今年 1 月 5 日截止。為繼續扶植國內電動車產業發展,並鼓勵民眾使用低汙染車輛,總統於 104 年 2 月 4 日公布修正使用牌照稅法第 5 條條文,繼續授權地方政府得對完全以電能為動力的汽車使用牌照稅免稅期間再延長 3 年。   稅捐處表示,配合國際新能源車輛的發展動向,及響應行政院繼續推動使用智慧電動車政策,新北市配合使用牌照稅法第 5 條修正,延長免徵牌照稅優惠期間至 107 年 1 月 5 日止。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

兩會進行時:關於新能源汽車的提案,都有什麼?

  3月3日,來自汽車行業的人大代表和政協委員再次聚首北京,為當今的汽車產業建言獻策,而新能源汽車仍將是2015年全國“兩會”汽車界熱議的焦點,政協委員和人大代表等也紛紛為新能源汽車的發展建言獻策。  
柳崇禧:加大基礎設施補貼力度 推動新能源車發展   全國政協委員、中國汽車工業工程公司副總經理柳崇禧帶來了《關於加大充換電基礎設施建設,推動新能源汽車市場應用的提案》。   針對電動汽車的發展受動力電池技術制約,充電設施網路不完善、充電不方便等問題,柳崇禧委員建議,加大充換電基礎設施建設,包括支援國家電網、南方電網等傳統電力供應央企加大充換電基礎設施建設,鼓勵中石化、中石油等傳統汽車能源供應央企參與充換電基礎設施建設。同時,還應鼓勵民營資本進入基礎設施建設,制定法規保障其權益,加大充換電基礎設施建設財政補貼力度,減免稅收。  
李書福:電動汽車不一定環保 呼籲放開汽車限購   全國政協委員、吉利集團董事長李書福共帶來”關於推動計程車體制改革”和”加強資訊化技術下經營交易稅收管理”兩個提案,就推進和深化計程車體制改革提出建議:關鍵要打破計程車牌照的壟斷式管理;建議根據不同城市規模,設立相應計程車體制改革試點城市,對各項改革措施,進行分類指導、科學評估。   另外李書福在接受採訪時呼籲放開汽車限購。他認為,目前某些地方實行的限購政策只是權宜之計。李書福表示,吉利今年會發力新能源汽車。但他認為電動汽車也不一定有利於環境,因為發電要耗費大量的非清潔能源。  
景柱:五大因素致使新能源汽車推廣艱難   全國人大代表、海馬集團董事長景柱認為,電動汽車商業化推廣存在五大癥結:一是以電池為靈魂的四大技術障礙,即功率密度、轉化效率、充電時間、系統安全。二是難以克服的兩大社會障礙:使用習慣、使用環境。三是非規模生產造成的高成本、高故障。四是以高成本為前提的兩大市場因素:特定環境和特定用戶。五是政策導向的四個誤區。   景柱認為,目前我國發展新能源汽車的最佳路徑,是汽車輕量化、節能減排和新能源技術齊頭並進。首先經過深混和插混技術,最終過渡到電動汽車或燃料電池汽車。  
鐘發平:建議國家大力發展混合動力汽車減少汽車污染   全國人大代表、科力遠董事長鐘發平建議國家通過大力發展混合動力汽車來減少汽車尾氣污染,解決當下的霧霾問題。鐘發平認為,相較動力鋰電池,鎳氫動力電池雖然能量密度不如,卻足夠穩定和安全。  
劉義發:小型電動車應成為國民車 實行分類管理   全國人大代表、山東時風集團董事長劉義發建議,綜合小型電動車的種種優勢,順應市場趨勢,將小型電動車作為“國民車”,實行分類管理,引導和規範行業科學發展。  
張天任:建議對微型電動車適當放開政策   全國人大代表、天能集團董事長張天任:建議國家對微型電動車適當放開政策,實行“產品准入從嚴,企業准入從寬”的管理原則,鼓勵和支援多種電池路線、不同電池搭配的新能源電動汽車相容發展,並針對微型電動車建立起科學的技術標準體系。他建議國家將微型電動車納入我國道路交通規劃之中,列入法定交通工具,允許符合標準的微型電動車上牌上路。  
王麒:推廣新能源汽車是治霾重要途徑之一   全國人大代表、啟陽(成都)投資管理有限公司董事長王麒認為推廣新能源汽車是汽車行業對治理霧霾做出貢獻的重要途徑之一,新能源汽車的使用推廣環節應當與生產研發的進度相匹配,要加快新能源汽車的推廣力度,完善新能源汽車相關配套設施建設。  
民進中央:可在有條件的省份率先開展低速電動車試點   民進中央提案呼籲,工信部、發改委和公安部應儘快出臺行業准入辦法,對具有汽車生產資質、產品通過檢測的低速電動車企業實行准入管理。建議將低速電動汽車和燃油汽車、新能源汽車進行分類管理。針對低速電動汽車制定一套嚴格的管理辦法,包括車輛生產、檢測、保險、上路管理等多個環節。在道路資源和安全可控範圍內,將低速電動車納入現行的道路交通管理體系,除高速公路和限行道路外,所有鄉村公路和城鎮的部分道路應向低速電動汽車開放。車輛可採用專用明顯標誌的車牌便於識別,駕照可考慮使用C4/C5駕照。   同時,民進中央提案建議,在有條件的省份率先開展低速電動車試點工作,探索一種以市場需求為拉動,政府不投資和補貼,技術路線由低到高、先易後難、自主研發,在發展中規範、在規範中提高的發展模式。  
全國工商聯:建議儘快廢除計程車專營制度   全國工商聯向大會遞交提案,建議儘快廢除計程車專營制度,讓計程車行業回歸市場經濟的軌道。政府應明確職責定位,徹底斬斷與行業之間的利益關聯,從行業中完全退場。此外,計程車市場應對社會開放,破除“數量管制”、建立“品質管制”體系。  
丁金宏:國家應出臺統一的城市限車標準   全國政協委員、人口學家丁金宏教授此次帶來了“限制大城市汽車消費”的提案,建議國家出臺統一標準,“城市規模達到多大,汽車擁有量達到多高時,就採取不同的措施,包括限購、重點區域限行、擁擠收費等,綜合採取多項措施,而不是多個城市單獨去做。”他說,限車如果僅僅從消費者權利和汽車發展利益出發,不能保證城市健康發展。  
施傑:應嚴管佔用應急車道 加大處罰   全國政協委員、國浩律師(成都)事務所律師施傑在提案中建議,對違反交規的駕駛行為,除了加大處罰力度以外,還應搭建一個公共監督平臺,讓公眾在履行自己監督義務時有暢道的管道。   施傑認為,對佔用應急車道行為除了加大罰款、扣分力度以外,同時還可實行“責任形式多元化”,用法律的“籠子”把佔用高速應急車道的行為給牢牢關住。 他認為,相關交通管理部門可以通過公眾微信帳號、微博平臺,收集公眾提供的照片、錄影等證據,一經核實便納入交管系統。對違規車輛進行處罰。同時對舉報者給予一定獎勵,當然,若發生虛報謊報情形也會加以懲處。讓公眾成為“移動電子眼”,以便做到無縫監督。  
宗慶後:建議儘快取消汽車限購限行   全國人大代表、娃哈哈集團董事長宗慶後:建議取消限購限行,加強對尾氣排放的檢測監管和交通基礎設施改善,從而減輕汽車帶來的環境壓力和交通壓力。   在宗慶後看來,現在限行限購都存在不合理的地方。“有些地方想用限購限行解決堵車問題、空氣污染問題,但限了購也限了行,路依舊在堵,空氣依舊污染,只是給老百姓造成了一些困惑而已。”他指出,目前汽車尾氣排放存在三個問題,一個是汽油的品質問題;二是噴油嘴的問題,噴油嘴不好不能噴得很均勻,不能充分燃燒,尾氣就大了;三是尾氣過濾問題。  
蔡繼明:計程車行業可試點放開平等競爭   全國人大代表、清華大學政治經濟學研究中心主任蔡繼明指出,專車和計程車一樣是平等競爭,而打破計程車行業壟斷的改革,需要改變現行法規,至少應試點在某個區域、一定時間內暫時停止執行相關法律檔。  
王鳳英:應加強交通綜合治理法制建設   全國人大代表、長城汽車總裁王鳳英提出要加強城市交通綜合治理法制建設。她建議站在國家法律、制度層面進行具有前瞻性的頂層設計,建立健全具有中國特色的、符合國情的相關法律和配套制度體系,保障各項交通治理政策措施的推出有法可依、有法必依;並在符合法律框架的前提下,積極採用最新的資訊技術、智慧交通等科技手段,對城市發展規劃、道路路網建設、市政交通管理等進行系統性創新管理,從而全面、高效地從根本上解決城市交通擁堵難題。     文章來源:第一電動網

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

法國將提高純電動車補貼 增至一萬歐元

據京華時報報導,法國政府正在醞釀進一步提高電動汽車的購買補貼,計畫將純電動汽車的補貼從6300歐元增加到1萬歐元,插電式混合動力車的購買補貼從4000歐元增加到6500歐元。   如果消費者家中有開了13年或者時間更長的柴油車,還可以舊換新,在所有補貼的基礎上再額外獲得3700歐元或者2500歐元的獎勵。而依然購買新柴油車(歐6車輛排放低於110克/CO)的消費者,只能獲得500歐元的獎勵。目前相關補貼方案正在制定中,並有望於今年4月1日起實行。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!