返回顶部
首页 > 资讯 > 前端开发 > VUE >怎么理解容器DefaultListableBeanFactory
  • 419
分享到

怎么理解容器DefaultListableBeanFactory

2024-04-02 19:04:59 419人浏览 独家记忆
摘要

这篇文章主要介绍“怎么理解容器DefaultListableBeanFactory”,在日常操作中,相信很多人在怎么理解容器DefaultListableBeanFactory问题上存在疑惑,小编查阅了各式

这篇文章主要介绍“怎么理解容器DefaultListableBeanFactory”,在日常操作中,相信很多人在怎么理解容器DefaultListableBeanFactory问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么理解容器DefaultListableBeanFactory”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

 1.DefaultListableBeanFactory

要说 XmlBeanFactory 就不得不先说它的父类 DefaultListableBeanFactory,因为 XmlBeanFactory  中的大部分功能实际上在 DefaultListableBeanFactory 中就已经提供好了,XmlBeanFactory 只是对 io  流的读取做了一些定制而已。

DefaultListableBeanFactory 是一个完整的、功能成熟的 ioc 容器,如果你的需求很简单,甚至可以直接使用  DefaultListableBeanFactory,如果你的需求比较复杂,那么通过扩展 DefaultListableBeanFactory  的功能也可以达到,可以说 DefaultListableBeanFactory 是整个 spring IoC 容器的始祖。

我们先来看一下 DefaultListableBeanFactory 的继承关系:

怎么理解容器DefaultListableBeanFactory

从这张类的关系图中可以看出,DefaultListableBeanFactory 实际上也是一个集大成者。在 Spring 中,针对 Bean  的不同操作都有不同的接口进行规范,每个接口都有自己对应的实现,最终在 DefaultListableBeanFactory  中将所有的实现汇聚到一起。从这张类的继承关系图中我们大概就能感受到 Spring 中关于类的设计是多么厉害,代码耦合度非常低。

这些类,在本系列后面的介绍中,大部分都会涉及到,现在我先大概介绍一下每个类的作用,大家先混个脸熟:

  1. BeanFactory:这个接口看名字就知道是一个 Bean 的工厂,BeanFactory 接口定义了各种获取 Bean 的方法、判断 Bean  是否存在、判断 Bean 是否单例等针对 Bean 的基础方法。

  2. ListableBeanFactory:这个接口继承自 BeanFactory,在 BeanFactory 的基础上,扩展了 Bean  的查询方法,例如根据类型获取 BeanNames、根据注解获取 BeanNames、根据 Bean 获取注解等。

  3. AutowireCapableBeanFactory:该接口继承自 BeanFactory,在 BeanFactory 的基础上,提供了 Bean  的创建、配置、注入、销毁等操作。有时候我们需要自己手动注入 Bean 的时候,可以考虑通过实现该接口来完成。AutowireCapableBeanFactory  在 Spring Security 中有一个重要的应用就是 ObjectPostProcessor,这个松哥将在 ??Spring Security  系列中和大家详细介绍。

  4. HierarchicalBeanFactory:该接口继承自 BeanFactory,并在 BeanFactory 基础上添加了获取 parent  beanfactory 的方法。

  5. SingletonBeanReGIStry:这个接口定义了对单例 Bean 的定义以及获取方法。

  6. ConfigurableBeanFactory:这个接口主要定了针对 BeanFactory 的各种配置以及销毁的方法。

  7. ConfigurableListableBeanFactory:这是 BeanFactory 的配置清单,这里定义了忽略的类型、接口,通过 Bean  的名称获取 BeanDefinition 、冻结 BeanDefinition 等。

  8. AliasRegistry:这个接口定义了对 alias 的注册、移除、判断以及查询操作。

  9. SimpleAliasRegistry:这个类实现了 AliasRegistry 接口并实现了它里边的方法,SimpleAliasRegistry 使用  ConcurrentHashMap 做载体,实现了对 alias 的注册、移除判断以及查询操作。

  10. DefaultSingletonBeanRegistry:这个类基于 Java 中的集合,对 SingletonBeanRegistry  接口进行了实现。

  11. FactoryBeanRegistrySupport:该类继承自 DefaultSingletonBeanRegistry,并在  DefaultSingletonBeanRegistry 的基础上,增加了获取 FactoryBean 类型、移除 FactoryBean  缓存的方法等等操作。

  12. AbstractBeanFactory:实现了 ConfigurableBeanFactory 接口并继承自  FactoryBeanRegistrySupport,在 AbstractBeanFactory 中对 ConfigurableBeanFactory  中定义的方法进行了实现。

  13. AbstractAutowireCapableBeanFactory:该类继承自 AbstractBeanFactory 并对  AutowireCapableBeanFactory 接口中定义的方法进行了落地实现。

  14. BeanDefinitionRegistry:这个接口继承自 AliasRegistry 接口,并增加了一系列针对 BeanDefinition  的注册、移除、查询、判断等方法。

  15. 最后的 DefaultListableBeanFactory 自然就具备了上面所有的功能。

