返回顶部
首页 > 资讯 > 精选 >Spring populateBean属性赋值和自动注入的方法是什么
  • 587
分享到

Spring populateBean属性赋值和自动注入的方法是什么

2023-07-05 12:07:28 587人浏览 安东尼
摘要

这篇文章主要介绍“spring populateBean属性赋值和自动注入的方法是什么”,在日常操作中,相信很多人在Spring populateBean属性赋值和自动注入的方法是什么问题上存在疑惑,小编查阅了各式资料,

这篇文章主要介绍“spring populateBean属性赋值和自动注入的方法是什么”,在日常操作中,相信很多人在Spring populateBean属性赋值和自动注入的方法是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Spring populateBean属性赋值和自动注入的方法是什么”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

正文

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.   //一、修改Bean实例   //给InstantiationAwareBeanPostProcessors最后一个机会在属性设置前改变bean   // 具体通过调用ibp.postProcessAfterInstantiation方法,如果调用返回false,表示不必继续进行依赖注入,直接返回   if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {      for (BeanPostProcessor bp : getBeanPostProcessors()) {         if (bp instanceof InstantiationAwareBeanPostProcessor) {            InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;            //返回值为true则继续填充bean            if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {               return;            }         }      }   }   PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);    // 根据bean的依赖注入方式:即是否标注有 @Autowired 注解或 autowire=“byType/byName” 的标签   // 会遍历bean中的属性,根据类型或名称来完成相应的注入   int resolvedAutowireMode = mbd.getResolvedAutowireMode();   if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {      MutablePropertyValues newPvs = new MutablePropertyValues(pvs);      // Add property values based on autowire by name if applicable.      //二、根据名称自动注入      if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {         autowireByName(beanName, mbd, bw, newPvs);      }      // Add property values based on autowire by type if applicable.      //三、根据类型自动注入      if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {         autowireByType(beanName, mbd, bw, newPvs);      }      pvs = newPvs;   }    // 容器是否注册了InstantiationAwareBeanPostProcessor   boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();   // 是否进行依赖检查   boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);   PropertyDescriptor[] filteredPds = null;   if (hasInstAwareBpps) {      if (pvs == null) {         pvs = mbd.getPropertyValues();      }      for (BeanPostProcessor bp : getBeanPostProcessors()) {         if (bp instanceof InstantiationAwareBeanPostProcessor) {            InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;            PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);            if (pvsToUse == null) {               if (filteredPds == null) {                  filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);               }               //对所有需要依赖检查的属性进行后处理               //四、处理属性值(@Autowired、@Value)               pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);               if (pvsToUse == null) {                  return;               }            }            pvs = pvsToUse;         }      }   }   // 检查是否满足相关依赖关系,对应的depends-on属性,3.0后已弃用   if (needsDepCheck) {      if (filteredPds == null) {         filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);      }      checkDependencies(beanName, mbd, filteredPds, pvs);   }   // 五、填充属性   if (pvs != null) {      applyPropertyValues(beanName, mbd, bw, pvs);   }}

一、postProcessAfterInstantiation:修改Bean实例

在填充属性之前调用postProcessAfterInstantiation修改Bean定义信息

if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {   for (BeanPostProcessor bp : getBeanPostProcessors()) {      if (bp instanceof InstantiationAwareBeanPostProcessor) {         InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;         if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {            return;         }      }   }}

二、autowireByName:根据名称自动注入

