Spring IoC容器(基础知识点+XML配置)

编程入门 行业动态 更新时间:2024-10-13 20:16:43

Spring IoC容器(基础<a href=https://www.elefans.com/category/jswz/34/1770093.html style=知识点+XML配置)"/>

Spring IoC容器(基础知识点+XML配置)

Spring IoC容器(XML配置)

  • 一、相关概念
    • 1.1 IoC和DI
    • 1.2 JavaBean、POJO对象
  • 二、Spring容器初始化
    • 2.1 引入依赖
    • 2.2 创建Spring配置文件
    • 2.3 ApplicationContext初始化方式
    • 2.4 测试
  • 三、Bean标签和管理对象
    • 3.1 Bean标签
    • 3.2 实例化Bean的方式
    • 3.3 Bean标签总结
  • 四、依赖注入
    • 4.1 构造函数注入
    • 4.2 set注入
    • 4.3 c,p 名称空间注入
    • 4.4 数组类型依赖注入
    • 4.5 集合类型依赖注入
      • 4.5.1 List集合注入
      • 4.5.2 Map集合注入
      • 4.5.3 Properties集合注入
  • 五、XML配置进阶
    • 5.1 多XML配置文件
    • 5.2 BeanFactoryPostProcessor
    • 5.3 自动装配
      • 5.3.1 全局设定
      • 5.3.2 个别设定

参考/摘录书籍:Spring+Spring MVC+MyBatis整合开发 著○陈学明
参考图片:来自网络
说明:本人相关博客仅供学习参考!

一、相关概念

1.1 IoC和DI

   IoC(Inversion of Control,控制反转) 容器是Spring最核心的概念和内容。它替代了传统的new方式初始化对象,通过读取在XML文件中配置的Bean定义,自动创建并管理容器的Bean实例及生命周期
控制权的转移即所谓的反转,依赖对象创建的控制权从应用程序本身转移到外部容器。控制反转的实现策略一般有两种:

  • 依赖查找(Dependency Lookup)
  • 依赖注入(Dependency Injection):依赖对象通过注入进行创建。由容器负责组件的创建和注入,这是更为流行的IoC实现策略,根据配置将符合依赖关系的对象传递给需要的对象。

IoC是一种软件设计思想,DI是这种思想的一种实现。控制反转乃至依赖注入的目的不是提升系统性能,而是提升组件重用性和系统的可维护性。
Spring IoC容器用来创建和管理类的实例称为Bean。

1.2 JavaBean、POJO对象

  JavaBean是JCP定义的一种Java类的标准,包括属性、方法和事件三方面的规范。

  • public修饰的类
  • 该类含有无参构造函数
  • 该类需要被序列化且实现Serializable接口
  • 可有一些列的读写属性(getter或setter方法)

1.public修饰是为了提供给其他类使用。
2.无参构造器是为了让框架和容器可以通过反射机制来进行实例化。
3.实现Serializable接口是为了可以序列化和反序列化,以便进行对象的传输或保存。

  POJO(简单Java对象)

  • 没有继承任何类
  • 没有实现任何接口

二、Spring容器初始化

  Spring IoC容器用来创建和管理类的实例称为Bean。本剧Bean的配置,使用Bean工厂(BeanFactory接口实现类的对象)创建和管理Bean实例。除了创建和管理Bean实例,Spring容器最重要的作用是根据配置注入依赖对象
  BeanFactoryApplicationContext是Spring进行对象管理的两个主要接口。
基于Spring框架的应用在启动时会根据配置创建一个实现BeanFactory接口的类对象,这个对象就是所谓的容器。
  ApplicationContext是IoC容器的另一个重要接口,被称为应用上下文,它继承自BeanFactory,包好了BeanFactory的所有功能,同时还提供了一些新的高级功能。如

  • MessageSource(国际化资源接口),用于信息的国际化显示。
  • ResourceLoader(资源加载接口),用于资源加载。
  • ApplicationEventPublisher(应用时间发布接口),用于应用事件的处理。

BeanFactory要在代码里写出来才可以被容器识别,而ApplicationContext是直接配置在配置文件即可。

2.1 引入依赖

<!-- 引入Spring依赖 -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.6.RELEASE</version>
</dependency>

2.2 创建Spring配置文件

  在类路径下创建一个任意名称的xml 文件。【这里名称为:beans.xml】

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns=""xmlns:xsi=""xsi:schemaLocation="://www.springframework/schema/beans/spring-beans.xsd">
<!-- xmlns 是XML Namespace的缩写,即XML命名空间 -->
<!-- xmlns:xsi 表示使用xsi作为前缀的命名空间,这个属性是为了下面的xsi:schemaLocation的定义 -->
<!-- xsi:schemaLocation 用于定义命名空间和对应的XSD(XML结构定义)文档的文职关系,两者是成对匹配的 --><!-- 从Spring2.5开始支持基于注解的配置;从Spring3.0开始支持直接使用Java代码进行配置; -->
<!-- 一般将service、dao、pojo层的对象交由容器管控 --><!-- 使用bean标签来配置一个Bean对象,spring会把此对象存到IoC容器中【默认情况下,容器根据每个Bean的配置创建和维护一个单例对象】id :给这个类的对象一个表示,以方便容器和程序查找和使用【如果不设定id的属性和值,则容器默认会以类名的首字母小写作为标识】class :类的全限定名--><bean id="userDao" class="combx.dao.impl.UserDaoImpl"></bean><bean id="userService" class="combx.service.impl.UserServiceImpl"></bean>
</beans>

2.3 ApplicationContext初始化方式

  在Spring中,使用“classpath:”来表示类的根路径;“classpath*:”来表示出了自身的类路径之外,还会查找依赖库(.jar)下的目录。
  类路径 是指编译后的class文件的目录,在web项目中是WEB-INF/classes目录。


此时使用ac.getBean(“userDao”);即可获得容器中创建的实例对象;

(1) 配置文件位于项目的类的根路径下
//方式1:使用类路径应用上下文初始化类
ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext.xml");
//方式2:使用文件路径应用上下文初始化类
ApplicationContext context= new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
(2) 配置文件位于项目的根路径下
ApplicationContext context= new FileSystemXmlApplicationContext("applicationContext.xml");

2.4 测试

//从spring容器中获取需要的对象
// 1.加载类路径下的spring的xml配置文件,获取Spring容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
//2.从容器中获取对象,根据名称来获取
UserDao userDao = (UserDao) ctx.getBean("userDao");
System.out.println(userDao);
UserService userService = (UserService) ctx.getBean("userService");
System.out.println(userService);

三、Bean标签和管理对象

3.1 Bean标签

  使用类的名字在XML中配置Bean,并给这个Bean设置一个标识,容器基于配置来创建相应的类的对象,之后就可以通过标识(也可以通过类)从容器中获取该对象,实现所谓的容器对Bean的托管。
作用:用于配置让 spring来创建对象的。
默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。

属性:

  • id: 给对象在容器中提供一个唯一标识。用于获取对象。
  • class: 指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
  • scope: 指定Bean所托管的对象的作用域范围。【Bean的作用域配置在Spring中指定的是当前配置创建的Bean相对于其他Bean的可见范围
    1、singleton :默认值,单例作用域。【Spring IoC容器只创建和维护一个该类型的Bean实例,并将这个实例存储到单例缓存(singleton cache)中,针对该Bean的请求和引用,使用的都是同一个实例。只要容器没有退出或者销毁,该类型的单一实例就会一直存活。
    2、prototype :原型作用域。【原型作用域的Bean在使用容器的getBean( )方法获取的时候,每次得到的都是一个新的对象,作为依赖对象注入到其它Bean的时候也会产生一个新的类对象。(容器不负责原型作用域Bean实例的完整声明周期,不需要的时候需要手动释放)
    3、request :请求作用域。针对每次HTTP请求,Spring都会创建一个Bean实例。WEB项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中。
    4、session :会话作用域。使用于HTTP Session,同一个Session共享同一个Bean实例。WEB项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中。
    5、application :应用作用域。整个Web应用,也就是在ServletContext生命周期中使用一个Bean实例。
    6、websocket:websocket作用域的配置是在一个WebSocket连接的生命周期中共用一个Bean实例。
  • parent:该属性可以从父Bean继承配置数据。
情景1:
<!-- Spring支持Bean的继承,Bean的继承用于Bean实例之间的参数值的延续; -->
<!-- 父bean包含name属性 -->
<bean id="parentBean" class="combx.pojo.ParentBean"><property name="name" value="张三" />
</bean>
<!-- 使用继承配置,子Bean的name的值从父Bean继承 -->
<bean id="childBean" class="combx.pojo.ChildBean" parent="parentBean"><property name="age" value="18" />
</bean>
<!-- 此时,childBean实例的属性值就包含了子Bean和父Bean的所有依赖注入(name和age) -->情景2:
<!-- 1.如果父Bean仅仅是作为一个配置末班的话,则可以设置父Bean的abstract属性为true,容器就不会实例化这个Bean。2.作为模板的父Bean可以不指定class属性,但需要指定abstract="true" ;3.如果父Bean制定了class属性,子Bean可以不指定class属性,此时子Bean使用的就是父Bean的class属性;-->
<bean id="parentBean" abstract="true"><!--抽象父bean作为属性值模板--><property name="name" value="张三" />
</bean>
  • init-method: 指定Bean在初始化时回调的方法【该托管的类中的方法】。
  • destroy-method: 指定Bean在初始化时回调的方法【该托的管类中的方法】。
  • lazy-init:设置为true表示获取对象时才去创建对象(延迟加载)【默认为false:主动加载】
<!-- 全局懒加载【不推荐】(在配置文件根元素<beans>中设置属性default-lazy-init的值) -->
<beans default-lazy-init="true" ></beans><!-- 个别懒加载【推荐】 -->
<beans id="helloService" class="combx.service.HelloService" lazy-init="true" />
<!--lazy-init的取值可有3种:default : 从default-lazy-init继承;true : 懒加载;false : 非懒加载;【不写lazy-init属性也是非懒加载】【注意】1.对于prototype作用域的Bean,lazy-init的配置无效,始终是懒加载!2.懒加载Bean不会在容器初始化的时候就创建,而是使用getBean()方法获取该Bean实例时创建!3.如果某个懒加载Bean作为依赖注入其它非懒加载的Bean时,此时容器在初始化的时候就会创建该懒加载Bean的实例!
-->

3.2 实例化Bean的方式

第一种方式:使用无参构造方法【重点掌握】

//Spring创建对象的时候默认都是调用类的无参构造器
public UserDaoImpl(){//当我们调用getBean()获取容器中的对象时该构造方法被调用System.out.println("UserDaoImpl对象创建了!");
}

第二种方式:使用静态工厂的方法创建对象

//第一步:创建静态工厂的类
public class StaticFactoryService{//声明静态变量private static StaticFactoryService service = new StaticFactoryService();//静态方法,返回该类的静态实例public static StaticFactoryService getInstance(){return service;}
}

使用Spring配置时,除了需要制定Bean的id和class属性之外,还要使用factory-method属性制定获取静态对象的方法,配置如下:

<!-- 静态方法的Bean定义 -->
<bean id="staticFactoryService" class="combx.utils.StaticFactoryService" factory-method="getInstance" />

第三种方式:使用实例工厂的方法创建对象

//普通的JavaBean类
public class Foo {
}
//含有工厂方法的类
public class InstanceFactory {private static Foo foo = new Foo();//工厂方法【获取目标类的实例化对象】public Foo getFooInstance(){return foo;}
}
<!-- 配置文件中需要配置工厂类的Bean和目标类的Bean -->
<!--目标对象的Bean-->
<bean id="instanceFactory" class="combx.factory.InstanceFactory" />
<!--工厂类的Bean【使用工厂类的方法实现目标类的Bean】-->
<bean id="foo" factory-bean="instanceFactory" factory-method="getFooInstance" />

在现实情况中,工厂类一般用来对多个不同的目标类实例化,对应不同的实例化方法,相应的只需要增加对应的配置(bean标签)即可。

3.3 Bean标签总结

分类属性名说明
属性配置idBean标识,默认类名首字母小写
classBean对应的Java类,一般情况下是必要的,实例工厂方式不需要
scope作用域配置,默认为 singleton
autowire自动装配
parent定义继承
lazy-init懒加载
init-method初始化方法
destroy-method销毁方法
factory-bean实例工厂配置使用
factory-method静态工厂和实例工厂配置使用
元素property属性依赖注入name、ref 和 value
constructor-arg构造器依赖注入name、ref 和 value
replaced-method方法替换

四、依赖注入

4.1 构造函数注入

  顾名思义,就是使用类中的带参构造函数,给成员变量赋值。赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入。

无参构造函数注入【默认】:

注意:
  如果显示声明了有参构造器的话,无参构造器会被覆盖,此时需要显式声明无参构造函数。否则依赖无参构造函数注入的Bean就会报错!

有参构造函数注入

< constructor-arg >标签

属性描述
name构造函数中对应参数的名称
type构造函数中对应参数的数据类型
index指定参数在构造函数参数列表的索引位置(从0开始)
value给指定参数赋值(它能赋值的类型为:基本数据类型,String类型)
ref引用其它的bean类型,作为参数的值(所引用的bean必须是在配置文件中配置了的)

4.2 set注入

  设置值注入使用的是属性的setter方法来注入依赖对象。【依赖无参构造函数】

< property >标签

属性描述
name是setXxx方法中的xxx【调用xxx属性的set方法实现注入】
value给相应的属性赋的简单类型的值
ref引用其它的bean类型,作为参数的值(所引用的bean必须是在配置文件中配置了的)

测试:

案例:
  简单类型作为依赖进行注入,在实际项目中可以用来做环境参数设置的配置类,或者做第三方类的配置信息的输入,类似于数据库连接的访问;
  为提高系统的灵活性和安全性,value的值不直接设置在配置中,而是使用占位符(也可以理解为变量);

	<!-- 加载关于数据库连接的properties属性文件【引入多个配置文件时在location属性中逗号隔开即可】 --><context:property-placeholder location="classpath:db.properties,classpath:otherdb.properties"/><!-- 【配置Druid数据源】Spring容器加载时立即创建 DruidDataSource 的实例 --><bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url" value="${jdbc.url}"/><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean>

  在类路径下配置 db.properties属性文件

#使用二级名称,避免SEL获取key时出错【例如:${jdbc.username}】
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db?useSSL=false&serverTimezone=GMT%2B8
jdbc.username=root
jdbc.password=123456

  使用< context >标签要引入context名称空间

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns=""xmlns:xsi=""xmlns:context=""xsi:schemaLocation="://www.springframework/schema/beans/spring-beans.xsd://www.springframework/schema/context/spring-context.xsd
">
</beans>

4.3 c,p 名称空间注入

  使用c、p命名空间可以分别对构造器注入(constructor-arg)和属性注入(property)进行简写,即从定义子元素换成定义属性的方式。步骤如下:

首先需要将<beans>根元素加入响应的命名空间;

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns=""xmlns:xsi=""xmlns:p=""xmlns:p=""xsi:schemaLocation="://www.springframework/schema/beans/spring-beans.xsd">
<!-- 【注】xsi:schemaLocation不需要加入文档结构的定义,因为这还是属于Bean元素的定义。 --></beans>

  c命名空间注入

  【只需要在<bean>增加:c:构造参数名=“值”

<!-- 简单类型的构造器注入 -->
<bean id="bookBean10" class="combx.pojo.Book" c:name="《c名称空间注入》" c:parent-ref="currentDate" p:price="16.5" lazy-init="true"/>

如果注入的依赖是对象类型,则在参数名后加上-ref即可,值对应的就是Bean的id[c、p命名空间可以混用]

  p命名空间注入

  此种方式是通过在 xml 中导入 p 名称空间,使用 p:propertyName 来注入数据,它的本质仍然是调用类中的set 方法实现注入功能。

<!-- 导入了p名称空间后,可直接用p:xxx=yyy方式或者p:xxx-ref=yyy方式来注入 -->
<bean id="bookBeanByPSpace" class="combx.pojo.Book"p:id="2" p:name="《p名称空间注入》" p:price="10.0" p:publishDate-ref="currentDate" lazy-init="true"/>

4.4 数组类型依赖注入

  类中的集合和数组成员传值,它用的也是基于set方法注入。

	<bean id="bookBean6" class="combx.pojo.Book" lazy-init="true"><property name="id" value="20"/><property name="name" value="《Map集合注入》"/><property name="bookId"><!-- 使用array配置一个数组,注入给类里的数组属性 --><array><value>100</value><value>200</value><value>300</value></array></property></bean>

4.5 集合类型依赖注入

  类中的集合和数组成员传值,它用的也是基于set方法注入。

4.5.1 List集合注入

测试代码:
//要从spring容器中获取需要的对象,先要获取Spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");//List集合注入测试
@Test
public void test6(){Book bookBean5 = ac.getBean("bookBean5", Book.class);lg.debug("List集合注入创建Book实例:"+bookBean5);List<String> bookNameList = bookBean5.getBookNameList();for (String n : bookNameList) {lg.debug(n);}
}

注意:
  如果List集合中的元素是其它Bean类型(除了简单类型以外的),该如何注入这种复杂Bean类型元素的List集合?
  在beans.xml中配置List集合,集合中需要添加元素,可以用<bean>标签或<ref>标签添加复杂Bean类型元素;

	<bean id="bookBean6" class="combx.pojo.Book" lazy-init="true"><property name="id" value="20"/><property name="name" value="《Map集合注入》"/><property name="addressList"><list><!-- 使用ref标签向集合中注入一个Bean对象 --><ref bean="address1" /><!-- 使用bean标签向集合中注入一个Bean对象 --><bean id="addresds2" class="com.java.pojo.Address"><property name="name" value="湖北" /></bean></list></property></bean>

4.5.2 Map集合注入


测试代码:

//map集合注入测试
@Test
public void test5(){
//从spring容器中获取需要的对象
//步骤1:加载类路径下的spring的xml配置文件,获取Spring容器ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");Book bookBean4 = ac.getBean("bookBean4", Book.class);lg.debug("Map集合注入创建Book实例:"+bookBean4);Map<Integer, String> bookIdMap = bookBean4.getBookIdMap();Set<Map.Entry<Integer, String>> entries = bookIdMap.entrySet();for (Map.Entry<Integer, String> entry : entries) {lg.debug("id:"+entry.getKey()+",bookName:"+entry.getValue());}
}

4.5.3 Properties集合注入

  <props>对应的是java.util.Properties的对象,用来配置字符串类型的键和值的属性,可以看成是对<map>的简化。使用<prop>子元素的key指定键,子元素的内容为值

	<bean id="bookBean7" class="combx.pojo.Book" lazy-init="true"><property name="animals"><!-- 使用props标签来配置一个Properies集合,里面每个prop代表键值对,即给Properies集合注入值 --><props><!-- prop配置键值对 --><prop key="id">111</prop><prop key="name">大象</prop><prop key="age">52</prop></props></property></bean>


测试代码:

	@Testpublic void test07(){Book book = (Book) ac.getBean("bookBean7");Properties prop = book.getAnimals();//返回此哈希表中键的枚举Enumeration keys = prop.keys();//遍历key的枚举while(keys.hasMoreElements()){String key =(String) keys.nextElement();String value = prop.getProperty(key);System.out.println(("key:"+key+",value:"+value));}}

五、XML配置进阶

5.1 多XML配置文件

  使用import导入拆分的配置:

<!-- 主配置的根目录 -->
<beans><!-- 导入服务类的配置 --><import resource="services.xml" /><!-- 导入消息的配置 --><import resource="resources/messageSource.xml" /><!-- 其它Bean的配置 --><bean id="bean1" class="..." /><bean id="bean2" class="..." />
</beans>

import 也常被用来导入第三方包提供的配置。

5.2 BeanFactoryPostProcessor

  BeanFactoryPostProcessor是在容器初始化之后Bean实例化之前,在容器加载Bean 的定义阶段执行,此扩展点可以对Bean配置的元数据读取和修改,比如Bean的scope、lazy-init属性和依赖注入对象等。
  PropertyPlaceholderConfigure,这个类继承自BeanFactoryPostProcessor,其作用是对所有的Bean定义的占位符替换成对应的值。

案例如下:
将数据库连接信息配置到db.properties属性文件中;

#使用二级名称,避免SEL获取key时出错【例如:${jdbc.username}】
#数据库驱动
jdbc.driverClassName=com.mysql.jdbc.Driver
#MySQL数据源地址
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db?useSSL=false&serverTimezone=GMT%2B8
#数据库用户名
jdbc.username=root
#密码
jdbc.password=123456

在Spring配置文件beans.xml中增加如下配置:

<!-- 占位符替换Bean定义,使用properties指定属性文件 --><bean id="dbConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" ><property name="properties" value="classpath:db.properties" /></bean><!-- 【配置Druid数据源】Spring容器加载时立即创建 DruidDataSource 的实例 --><bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><!-- ${ }占位符 --><property name="url" value="${jdbc.url}"/><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean>

【占位符的变量替换成值的步骤】

  1. 配置PropertyPlaceholderConfigurer的Bean,依赖注入属性文件之后,在其他Bean的定义中就可以使用${变量名}这样的占位符注入简单类型。
  2. 每个Bean初始化之前,会对每个Bean的定义做一次调整和修改,PropertyPlaceholderConfigurer将对应的变量替换成实际的值。这样在Bean实例化的时候使用的就是替换后的值。

5.3 自动装配

  使用构造器和属性注入的依赖项,需要在<bean>元素下手动配置需要注入的对象。Spring也提供了自动装配(autowire)的配置,可以省去依赖注入的配置。
【实际开发中经常使用@Autowired注解进行依赖的装配】

5.3.1 全局设定

  在beans根元素设置default-autowire的值可以开启整个应用中配置Bean的依赖自动注入,容器会根据default-autowire设置的匹配类型自动查找符合的Bean实例进行注入。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns=""xmlns:xsi=""xsi:schemaLocation="://www.springframework/schema/beans/spring-beans.xsd"default-autowire="byName" >
<!-- default-autowire="值";该“值”有3种;通过这三种方式来查找依赖对象。byName : 根据Bean的标识(id、name和别名)byType : 类的类型constructor : 构造器中参数类型;--></beans>

全局设定对所有Bean会自动生效,如果希望某个Bean不作为依赖被其它Bean使用的话,可以在该Bean上设置autowire-candidate的属性值为false。

5.3.2 个别设定

  设置<bean>的autowire来指定依赖自动装配的方式,autowire可设置的值和全局的设定的值是相同的。

<bean id="bookBean" class="combx.pojo.Book" autowire="byName" />

参考详细自动装配(相关博客)

更多推荐

Spring IoC容器(基础知识点+XML配置)

本文发布于:2024-03-23 20:27:08,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1742466.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:知识点   容器   基础   Spring   IoC

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!