• spring bean初始化过程中的9个beanProcesser 后置处理器


    BeanPostProcessor  是spring容器的容器的一个扩展点,可以进行自定义的实例化、初始化、依赖装配、依赖检查等流程,即可以覆盖默认的实例化,也可以增强初始化、依赖注入、依赖检查等流程。

    Spring提供了很多BeanPostProcesser的扩展接口及其实现,用于完成除实例化之外的其他功能。

    public interface BeanPostProcessor {
            
    	@Nullable
    	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    		return bean;
    	}
    
    	
    	@Nullable
    	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    		return bean;
    	}    
    

      bean的创建和初始化

    第1次调用后置处理器:

      

    InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
    

      第1次调用后置处理器在目标对象实例化之前调用,可以返回任意类型的值,如果不为空,此时可以代替原本应该生成的目标对象(一般是代理对象),并且会调用postProcessAfterInitialization 方法,否则走正常实例化流程

      源码跟踪:

        调用 AbstractAutowireCapableBeanFactory的createBean方法创建bean实例

      

    protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
    			throws BeanCreationException {
    
           。。。。。。。
        
         try {
    			// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
    			// 实例化前的后置处理器调用 InstantiationAwareBeanPostProcessor
    			// 第1次调用后置处理器
    			Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
    			if (bean != null) {
    				// 直接返回替代的对象实例,不再实例化目标对象
    				return bean;
    			}
    		}
    
    }
    

      AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation 代码如下

    protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
    		Object bean = null;
    		if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
    			// Make sure bean class is actually resolved at this point.
    			//确保此时bean类已经被解析
    			if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    				Class<?> targetType = determineTargetType(beanName, mbd);
    				if (targetType != null) {
    					// 在目标对象实例化之前调用,可以返回任意类型的值,如果不为空,
    					// 此时可以代替原本应该生成的目标对象实例(一般是代理对象)
    					bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
    					if (bean != null) {
    						// 如果bean不为空,调用 postProcessAfterInitialization 方法,否则走正常实例化流程
    						bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
    					}
    				}
    			}
    			mbd.beforeInstantiationResolved = (bean != null);
    		}
    		return bean;
    	}


    @Nullable
    protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
    if (bp instanceof InstantiationAwareBeanPostProcessor) {
    InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;

    Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
    if (result != null) {
    return result;
    }
    }
    }
    return null;
    }

    InstantiationAwareBeanPostProcessor 是BeanPostProcesser的子类,如果bean继承了该类,则会走 applyBeanPostProcessorsBeforeInstantiation方法,不会走正常的实例化bean的流程,在此返回object对象

    通过如上代码可以进行实例化的预处理(自定义实例化bean,如创建相应的代理对象)和后处理(如 进行自定义实例化的bean的依赖装配)。 

    第2次调用后置处理器:

      第2次调用后置处理器,决定以哪种方式创建bean对象,(先通过工厂创建;如果不能,使用构造方法;最后使用无参构造 )假如类中有多个构造方法,生效的优先级(@Autowired > autowireMode =3  > 无参构造)

    SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors

    确定要为给定bean使用的候选构造器,检查所有已注册的构造器,实现类 AutowiredAnnotationBeanPostProcessor,扫描@Autowired修饰的构造器,判断创建对象所用的构造 器(deafult,primary)

    源码跟踪 :
        AbstractAutowireCapableBeanFactory的doCreateBean方法代码如下:

    	protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
    			throws BeanCreationException {
    
             // Instantiate the bean.
    		BeanWrapper instanceWrapper = null;
    		if (mbd.isSingleton()) {
    			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    		}
    		if (instanceWrapper == null) {
    			/**
    			 * 第2次调用后置处理器
    			 * 创建bean实例,并将实例放在包装类BeanWrapper中返回
    			 * 1.通过工厂方法创建bean实例  method.invoke()
    			 * 2.通过构造方法自动注入创建bean实例  clazz.newInstance()
    			 * 3.通过无参构造器创建bean实例 clazz.newInstance()
     			 */
    			instanceWrapper = createBeanInstance(beanName, mbd, args);
    		}
    		final Object bean = instanceWrapper.getWrappedInstance();
    		Class<?> beanType = instanceWrapper.getWrappedClass();
    		if (beanType != NullBean.class) {
    			mbd.resolvedTargetType = beanType;
    		}
    
    		// Allow post-processors to modify the merged bean definition.
    		synchronized (mbd.postProcessingLock) {
    			if (!mbd.postProcessed) {
    				try {
    					//允许后置处理器修改合并的bean定义   MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
    					// 第3次调用后置处理器
    					applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
    				}
    				catch (Throwable ex) {
    					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
    							"Post-processing of merged bean definition failed", ex);
    				}
    				mbd.postProcessed = true;
    			}
    		}
    
    		// Eagerly cache singletons to be able to resolve circular references
    		// even when triggered by lifecycle interfaces like BeanFactoryAware.
    		// 急切地缓存单例,以便能够解析循环引用,即使是由生命周期接口(如BeanFactoryAware)触发的。
    		// 如果当前是单例,且允许循环依赖而且当前bean处于创建状态
    		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");
    			}
    			// 添加到singletonFactories SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference
    			//第4次调用后置处理器  提前暴露对象的引用到 singletonFactory 单例工厂中,解决循环引用的问题
    			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    		}
    
    		// Initialize the bean instance.
    		// 初始化bean实例
    		Object exposedObject = bean;
    		try {
    			// 填充bean 设置属性  InstantiationAwareBeanPostProcessors
    			// 第5次,第6次调用后置处理器     注入依赖
    			populateBean(beanName, mbd, instanceWrapper);
    			// 初始化bean
    			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);
    			}
    		}
    		// 提前暴露单例
    		if (earlySingletonExposure) {
    			Object earlySingletonReference = getSingleton(beanName, false);
    			if (earlySingletonReference != null) {
    				if (exposedObject == bean) {
    					exposedObject = earlySingletonReference;
    				}
    				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
    					String[] dependentBeans = getDependentBeans(beanName);
    					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
    					for (String dependentBean : dependentBeans) {
    						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
    							actualDependentBeans.add(dependentBean);
    						}
    					}
    					if (!actualDependentBeans.isEmpty()) {
    						throw new BeanCurrentlyInCreationException(beanName,
    								"Bean with name '" + beanName + "' has been injected into other beans [" +
    								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
    								"] in its raw version as part of a circular reference, but has eventually been " +
    								"wrapped. This means that said other beans do not use the final version of the " +
    								"bean. This is often the result of over-eager type matching - consider using " +
    								"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
    					}
    				}
    			}
    		}
    
    		// Register bean as disposable.
    		try {
    			// 将bean注册为可以销毁   DestructionAwareBeanPostProcessor bean的销毁后置处理器
    			// 第九次调用后置处理器
    			registerDisposableBeanIfNecessary(beanName, bean, mbd);
    		}
    		catch (BeanDefinitionValidationException ex) {
    			throw new BeanCreationException(
    					mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
    		}
    
    		return exposedObject;
    
    
    
    
    }
    

      

    AbstractAutowireCapableBeanFactory.createBeanInstance()

    protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
    
          ..........
         // Candidate constructors for autowiring?
    		// 自动装配的候选构造器   SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors
    		// 第2次调用后置处理器
    		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
    		//  AutowireMode设置为3,采用构造器贪婪模式  最多参数构造器注入    Autowire)constructor = 3
    		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
    				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
    			return autowireConstructor(beanName, mbd, ctors, args);
    		}
    
    		// Preferred constructors for default construction?
    		// 默认构造的首选构造器
    		ctors = mbd.getPreferredConstructors();
    		if (ctors != null) {
    			return autowireConstructor(beanName, mbd, ctors, null);
    		}
    
    		// No special handling: simply use no-arg constructor.
    		// 无参构造器
    		return instantiateBean(beanName, mbd);
    
    }    
    

      

    第3次调用后置处理器:见上面代码:

        

    MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
    

      允许后置处理器修改合并的bean定义 ,实现类 AutowiredAnnotationBeanPostProcessor,用于扫描 @Autowired和@Value修饰的属性和方法,封装到InjectionMetadata。  

      AutowiredAnnotationBeanPostProcessor 将扫描 Spring 容器中所有 Bean,当发现 Bean 中拥有@Autowired 注解时就找到和其匹配(默认按类型匹配)的 Bean,并注入到对应的地方中去。会调用到AutowiredAnnotationBeanPostProcessor 的buildAutowiringMetadata()方法,buildAutowiringMetadata方法解析等待自动注入类的所有属性。它通过分析所有字段和方法并初始化org.springframework.beans.factory.annotation.InjectionMetadata类的实例来实现。
    详细代码如下:
    private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
    		List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
    		Class<?> targetClass = clazz;
    
    		do {
    			final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
    
    			ReflectionUtils.doWithLocalFields(targetClass, field -> {
    // 找到@Autowired 注解修饰的属性, AnnotationAttributes ann = findAutowiredAnnotation(field); if (ann != null) { if (Modifier.isStatic(field.getModifiers())) { if (logger.isInfoEnabled()) { logger.info("Autowired annotation is not supported on static fields: " + field); } return; } boolean required = determineRequiredStatus(ann); currElements.add(new AutowiredFieldElement(field, required)); } }); ReflectionUtils.doWithLocalMethods(targetClass, method -> { Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) { return; }// 找到@Autowired 修饰的方法 AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod); if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { if (Modifier.isStatic(method.getModifiers())) { if (logger.isInfoEnabled()) { logger.info("Autowired annotation is not supported on static methods: " + method); } return; } if (method.getParameterCount() == 0) { if (logger.isInfoEnabled()) { logger.info("Autowired annotation should only be used on methods with parameters: " + method); } } boolean required = determineRequiredStatus(ann); PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); currElements.add(new AutowiredMethodElement(method, required, pd)); } }); elements.addAll(0, currElements); targetClass = targetClass.getSuperclass(); } while (targetClass != null && targetClass != Object.class);           // 生成一个 InjectionMetadata对象,进行属性赋值(通过反射) return new InjectionMetadata(clazz, elements); }

      

      第4次调用后置处理器:解决循环引用的问题,提前暴露对象的应用到 SingletonFactory 单例工厂中,此时对象的属性未赋值;详细见spring循环依赖;

      第5次调用后置处理器:

           

    InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation
    

      在目标对象实例化之后调用,此时对象被实例化,但是对象的属性还未被设置。如果该方法返回false,则会忽略之后的属性设置;返回true,则进行正常的属性设置。

    源码跟踪:

      

    // 初始化bean实例
    		Object exposedObject = bean;
    		try {
    			// 填充bean 设置属性  InstantiationAwareBeanPostProcessors
    			// 第5次,第6次调用后置处理器     注入依赖
    			populateBean(beanName, mbd, instanceWrapper);
    			// 初始化bean
    			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);
    			}
    		}
    

      

    	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 {
    				// Skip property population phase for null instance.
    				return;
    			}
    		}
    
    		// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the
    		// state of the bean before properties are set. This can be used, for example,
    		// to support styles of field injection.
    		boolean continueWithPropertyPopulation = true;
    		//  在属性设置之前修改bean的状态  InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation
    		// 第5次调用后置处理器
    		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    			for (BeanPostProcessor bp : getBeanPostProcessors()) {
    				if (bp instanceof InstantiationAwareBeanPostProcessor) {
    					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
    					//在目标对象实例化之后调用,此时对象被实例化,但是对象的属性还未设置。如果该方法返回
    					//fasle,则会忽略之后的属性设置。返回true,按正常流程设置属性值
    					if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
    						continueWithPropertyPopulation = false;
    						break;
    					}
    				}
    			}
    		}
    
    		if (!continueWithPropertyPopulation) {
    			// InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation方法返回false,
    			// 直接返回,将忽略实例的属性设置
    			return;
    		}
    
    		PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
    
    		// 判断autowireMode否是 by_name或者by_type
    		if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_NAME || mbd.getResolvedAutowireMode() == AUTOWIRE_BY_TYPE) {
    			MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
    			// Add property values based on autowire by name if applicable.
    			if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_NAME) {
    				autowireByName(beanName, mbd, bw, newPvs);
    			}
    			// Add property values based on autowire by type if applicable.
    			if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_TYPE) {
    				autowireByType(beanName, mbd, bw, newPvs);
    			}
    			pvs = newPvs;
    		}
    
    		boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
    		boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
    
    		PropertyDescriptor[] filteredPds = null;
    		if (hasInstAwareBpps) {
    			if (pvs == null) {
    				pvs = mbd.getPropertyValues();
    			}
    			//InstantiationAwareBeanPostProcessor.postProcessProperties
    			// 第6次调用后置处理器
    			// 可以在该方法内对属性值进行修改(此时属性值还未设置,但可以修改原本会设置的进去的属性值)。
    			// 如果postProcessAfterInstantiation方法返回false,该方法不会调用
    			for (BeanPostProcessor bp : getBeanPostProcessors()) {
    				if (bp instanceof InstantiationAwareBeanPostProcessor) {
    
    					//处理 @Autowired 属性注入逻辑
    					// AutowiredAnnotationBeanPostProcessor.postProcessProperties
    					//InjectionMetadata#inject
    					//AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
    					//DefaultListableBeanFactory#resolveDependency
    					//DefaultListableBeanFactory#doResolveDependency
    					//DependencyDescriptor#resolveCandidate //此方法直接返回 beanFactory.getBean(beanName);
    
    					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
    					PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
    					if (pvsToUse == null) {
    						if (filteredPds == null) {
    							filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
    						}
    						pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
    						if (pvsToUse == null) {
    							return;
    						}
    					}
    					pvs = pvsToUse;
    				}
    			}
    		}
    		if (needsDepCheck) {
    			if (filteredPds == null) {
    				filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
    			}
    			checkDependencies(beanName, mbd, filteredPds, pvs);
    		}
    
    		if (pvs != null) {
    			// 普通属性填充   propertyValues
    			applyPropertyValues(beanName, mbd, bw, pvs);
    		}
    	}
    

      

      第6次调用后置处理器,用来处理Bean中 属性注入,包括 @Autowired 注入;此时如果存在循环引用的问题,也会一并处理掉(从单例工厂中获取属性bean,生成earlySingletonObject,添加到earlySingletonObjects中,解决循环依赖问题);(代码见上)

      第7次调用后置处理器:  主要用于处理aware接口,bean.setXXXAware()

    protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
    		if (System.getSecurityManager() != null) {
    			AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
    				invokeAwareMethods(beanName, bean);
    				return null;
    			}, getAccessControlContext());
    		}
    		else {
    
    			invokeAwareMethods(beanName, bean);
    		}
    
     		Object wrappedBean = bean;
    		if (mbd == null || !mbd.isSynthetic()) {
    			//  BeanPostProcessor.postProcessBeforeInitialization
    			//第7次调用后置处理器
    			//  如果配置了@PostConstruct  会调用
    			// InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization
    			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    		}
    
    		try {
    			// 执行bean生命周期回调的init方法
    			invokeInitMethods(beanName, wrappedBean, mbd);
    		}
    		catch (Throwable ex) {
    			throw new BeanCreationException(
    					(mbd != null ? mbd.getResourceDescription() : null),
    					beanName, "Invocation of init method failed", ex);
    		}
    		if (mbd == null || !mbd.isSynthetic()) {
    			//  BeanPostProcessor.postProcessAfterInitialization
    			//第8次调用后置处理器
    			//   aop实现:AbstractAutoProxyCreator#postProcessAfterInitialization
    			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    		}
    
    		return wrappedBean;
    	}
    

      

    BeanPostProcessor#postProcessBeforeInitialization
    

      在bean new出来并完成属性填充之后,回调init方法之前调用;

      其他扩展:处理aware方法调用,比如实现了ApplicationContextAware或者EnvironmentAware接口,

    ApplicationContextAwareProcessor#postProcessBeforeInitialization
    

      

    如果配置了@PostConstruct 会调用     

    InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization
    

      说明 :构造方法 > @Autowired > @PostContruct 方法 执行顺序 (此时还未执行bean的 init 初始化方法),@PostContruct 修饰的方法,主要用于bean实例化 属性注入之后的调用

    源码追踪:

    AbstractAutowireCapableBeanFactory#initializeBean(String, Object, RootBeanDefinition) 
    > wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName)
    

      

      第8次调用后置处理器:在执行bean的 init方法之后调用;扩展:spring的aop基于此实现;源码见 上

      第9次调用后置处理器:标记单例bean为可销毁的,

    AbstractBeanFactory  
    
        protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {
    		AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null);
    		//requiresDestruction中调用DestructionAwareBeanPostProcessor#requiresDestruction
    		//第九次调用后置处理器  确定给定的bean实例是否需要销毁后处理器  单例bean才会加标记 为可销毁,
    // (这里是registerDisposableBean方法加入到 DefaultSingletonBeanFactory 中的 disposableBeans 中,是个map,k-beanName,v-bean) if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) { if (mbd.isSingleton()) { // Register a DisposableBean implementation that performs all destruction // work for the given bean: DestructionAwareBeanPostProcessors, // DisposableBean interface, custom destroy method. registerDisposableBean(beanName, new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc)); } else { // A bean with a custom scope... Scope scope = this.scopes.get(mbd.getScope()); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + mbd.getScope() + "'"); } scope.registerDestructionCallback(beanName, new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc)); } } }

      

        

      

  • 相关阅读:
    [转载]android开发手册
    [转载]windows phone7 学习笔记10——生命周期/墓碑化
    [转载]Windows Phone 系列 本地数据存储
    【转载】windows phone7 学习笔记12——推送通知服务
    【转载】windows phone7 学习笔记15——Bing Maps
    [转载]WP7交互特性浅析及APP设计探究
    【】windows phone7 学习笔记11——启动器与选择器
    [转载]支持的 Windows Phone 媒体编解码器
    【转载】wp7屏幕截图代码
    【转载】windows phone7 学习笔记14——地理位置服务与反应性扩展框架
  • 原文地址:https://www.cnblogs.com/wl20200316/p/12560920.html
Copyright © 2020-2023  润新知