protected void autowireByName(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {    //寻找bw中需要依赖注入的属性    String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);    for (String propertyName : propertyNames) {        //检查缓存bean中是否存在当前bean        if (containsBean(propertyName)) {            //递归初始化相关的bean. 代码(1)            Object bean = getBean(propertyName);            pvs.add(propertyName, bean);            //注册依赖            reGISterDependentBean(propertyName, beanName);            if (logger.isTraceEnabled()) {                logger.trace("Added autowiring by name from bean name '" + beanName +                        "' via property '" + propertyName + "' to bean named '" + propertyName + "'");            }        } else {            // 找不到则不处理            if (logger.isTraceEnabled()) {                logger.trace("Not autowiring property '" + propertyName + "' of bean '" + beanName +                        "' by name: no matching bean found");            }        }    }}

三、autowireByType:根据类型自动注入

protected void autowireByType(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {    // 获取自定义的类型转换器    TypeConverter converter = getCustomTypeConverter();    if (converter == null) {        converter = bw;    }    Set<String> autowiredBeanNames = new LinkedHashSet<>(4);    //寻找bw中需要依赖注入的属性    String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);    for (String propertyName : propertyNames) {        try {            // 获取属性描述符            PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);            //不要尝试按类型为Object类型自动装配:即使从技术上讲是不满意的,非简单的属性,也没有意义。            if (Object.class != pd.getPropertyType()) {                //探测指定属性的set方法                MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);                // Do not allow eager init for type matching in case of a prioritized post-processor.                boolean eager = !PriorityOrdered.class.isInstance(bw.getWrappedInstance());                DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager);                //解析指定beanName的属性所匹配的值,并把解析到的属性名称存储在autowiredBeanNames中,当属性存在多个封装bean时                //比如: @Autowired private List<A> aList; 就会找到所有匹配A类型的bean并将其注入                Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter);                if (autowiredArgument != null) {                    // 添加到待注入的bean列表中                    pvs.add(propertyName, autowiredArgument);                }                for (String autowiredBeanName : autowiredBeanNames) {                    //注册依赖                    registerDependentBean(autowiredBeanName, beanName);                    if (logger.isTraceEnabled()) {                        logger.trace("Autowiring by type from bean name '" + beanName + "' via property '" +                                propertyName + "' to bean named '" + autowiredBeanName + "'");                    }                }                autowiredBeanNames.clear();            }        } catch (BeansException ex) {            throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex);        }    }}

四、postProcessPropertyValues:处理属性值(@Resource、@Autowired、@Value)

CommonAnnotationBeanPostProcessor:处理@Resource

AutowiredAnnotationBeanPostProcessor:处理@Autowired、@Value。

详情:https://www.yisu.com/article/277330.htm

五、applyPropertyValues:填充属性

protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {    if (pvs.isEmpty()) {        return;    }    if (System.getSecurityManager() != null && bw instanceof BeanWrapperImpl) {        ((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext());    }    MutablePropertyValues mpvs = null;    List<PropertyValue> original;    if (pvs instanceof MutablePropertyValues) {        mpvs = (MutablePropertyValues) pvs;        //如果mpvs中的值已经被转换为对应的类型那么可以直接设置到beanWrapper        if (mpvs.isConverted()) {            // Shortcut: use the pre-converted values as-is.            try {                bw.setPropertyValues(mpvs);                return;            } catch (BeansException ex) {                throw new BeanCreationException(                        mbd.getResourceDescription(), beanName, "Error setting property values", ex);            }        }        original = mpvs.getPropertyValueList();    } else {        //如果pvs并不是使用MutablePropertyValues封装的类型,那么直接使用原始的属性获取方法        original = Arrays.asList(pvs.getPropertyValues());    }    TypeConverter converter = getCustomTypeConverter();    if (converter == null) {        converter = bw;    }    //获取对应的解析器    BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);    // Create a deep copy, resolving any references for values.    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();            if (originalValue == AutowiredPropertyMarker.INSTANCE) {                Method writeMethod = bw.getPropertyDescriptor(propertyName).getWriteMethod();                if (writeMethod == null) {                    throw new IllegalArgumentException("Autowire marker for property without write method: " + pv);                }                originalValue = new DependencyDescriptor(new MethodParameter(writeMethod, 0), true);            }            //解析、注入值            Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);            Object convertedValue = resolvedValue;            boolean convertible = bw.isWritableProperty(propertyName) &&                    !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);            if (convertible) {                convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);            }            // Possibly store converted value in merged bean definition,            // in order to avoid re-conversion for every created bean instance.            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));            }        }    }    if (mpvs != null && !resolveNecessary) {        mpvs.setConverted();    }    // Set our (possibly massaged) deep copy.    try {        bw.setPropertyValues(new MutablePropertyValues(deepCopy));    } catch (BeansException ex) {        throw new BeanCreationException(                mbd.getResourceDescription(), beanName, "Error setting property values", ex);    }}

解析、注入值

public Object resolveValueIfNecessary(Object argName, @Nullable Object value) {    // We must check each value to see whether it requires a runtime reference    // to another bean to be resolved.    // 5.1 解析引用    if (value instanceof RuntimeBeanReference) {        RuntimeBeanReference ref = (RuntimeBeanReference) value;        return resolveReference(argName, ref);    }    // 如果根据另一个Bean的name进行依赖,进入下面的分支    else if (value instanceof RuntimeBeanNameReference) {        String refName = ((RuntimeBeanNameReference) value).getBeanName();        refName = String.valueOf(doEvaluate(refName));        if (!this.beanFactory.containsBean(refName)) {            throw new BeanDefinitionStoreException(                    "Invalid bean name '" + refName + "' in bean reference for " + argName);        }        return refName;    }    // 解析BeanDefinitionHolder    else if (value instanceof BeanDefinitionHolder) {        // Resolve BeanDefinitionHolder: contains BeanDefinition with name and aliases.        BeanDefinitionHolder bdHolder = (BeanDefinitionHolder) value;        return resolveInnerBean(argName, bdHolder.getBeanName(), bdHolder.getBeanDefinition());    }    // 解析纯BeanDefinition    else if (value instanceof BeanDefinition) {        // Resolve plain BeanDefinition, without contained name: use dummy name.        BeanDefinition bd = (BeanDefinition) value;        String innerBeanName = "(inner bean)" + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR +                ObjectUtils.getIdentityHexString(bd);        return resolveInnerBean(argName, innerBeanName, bd);    }    // 解析数组    else if (value instanceof ManagedArray) {        // May need to resolve contained runtime references.        ManagedArray array = (ManagedArray) value;        Class<?> elementType = array.resolvedElementType;        if (elementType == null) {            String elementTypeName = array.getElementTypeName();            if (StringUtils.hasText(elementTypeName)) {                try {                    elementType = ClassUtils.forName(elementTypeName, this.beanFactory.getBeanClassLoader());                    array.resolvedElementType = elementType;                }                catch (Throwable ex) {                    // Improve the message by showing the context.                    throw new BeanCreationException(                            this.beanDefinition.getResourceDescription(), this.beanName,                            "Error resolving array type for " + argName, ex);                }            }            else {                elementType = Object.class;            }        }        return resolveManagedArray(argName, (List<?>) value, elementType);    }    //5.2解析List    else if (value instanceof ManagedList) {        // May need to resolve contained runtime references.        return resolveManagedList(argName, (List<?>) value);    }    // 解析Set    else if (value instanceof ManagedSet) {        // May need to resolve contained runtime references.        return resolveManagedSet(argName, (Set<?>) value);    }    // 解析Map    else if (value instanceof ManagedMap) {        // May need to resolve contained runtime references.        return resolveManagedMap(argName, (Map<?, ?>) value);    }    // 解析Properties    else if (value instanceof ManagedProperties) {        Properties original = (Properties) value;        Properties copy = new Properties();        original.forEach((propKey, propValue) -> {            if (propKey instanceof TypedStringValue) {                propKey = evaluate((TypedStringValue) propKey);            }            if (propValue instanceof TypedStringValue) {                propValue = evaluate((TypedStringValue) propValue);            }            if (propKey == null || propValue == null) {                throw new BeanCreationException(                        this.beanDefinition.getResourceDescription(), this.beanName,                        "Error converting Properties key/value pair for " + argName + ": resolved to null");            }            copy.put(propKey, propValue);        });        return copy;    }    // 解析String    else if (value instanceof TypedStringValue) {        // Convert value to target type here.        TypedStringValue typedStringValue = (TypedStringValue) value;        Object valueObject = evaluate(typedStringValue);        try {            Class<?> resolvedTargetType = resolveTargetType(typedStringValue);            if (resolvedTargetType != null) {                return this.typeConverter.convertIfNecessary(valueObject, resolvedTargetType);            }            else {                return valueObject;            }        }        catch (Throwable ex) {            // Improve the message by showing the context.            throw new BeanCreationException(                    this.beanDefinition.getResourceDescription(), this.beanName,                    "Error converting typed String value for " + argName, ex);        }    }    else if (value instanceof NullBean) {        return null;    }    else {        return evaluate(value);    }}

5.1 解析依赖

核心还是getBean方法!开始触发关联创建Bean

private Object resolveReference(Object argName, RuntimeBeanReference ref) {    try {        Object bean;        // 获取BeanName        String refName = ref.getBeanName();        refName = String.valueOf(doEvaluate(refName));        // 如果Bean在父容器,则去父容器取        if (ref.isToParent()) {            if (this.beanFactory.getParentBeanFactory() == null) {                throw new BeanCreationException(                        this.beanDefinition.getResourceDescription(), this.beanName,                        "Can't resolve reference to bean '" + refName +                                "' in parent factory: no parent factory available");            }            bean = this.beanFactory.getParentBeanFactory().getBean(refName);        }        else {            // 在本容器,调用getBean            bean = this.beanFactory.getBean(refName);            this.beanFactory.registerDependentBean(refName, this.beanName);        }        if (bean instanceof NullBean) {            bean = null;        }        return bean;    }    catch (BeansException ex) {        throw new BeanCreationException(                this.beanDefinition.getResourceDescription(), this.beanName,                "Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex);    }}

5.2 解析List

直接把 List 集合塞入属性中即可。

private List<?> resolveManagedList(Object argName, List<?> ml) {    List<Object> resolved = new ArrayList<>(ml.size());    for (int i = 0; i < ml.size(); i++) {        resolved.add(resolveValueIfNecessary(new KeyedArgName(argName, i), ml.get(i)));    }    return resolved;}

到此,关于“Spring populateBean属性赋值和自动注入的方法是什么”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

--结束END--

本文标题: Spring populateBean属性赋值和自动注入的方法是什么

本文链接: https://lsjlt.com/news/351628.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Spring populateBean属性赋值和自动注入的方法是什么
    这篇文章主要介绍“Spring populateBean属性赋值和自动注入的方法是什么”,在日常操作中,相信很多人在Spring populateBean属性赋值和自动注入的方法是什么问题上存在疑惑,小编查阅了各式资料,...
    99+
    2023-07-05
  • Spring注解驱动开发之属性赋值的示例分析
    这篇文章给大家分享的是有关Spring注解驱动开发之属性赋值的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、@Value注解在Person的属性上使用@Value注解指定注入值public ...
    99+
    2023-06-15
  • Spring Bean集合注入和自动装配的方法
    本篇内容介绍了“Spring Bean集合注入和自动装配的方法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、Spring Be...
    99+
    2023-07-02
  • 利用@Value注解为bean的属性赋值方法总结
    目录1.@Value注解2.@Value注解的用法2.1.不通过配置文件注入属性的情况2.2.通过配置文件注入属性的情况2.3.@Value中#{··&m...
    99+
    2023-05-19
    @Value注解为bean的属性赋值 @Value为bean属性赋值 @Value bean
  • Spring Cloud自定义引导属性源的方法是什么
    本篇内容介绍了“Spring Cloud自定义引导属性源的方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!引导过程添加的外部配置的默...
    99+
    2023-06-05
  • python输入数字赋值的方法是什么
    Python中可以使用input函数来获取用户输入的数字,并将其赋值给变量。input函数会将用户输入的内容作为字符串返回,如果需要...
    99+
    2023-09-17
    python
  • Spring自动注入失败的解决方法
    目录Spring自动注入失败如何解决?回答注入你的bean使用@Configurable手动查找bean:不推荐Spring自动注入失败如何解决? 我有一个被Spring @Ser...
    99+
    2024-04-02
  • Spring三种方法的注解自动注入问题
    目录Spring三种方法的注解自动注入1 @Autowired注解2 @Resource3 @InjectSpring 注解版 属性赋值 自动注入总结Spring三种方法的注解自动注...
    99+
    2022-12-28
    Spring注解 Spring自动注入 Spring注解自动注入
  • Flutter模型动态化赋值的方法是什么
    今天小编给大家分享一下Flutter模型动态化赋值的方法是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。实现思路通过重载...
    99+
    2023-07-05
  • css中的浮动属性值是什么
    这篇“css中的浮动属性值是什么”文章,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要参考一下,对于“css中的浮动属性值是什么”,小编整理了以下知识点,请大家跟着小编的步伐一步一步的慢慢理解,接下来就让我们进入主题...
    99+
    2023-06-06
  • Python List的赋值方法是什么
    这篇“Python List的赋值方法是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“...
    99+
    2024-04-02
  • Spring根据XML配置文件注入属性的方法
    方法一使用setter方法package com.swift;public class Book { private String bookName; public void setBook(String bookName) { this...
    99+
    2023-05-30
    spring xml 配置文件
  • mybatis TypeHandler注入spring依赖的方法是什么
    这篇文章主要讲解了“mybatis TypeHandler注入spring依赖的方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“mybatis TypeHandl...
    99+
    2023-06-29
  • Spring中bean集合注入的方法是什么
    这篇文章主要讲解了“Spring中bean集合注入的方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Spring中bean集合注入的方法是什么”吧!Spring作为项目中不可缺少的底...
    99+
    2023-07-02
  • C#枚举赋值的方法是什么
    这篇文章主要讲解了“C#枚举赋值的方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C#枚举赋值的方法是什么”吧!Q:我留意到Code #02中的.field public stati...
    99+
    2023-06-18
  • php给input赋值的方法是什么
    在PHP中,给input赋值有多种方法,取决于你是在服务器端还是在客户端进行操作。1. 在服务器端给input赋值:你可以使用PHP...
    99+
    2023-09-27
    php
  • python连续赋值的方法是什么
    Python中的连续赋值是一种将多个变量分别赋予相同或不同的值的方法。方法1:使用逗号分隔变量```pythona, b, c = ...
    99+
    2023-09-28
    python
  • python字典赋值的方法是什么
    Python中的字典是一种无序的键值对集合,每个键值对之间用逗号分隔,整个字典用花括号{}括起来表示。字典中的键必须是唯一的,而值可...
    99+
    2023-05-13
    python字典赋值 python
  • php变量赋值的方法是什么
    在PHP中,变量可以通过简单的赋值操作来进行赋值。赋值操作使用等号(=)进行,将右边的值赋给左边的变量。例如,要将值"Hello W...
    99+
    2023-08-29
    php
  • c#中radiobutton赋值的方法是什么
    在C#中,通过编程方式给RadioButton赋值可以使用RadioButton的Checked属性。例如,可以通过设置RadioB...
    99+
    2024-03-14
    c#
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作