上面的内容可能看的大家眼花缭乱,松哥这里通过几个简单实际的例子,来带大家使用一下 DefaultListableBeanFactory  的功能,可能大家的理解就比较清晰了。

DefaultListableBeanFactory 作为一个集大成者,提供了非常多的功能,我们一个一个来看。

2.代码改造

首先文章中一开始的三行代码我们可以对其略加改造,因为我们已经说了 XmlBeanFactory 中的大部分功能实际上在  DefaultListableBeanFactory 中就已经提供好了,XmlBeanFactory 只是对 IO  流的读取做了一些定制而已,文件的读取主要是通过 XmlBeanDefinitionReader  来完成的(本系列前面文章已经讲过),我们可以对文章一开始的三行代码进行改造,以便更好的体现“XmlBeanFactory 中的大部分功能实际上在  DefaultListableBeanFactory 中就已经提供好了”:

ClassPathResource res=new ClassPathResource("beans.xml"); DefaultListableBeanFactory factory=new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(res); User user = factory.getBean(User.class); System.out.println("user = " + user);

使用前四行代码代替 XmlBeanFactory,这样 XmlBeanFactory 的功能是不是就很明确了?就是前四行代码的功能。

3.动态注册 Bean

动态注册 Bean,这是 DefaultListableBeanFactory 的功能之一,不过准确来说应该是动态注册 BeanDefinition  。

我们先来看一个简单的例子:

DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory(); GenericBeanDefinition userBeanDefinition = new GenericBeanDefinition(); MutablePropertyValues pvs = new MutablePropertyValues(); pvs.add("username", "javaboy"); pvs.add("address", "www.javaboy.org"); userBeanDefinition.setPropertyValues(pvs); userBeanDefinition.setBeanClass(User.class); defaultListableBeanFactory.registerBeanDefinition("user", userBeanDefinition); User user = defaultListableBeanFactory.getBean(User.class); System.out.println("user = " + user);

首先我们自己手动构建一个 DefaultListableBeanFactory 对象。当然也可以使用前面的 XmlBeanFactory。

然后再手动构建一个 GenericBeanDefinition。在前面的文章中,松哥和大家讲过,现在默认使用的 BeanDefinition 就是  GenericBeanDefinition,所以这里我们自己也手动构建一个 GenericBeanDefinition。有了  GenericBeanDefinition 之后,我们设置相关的类和属性。

接下来再将 userBeanDefinition 注册到 defaultListableBeanFactory。注册完成之后,我们就可以从  defaultListableBeanFactory 中获取相应的 Bean 了。

这里说一句题外话,希望大家在阅读本系列每一篇文章的时候,能够将本系列前后文章联系起来一起理解,这样会有很多意料之外的收获。例如上面的,我们既可以声明一个  DefaultListableBeanFactory,也可以声明一个 XmlBeanFactory,那你大概就能据此推断出 XmlBeanFactory  的主要目的可能就是对资源文件进行读取和注册。

那么到底是怎么注册的呢?我们来看一下 defaultListableBeanFactory.registerBeanDefinition  方法的定义:

@Override public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)   throws BeanDefinitionStoreException {  Assert.hasText(beanName, "Bean name must not be empty");  Assert.notNull(beanDefinition, "BeanDefinition must not be null");  if (beanDefinition instanceof AbstractBeanDefinition) {   try {    ((AbstractBeanDefinition) beanDefinition).validate();   }   catch (BeanDefinitionValidationException ex) {    throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,      "Validation of bean definition failed", ex);   }  }  BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);  if (existingDefinition != null) {   if (!isAllowBeanDefinitionOverriding()) {    throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);   }   else if (existingDefinition.getRole() < beanDefinition.getRole()) {    // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE    if (logger.isInfoEnabled()) {     logger.info("Overriding user-defined bean definition for bean '" + beanName +       "' with a framework-generated bean definition: replacing [" +       existingDefinition + "] with [" + beanDefinition + "]");    }   }   else if (!beanDefinition.equals(existingDefinition)) {    if (logger.isDebugEnabled()) {     logger.debug("Overriding bean definition for bean '" + beanName +       "' with a different definition: replacing [" + existingDefinition +       "] with [" + beanDefinition + "]");    }   }   else {    if (logger.isTraceEnabled()) {     logger.trace("Overriding bean definition for bean '" + beanName +       "' with an equivalent definition: replacing [" + existingDefinition +       "] with [" + beanDefinition + "]");    }   }   this.beanDefinitionMap.put(beanName, beanDefinition);  }  else {   if (hasBeanCreationStarted()) {    // Cannot modify startup-time collection elements anymore (for stable iteration)    synchronized (this.beanDefinitionMap) {     this.beanDefinitionMap.put(beanName, beanDefinition);     List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);     updatedDefinitions.addAll(this.beanDefinitionNames);     updatedDefinitions.add(beanName);     this.beanDefinitionNames = updatedDefinitions;     removeManualSingletonName(beanName);    }   }   else {    // Still in startup registration phase    this.beanDefinitionMap.put(beanName, beanDefinition);    this.beanDefinitionNames.add(beanName);    removeManualSingletonName(beanName);   }   this.frozenBeanDefinitionNames = null;  }  if (existingDefinition != null || containsSingleton(beanName)) {   resetBeanDefinition(beanName);  }  else if (isConfigurationFrozen()) {   clearByTypeCache();  } }

registerBeanDefinition 方法是在 BeanDefinitionRegistry  接口中声明的,DefaultListableBeanFactory 类实现了 BeanDefinitionRegistry  接口,并实现了该方法,我们来看分析下该方法:

  • 首先对传入的 beanDefinition 对象进行校验,这也是注册前的最后一次校验,不过这个时候 BeanDefinition  对象已经到手了,所以这个校验并非 XML 文件校验,这里主要是对 methodOverrides 的校验。

  • 接下来会根据 beanName 从 beanDefinitionMap 中获取 BeanDefinition,看看当前 Bean  是否已经定义过了。beanDefinitionMap 是一个 Map 集合,这个集合中 key 是 beanName,value 是  BeanDefinition 对象。

  • 如果 BeanDefinition 已经存在了,那么接下来会判断是否允许 BeanDefinition  覆盖,如果不允许,就直接抛出异常(不知道小伙伴们有没有印象,在松哥前面的 OAuth3 系列教程中,经常需要配置允许 BeanDefinition  的覆盖,就是因为这个原因,公众号【江南一点雨】后台回复 OAuth3 获取该教程),如果允许 BeanDefinition 的覆盖,那就向  beanDefinitionMap 中再次存一次值,覆盖之前的值。

  • 如果 BeanDefinition 不存在,那就直接注册。直接注册分两种情况:项目已经运行了和项目还没运行。

  • 如果项目已经运行,由于 beanDefinitionMap 是一个全局变量,可能存在并发问题,所以要加处理。否则就直接注册,所谓的注册就是把对象存入  beanDefinitionMap 中,同时将 beanName 都存入 beanDefinitionNames 集合中。

这便是 registerBeanDefinition 方法的工作流程。

有小伙伴会说,这个方法从头到尾都是 BeanDefinition,跟 Bean 有什么关系呢?

咋一看确实好像和 Bean 没有直接关系。

其实这涉及到另外一个问题,就是 Bean 的懒加载。这个时候先把 BeanDefinition 定义好,等到真正调用 Bean 的时候,才会去初始化  Bean。我们可以在 User 类的构造方法中打印日志看下,如下:

public class User {     private String username;     private String address;      public User() {         System.out.println("--------user init--------");     }      @Override     public String toString() {         return "User{" +                 "username='" + username + '\'' +                 ", address='" + address + '\'' +                 '}';     }      public String getUsername() {         return username;     }      public void setUsername(String username) {         this.username = username;     }      public String getAddress() {         return address;     }      public void setAddress(String address) {         this.address = address;     } }

从下图可以看到,当 BeanDefinition 注册完成后,User 并没有初始化,等到 getBean 方法被调用的时候,User  才初始化了。

怎么理解容器DefaultListableBeanFactory

需要注意的是,我们日常开发中使用的 ApplicationContext 并非懒加载

那么如果不想懒加载该怎么办呢?当然有办法。

4.提前注册 Bean

在 DefaultListableBeanFactory 中还有一个 preInstantiateSingletons 方法可以提前注册  Bean,该方法是在 ConfigurableListableBeanFactory 接口中声明的,DefaultListableBeanFactory  类实现了 ConfigurableListableBeanFactory 接口并实现了接口中的方法:

@Override public void preInstantiateSingletons() throws BeansException {  if (logger.isTraceEnabled()) {   logger.trace("Pre-instantiating singletons in " + this);  }  // Iterate over a copy to allow for init methods which in turn register new bean definitions.  // While this may not be part of the regular factory bootstrap, it does otherwise work fine.  List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);  // Trigger initialization of all non-lazy singleton beans...  for (String beanName : beanNames) {   RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);   if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {    if (isFactoryBean(beanName)) {     Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);     if (bean instanceof FactoryBean) {      final FactoryBean<?> factory = (FactoryBean<?>) bean;      boolean isEagerInit;      if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {       isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)           ((SmartFactoryBean<?>) factory)::isEagerInit,         getAccessControlContext());      }      else {       isEagerInit = (factory instanceof SmartFactoryBean &&         ((SmartFactoryBean<?>) factory).isEagerInit());      }      if (isEagerInit) {       getBean(beanName);      }     }    }    else {     getBean(beanName);    }   }  }  // Trigger post-initialization callback for all applicable beans...  for (String beanName : beanNames) {   Object singletonInstance = getSingleton(beanName);   if (singletonInstance instanceof SmartInitializingSingleton) {    final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;    if (System.getSecurityManager() != null) {     AccessController.doPrivileged((PrivilegedAction<Object>) () -> {      smartSingleton.afterSingletonsInstantiated();      return null;     }, getAccessControlContext());    }    else {     smartSingleton.afterSingletonsInstantiated();    }   }  } }

preInstantiateSingletons 方法的整体逻辑比较简单,就是遍历 beanNames,对符合条件的 Bean  进行实例化,而且大家注意,这里所谓的提前初始化其实就是在我们调用 getBean 方法之前,它自己先调用了一下 getBean。

我们可以在案例中手动调用该方法:

DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory(); GenericBeanDefinition userBeanDefinition = new GenericBeanDefinition(); MutablePropertyValues pvs = new MutablePropertyValues(); pvs.add("username", "javaboy"); pvs.add("address", "www.javaboy.org"); userBeanDefinition.setPropertyValues(pvs); userBeanDefinition.setBeanClass(User.class); defaultListableBeanFactory.registerBeanDefinition("user", userBeanDefinition); defaultListableBeanFactory.preInstantiateSingletons(); User user = defaultListableBeanFactory.getBean(User.class); System.out.println("user = " + user);

此时在调用 getBean 方法之前,User 就已经初始化了,如下图:

怎么理解容器DefaultListableBeanFactory

5.getBean

DefaultListableBeanFactory 中另外一个重量级方法就是 getBean 了。不过 getBean 方法的真正实现是在  DefaultListableBeanFactory 的父类 AbstractBeanFactory 中,具体的实现方法是  doGetBean,本来想和大家子在这里聊一聊这个问题,但是发现这是一个非常庞大的问题,BeanFactory 和 FactoryBean  都还没和大家分享,所以这个话题我们还是暂且押后,一个点一个点来。

到此,关于“怎么理解容器DefaultListableBeanFactory”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

--结束END--

本文标题: 怎么理解容器DefaultListableBeanFactory

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

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

猜你喜欢
  • 怎么理解容器DefaultListableBeanFactory
    这篇文章主要介绍“怎么理解容器DefaultListableBeanFactory”,在日常操作中,相信很多人在怎么理解容器DefaultListableBeanFactory问题上存在疑惑,小编查阅了各式...
    99+
    2024-04-02
  • 如何初始化容器与DefaultListableBeanFactory
    本篇内容介绍了“如何初始化容器与DefaultListableBeanFactory”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家...
    99+
    2024-04-02
  • 怎么理解Linux容器
    这篇文章主要介绍“怎么理解Linux容器”,在日常操作中,相信很多人在怎么理解Linux容器问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么理...
    99+
    2024-04-02
  • css Flex容器怎么理解
    今天小编给大家分享一下css Flex容器怎么理解的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。概念1、使用Flex布局的元...
    99+
    2023-06-30
  • 怎样理解Java 容器
    怎样理解Java 容器,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合...
    99+
    2023-06-05
  • 浏览器兼容性怎么理解
    这篇文章主要介绍“浏览器兼容性怎么理解”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“浏览器兼容性怎么理解”文章能帮助大家解决问题。 一旦为页面设置了恰当的 DTD...
    99+
    2024-04-02
  • 怎么理解Kubernetes容器网络模型
    这篇文章主要讲解了“怎么理解Kubernetes容器网络模型”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么理解Kubernetes容器网络模型”吧!&n...
    99+
    2024-04-02
  • HBase的容错性怎么理解
    这篇文章主要讲解了“HBase的容错性怎么理解”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“HBase的容错性怎么理解”吧!Master容错:zookeeper重新选择一个新的Master–...
    99+
    2023-06-03
  • 怎么理解Kubernetes容器编排的构建块
    本篇内容主要讲解“怎么理解Kubernetes容器编排的构建块”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么理解Kubernetes容器编排的构建块”吧!容...
    99+
    2024-04-02
  • Linux容器和Kubernetes的云服务怎么理解
    这篇文章主要讲解了“Linux容器和Kubernetes的云服务怎么理解”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Linux容器和Kubernetes的云服务怎么理解”吧!一、云化之旅从...
    99+
    2023-06-16
  • WPF布局及布局容器该怎么理解
    本篇文章为大家展示了WPF布局及布局容器该怎么理解,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。WPF布局基础WPF布局原则一个窗口中只能包含一个元素不应显示设置元素尺寸不应使用坐标设置元素的位置可...
    99+
    2023-06-29
  • 怎么理解List的扩容机制
    这篇文章主要讲解了“怎么理解List的扩容机制”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么理解List的扩容机制”吧!一:背景 讲故事在前一篇大内存排查中,我们看到了Dictionar...
    99+
    2023-06-01
  • hashmap的扩容机制怎么理解
    今天小编给大家分享一下hashmap的扩容机制怎么理解的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。hashmap的扩容机制...
    99+
    2023-07-05
  • docker怎么清理容器缓存
    要清理Docker容器缓存,可以使用以下命令: 停止并删除所有Docker容器: docker stop $(docker ps...
    99+
    2024-02-29
    docker
  • Docker容器怎么连接代理Wormhole
    这篇文章主要讲解了“Docker容器怎么连接代理Wormhole”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Docker容器怎么连接代理Wormhole”吧!Wormhole 是一个能识别...
    99+
    2023-06-27
  • 怎么理解IE6 IE7 IE8浏览器的兼容性对比
    本篇文章为大家展示了怎么理解IE6 IE7 IE8浏览器的兼容性对比,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。你对IE6IE7IE8浏览器的兼容性是否比较熟悉,...
    99+
    2024-04-02
  • 怎么理解TiDB兼容MySQL参数优化
    本篇内容介绍了“怎么理解TiDB兼容MySQL参数优化”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!默认 ...
    99+
    2024-04-02
  • java中容器(顶层容器和中间容器)的布局管理器详解
    目录java容器(顶层容器和中间容器)的布局管理器一、布局管理器所属类包二、容器的默认布局管理器java常用的四大容器总结一、为什么要使用容器(集合类)?二、Java中四大容器的简介...
    99+
    2024-04-02
  • 容器化 | ClickHouse Operator 原理解析
    作者:苏厚镇 青云科技数据库研究工程师 从事 RadonDB ClickHouse 相关工作,热衷于研究数据库内核。 通过《ClickHouse on K8s 部署篇》,对比了 RadonDB ClickHouse 集群在 Kuber...
    99+
    2016-01-21
    容器化 | ClickHouse Operator 原理解析
  • Docker容器日志怎么收集和管理
    Docker容器日志的收集和管理是非常重要的,可以帮助我们监控容器的运行状态、排查问题和进行性能分析。以下是一些常用的方法来收集和管...
    99+
    2024-04-02
软考高级职称资格查询
推荐阅读
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作