Spring(一)之IOC、bean、注入

spring简介

  首先它是一个开源的、用来简化企业级应用开发的框架。

  Spring为编写企业应用程序提供了轻量的解决方案,同时仍然支持使用声明式事务、 用RMI或web service远程调用、以及使用多种方式来将数据持久化到数据库。Spring提供了全功能的 MVC framework, 以及透明集成AOP到你的软件中的能力。

  Spring可能是你的企业应用程序所需要的一站式解决方案, 但Spring仍然是模块化的,允许你只使用你所需的哪些部分,而无需附加上其他部分。 你可以使用 IoC容器,在其上使用Struts,但是你也可以选择使用 Hibernate 整合代码或者 JDBC 抽象层。 我们将Spring设计为非侵入式的(并且以后也是如此),这意味着应用基本上不需要依赖框架本身 (或者肯定是最小的,取决于所使用的部分)。

spring的优势:

  本质是管理软件中的对象和维护对性之间的关系

  • 简化

      spring对很多常用的api做了封装(比如,使用spring

      jdbc来访问数据库,就不再需要获得连接与关闭连接)。
  • 解耦

    spring可以帮我们管理对象及对象之间的关系,这样一来,

    软件的可维护性会大大提高。
  • 集成

    spring可以将其它的一些框架(比如quartz)集成进来。

概览

  Spring框架包含许多特性,并被很好地组织在下图所示的六个模块中。

Spring(一)之IOC、bean、注入

  Core 封装包是框架的最基础部分,提供IoC和依赖注入特性。这里的基础概念是BeanFactory,它提供对Factory模式的经典实现来消除对程序性单例模式的需要,并真正地允许你从程序逻辑中分离出依赖关系和配置。

  Context(上下文) 封装包构筑于Core封装包的坚固基础上:它提供了用一种框架风格的方式来访问对象,有些像JNDI注册表。Context封装包继承了beans包的功能,还增加了国际化(I18N)(用于规范resource bundle),事件传播,资源装载,以及透明创建上下文,例如通过servlet容器。

  DAO 提供了JDBC的抽象层,它可消除冗长的JDBC编码和解析数据库厂商特有的错误代码。 并且,JDBC 封装包还提供了一种比编程性更好的声明性事务管理方法,不仅仅是实现了特定接口,而且对所有的POJOs(plain old Java objects)都适用。

  ORM 封装包提供了常用的“对象/关系”映射APIs的集成层。 其中包括JPA、JDO、Hibernate 和 iBatis 。利用ORM封装包,可以混合使用所有Spring提供的特性进行“对象/关系”映射,如前边提到的简单声明性事务管理

  Spring的 AOP 封装包提供了符合 AOP Alliance规范的面向方面的编程(aspect-oriented programming)实现,让你可以定义,例如方法拦截器(method-interceptors)和切点(pointcuts),从逻辑上讲,从而减弱代码的功能耦合,清晰的被分离开。而且,利用source-level的元数据功能,还可以将各种行为信息合并到你的代码中,这有点象.Net的attribute的概念。

  Spring中的 Web 包提供了基础的针对Web开发的集成特性,例如多方文件上传,利用Servlet listeners进行IoC容器初始化和针对Web的application context。当与WebWork或Struts一起使用Spring时,这个包使Spring可与其他框架结合。

  Spring中的 MVC 封装包提供了Web应用的Model-View-Controller(MVC)实现。Spring的MVC框架并不是仅仅提供一种传统的实现,它提供了一种 清晰的 分离模型,在领域模型代码和web form之间。并且,还可以借助Spring框架的其他特性。

典型的完整Spring Web应用的模式图

Spring(一)之IOC、bean、注入

  通过用Spring的 声明事务管理特性,Web应用可以做到完全事务性,就像使用EJB提供的那种容器管理的事务一样。 所有自定义的业务逻辑可以通过简单的POJO来实现,并利用Spring的IoC容器进行管理。对于其他的服务,比如发送email和不依赖web层的校验信息,还可以让你自己决定在哪里执行校验规则。 Spring本身的ORM支持可以和JPA、Hibernate、JDO以及iBatis集成起来,例如使用Hibernate,你可复用已经存在的映射文件与标准的Hibernate SessionFactory 配置。用控制器去无缝整合web层和领域模型,消除对 ActionForms 的依赖,或者避免了其他class为领域模型转换HTTP参数的需要。

IoC(控制反转)容器

简介

 org.springframework.beansorg.springframework.context包 是Spring IoC容器的基础。**BeanFactory **提供的高级配置机制,使得管理各种对象成为可能。 ApplicationContext 是BeanFactory的扩展,功能得到了进一步增强,比如更易 与Spring AOP集成、资源处理(国际化处理)、事件传递及各种不同应用层的context实现 (如针对web应用的WebApplicationContext)。

  简而言之,BeanFactory提供了配制框架及基本功能,而 ApplicationContext 则增加了更多支持企业核心内容的功能。 ApplicationContext完全由BeanFactory扩展而来, 因而BeanFactory所具备的能力和行为也适用于ApplicationContext。

  一般把ApplicationContext接 口作为首选。

基本原理 - 容器和bean

bean

  在Spring中,那些组成你应用程序的主体(backbone)及由Spring IoC容器所管理的对象,被称之为bean。 简单地讲,bean就是由Spring容器初始化、装配及管理的对象,除此之外,bean就与应用程序中的其他对象没有什么区别了。 而bean定义以及bean相互间的依赖关系将通过配置元数据来描述。

  bean:bean具有以下几个特征

  •    是一个public类
  •    有包
  •    可以序列化
  •    有无参构造器
  •    有getset方法

注意:在spring中Java类和javaBean都被当做bean

容器

  org.springframework.beans.factory.BeanFactory 是Spring IoC容器的实际代表者,IoC容器负责容纳此前所描述的bean,并对bean进行管理。

  在Spring中,BeanFactory是IoC容器的核心接口。 它的职责包括:实例化定位配置应用程序中的对象及建立这些对象间的依赖

  Spring为我们提供了许多易用的BeanFactory实现, XmlBeanFactory就是最常用的一个。该实现将以XML方式描述组成应用的对象 以及对象间的依赖关系。XmlBeanFactory类将获取此XML配 置元数据,并用它来构建一个完全可配置的系统或应用。

配置元数据(重点)

  pring IoC容器将读取配置元数据; 并通过它对应用中各个对象进行实例化、配置以及组装。通常情况下我们使用简单直观 的XML来作为配置元数据的描述格式。在XML配置元数据中我们可以对那些我们希望通过 Spring IoC容器管理的bean进行定义。

注意:XML的元数据是最常用到的配置元数据格式。然而,它并 不是唯一的描述格式

  Spring IoC容器至少包含一个bean定义,但大多数情况下会有多个bean定义。当使用 基于XML的配置元数据时,将在顶层的元素中配置一个 或多个元素。

  bean定义与应用程序中实际使用的对象一一对应。通常情况下bean的定义包括:服务 层对象数据访问层对象(DAO)、类似Struts Action的 表示层对象Hibernate SessionFactory对象JMS Queue对象等等。通常bean的定义并不与容器中的领域 对象相同,因为领域对象的创建和加载必须依赖具体的DAO和业务逻辑。

基于XML配置配置元数据

以下是一个基于XML的配置元数据的基本结构:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean> <bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean> <!-- more bean definitions go here --> </beans>
实例化容器
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] {"services.xml", "daos.xml"}); // an ApplicationContext is also a BeanFactory (via inheritance)
BeanFactory factory = context;
  • XML配置元数据的结构

  将XML配置文件分拆成多个部分是非常有用的。为了加载多个XML文件生成一个 ApplicationContext实例,可以将文件路径作为字符串数组传给ApplicationContext构造器 。而bean factory将通过调用bean defintion reader从多个文件中读取bean定义

  通常情况下,Spring团队倾向于上述做法,因为这样各个配置并不会查觉到它们 与其他配置文件的组合。

  另外一种方法是使用一个或多个的<import/>元素 来从另外一个或多个文件加载bean定义。所有的元素必 须在<bean/>元素之前完成bean定义的导入。

  例子:

<beans>

 <import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/> <bean id="bean1" class="..."/>
<bean id="bean2" class="..."/> </beans>

   在上面的例子中,从3个外部文件:services.xmlmessageSource.xmlthemeSource.xml 来加载bean定义。这里采用的都是相对路径,因此,此例中的services.xml 一定要与导入文件放在同一目录或类路径,而messageSource.xm l和themeSource.xml的文件位置必须放在导入文件所 在目录下的resources目录中。正如你所看到的那样,开头的斜杠 ‘/’实际上可忽略。因此不用斜杠‘/’可能会更好一点。根据Spring XML配置文件的 Schema(或DTD),被导入文件必须是完全有效的XML bean定义文件,且根节点必须为 <beans/> 元素。

bean

多种bean

  Spring IoC容器将管理一个或多个bean,这些bean 将通过配置文件中的bean定义被创建(在XML格式中为 元素)。

  在容器内部,这些bean定义由BeanDefinition 对象来表示,该定义将包含以下信息:

  • 全限定类名:这通常就是已定义bean的实际实现类。

  • bean行为的定义,这些定义将决定bean在容器中的行为(作用域、生命周期回调等等)

  • 对其他bean的引用,这些引用bean也可以称之为协作bean(collaborators) 或依赖bean(dependencies).

  • 创建bean实例时的其他配置设置。比如使用bean来定义连接池,可以通过属性或者构造参数指定连接数,以及连接池大小限制等。

    上述内容直接被翻译为每个bean定义包含的一组properties

bean定义

名称 链接内容
id 唯一标示
class 实例化bean
name bean的命名
alias bean的别名
scope bean的作用域
constructor arguments 注入依赖
properties 注入依赖
autowiring mode 自动装配(autowire)的协作者
dependency checking mode 依赖检查
lazy-initization method 延迟初始化bean
initialization 初始化回调
desctruction method 析构回调

  除了通过bean定义来描述要创建的指定bean的属性之外,某些 BeanFactory的实现也允许将那些非BeanFactory创建的、已有的用户 对象注册到容器中,比如使用DefaultListableBeanFactory 的registerSingleton(..) 方法。不过大多数应用还是采用 元数据定义为主。

bean的命名

  bean通过id或 name属性来指定bean标识符。

  每个bean都有一个或多个id(或称之为标识符或名称,在术语 上可以理解成一回事)。这些id在当前IoC容器中必须唯一。如果 一个bean有多个id,那么其他的id在本质上将被认为是别名

 bean命名约定

  bean的命名采用标准的Java命名约定,即小写字母开头,首字母大写间隔的命名方式。

  为一个bean提供一个name并不是必须的,如果没有指定,那么容 器将为其生成一个惟一的name。

bean的别名

  为了让应用的每一个组件能更容易的对公共组件进行引用,我们通常使用别名,可以通过id或者alias属性起别名

<alias id="a1" name="fromName" alias="toName"/>

实例化bean

  从本质上来说,bean定义描述了如何创建一个或多个对象实例。当需要的时候, 容器会从bean定义列表中取得一个指定的bean定义,并根据bean定义里面的配置元数据 使用反射机制来创建(或取得)一个实际的对象。

  当采用XML描述配置元数据时,将通过元素的 class属性来指定实例化对象的类型。

  • 用构造器来实例化

      利用无参构造器创建一个bean实例。

      当采用构造器来创建bean实例时,Spring对class并没有特殊的要求, 我们通常使用的class都适用。也就是说,被创建的类并不需要实现任何特定的 接口,或以特定的方式编码,只要指定bean的class属性即可。不过根据所采用 的IoC类型,class可能需要一个默认的空构造器。

  基于XML的元数据配置文件,可以这样来指定bean类:

<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
  • 使用静态工厂方法实例化

  当采用静态工厂方法创建bean时,除了需要指定class 属性外,还需要通过factory-method属性来指定创建bean实例 的工厂方法。Spring将调用此方法(其可选参数接下来介绍)返回实例对象,就此而言, 跟通过普通构造器创建类实例没什么两样。

  下面的bean定义展示了如何通过工厂方法来创建bean实例。

 注意factory-method属性:指定要调用静态方法

<bean id="exampleBean"
class="examples.ExampleBean2"
factory-method="createInstance"/>

此定义并未指定返回对象的类型,仅指定该类包含的工厂方法。在此例中, createInstance()必须是一个static方法。

  • 使用实例工厂方法实例化

  使用静态工厂方法实例化类似,用来进行实例化的非静态实例工厂方法位 于另外一个bean中,容器将调用该bean的工厂方法来创建一个新的bean实例。为使 用此机制,class属性必须为空,而factory-bean 属性必须指定为当前(或其祖先)容器中包含工厂方法的bean的名称,而该 工厂bean的工厂方法本身必须通过factory-method属性来设定。

  即factory-bean 属性:调用的一个bean的id

   factory-method属性:要调用的方法

<bean id="serviceLocator" class="com.foo.DefaultServiceLocator">
</bean>
<bean id="exampleBean"
factory-bean="serviceLocator"
factory-method="createInstance"/>

bean的作用域

作用域 描述
singleton 在每个Spring IoC容器中一个bean定义对应一个对象实例。
prototype 一个bean定义对应多个对象实例。
request 在一次HTTP请求中,一个bean定义对应一个实例;即每次HTTP请求将会有各自的bean实例, 它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。
session 在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
global session 在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于web的Spring ApplicationContext情形下有效。

Singleton作用域(单例模式)

  当一个bean的作用域为singleton, 那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例

  换言之,当把一个bean定义设置为singlton作用域时,Spring IoC容器只会创建该bean定义的唯一实例。这个单一实例会被存储到单例缓存(singleton cache)中,并且所有针对该bean的后续请求和引用都将返回被缓存的对象实例。

  单个Spring容器内定义了某个指定class的bean,那么Spring容器将会创建一个且仅有一个由该bean定义指定的类实例。Singleton作用域是Spring中的缺省作用域。

	<bean id="accountService" class="com.foo.DefaultAccountService"/>
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/> <bean id="accountService" class="com.foo.DefaultAccountService" singleton="true"/>

Prototype作用域(实例)

  Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。

<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>

<bean id="accountService" class="com.foo.DefaultAccountService" singleton="false"/>

对于prototype作用域的bean,有一点非常重要,那就是Spring不能对一个prototype bean的整个生命周期负责:容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端随后就对该prototype实例不闻不问了。不管何种作用域,容器都会调用所有对象的初始化生命周期回调方法。但对prototype而言,任何配置好的析构生命周期回调方法都将不会被调用。清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源,都是客户端代码的职责。

Singleton beans和prototype-bean的依赖

  如果要把一个prototype-scoped bean注入到singleton-scoped bean,实际上只是实例化一个新的prototype bean注入到 singleton bean...但这是全部。这种情况下,singleton-scoped bean获得的prototype实例是唯一的

然而,你可能需要在运行期让singleton-scoped bean每次都获得prototype-scoped bean的新实例。在这种情况下,只将prototype-scoped bean注入到你的singleton bean中是没有用的

其他作用域

  其他作用域,即request、session以及global session 仅在基于web的应用中使用。

初始化web配置

  要使用request、session和 global session作用域的bean(即具有web作用域的bean), 在开始设置bean定义之前,还要做少量的初始配置。请注意,假如你只想要“常规的”作用域,(singleton和prototype),就不需要这一额外的设置。

  如果你用Spring Web MVC,即用SpringDispatcherServlet或DispatcherPortlet来处理请求,则不需要做特别的配置:DispatcherServlet 和 DispatcherPortlet已经处理了所有有关的状态。

Request作用域

<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>

  针对每次HTTP请求,Spring容器会根据loginAction bean定义创建一个全新的LoginAction bean实例, 且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态, 而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。 当处理请求结束,request作用域的bean实例将被销毁。

Session作用域

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

  针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例, 且该userPreferences bean仅在当前HTTP Session内有效。 与request作用域一样,你可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例, 将不会看到这些特定于某个HTTP Session的状态变化。 当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。

global session作用域

<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>

  global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session的概念,它被所有构成某个portlet web应用的各种不同的portlet所共享。在global session作用域中定义的bean被限定于全局portlet Session的生命周期范围内。

   注意:假如你在编写一个标准的基于Servlet的web应用,并且定义了一个或多个具有global session作用域的bean,系统会使用标准的HTTP Session作用域,并且不会引起任何错误。

依赖

注入依赖

  依赖注入(DI)背后的基本原理是对象之间的依赖关系(即一起工作的其它对象)只会通过以下几种方式来实现:构造器的参数工厂方法的参数,或给由构造函数或者工厂方法创建的对象设置属性。因此,容器的工作就是创建bean时注入那些依赖关系。相对于由bean自己来控制其实例化、直接在构造器中指定依赖关系或者类似服务定位器(Service Locator)模式这3种自主控制依赖关系注入的方法来说,控制从根本上发生了倒转,这也正是控制反转(Inversion of Control, IoC) 名字的由来。

  应用DI原则后,代码将更加清晰。而且当bean自己不再担心对象之间的依赖关系(甚至不知道依赖的定义指定地方和依赖的实际类)之后,实现更高层次的松耦合将易如反掌。DI主要有两种注入方式,即Setter注入和构造器注入

构造器注入

  基于构造器的DI通过调用带参数的构造器来实现,每个参数代表着一个依赖。此外,还可通过给stattic工厂方法传参数来构造bean。接下来的介绍将认为给构造器传参与给静态工厂方法传参是类似的。下面展示了只能使用构造器参数来注入依赖关系的例子。请注意,这个类并没有什么特别之处。

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}

构造器参数解析

  构造器参数解析根据参数类型进行匹配,如果bean的构造器参数类型定义非常明确,那么在bean被实例化的时候,bean定义中构造器参数的定义顺序就是这些参数的顺序,依次进行匹配,比如下面的代码

package x.y;

public class Foo {

    public Foo(Bar bar, Baz baz) {
// ...
}
}

  上述例子中由于构造参数非常明确(这里我们假定 Bar和 Baz之间不存在继承关系)。因此下面的配置即使没有明确指定构造参数顺序(和类型),也会工作的很好。

<beans>
<bean name="foo" class="x.y.Foo">
<constructor-arg>
<bean class="x.y.Bar"/>
</constructor-arg>
<constructor-arg>
<bean class="x.y.Baz"/>
</constructor-arg>
</bean>
</beans>

  我们再来看另一个bean,该bean的构造参数类型已知,匹配也没有问题(跟前面的例子一样)。但是当使用简单类型时,比如true,Spring将无法知道该值的类型。不借助其他帮助,他将无法仅仅根据参数类型进行匹配,比如下面的这个例子:

package examples;

public class ExampleBean {
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}

}

构造器参数类型匹配

  针对上面的场景可以通过使用'type'属性来显式指定那些简单类型的构造参数的类型,比如:

<bean id="exampleBean" class="examples.ExampleBean">

  <constructor-arg type="int" value="7500000"/>

  <constructor-arg type="java.lang.String" value="42"/>

</bean>

构造参数索引

我们还可以通过index属性来显式指定构造参数的索引,比如下面的例子:

<bean id="exampleBean" class="examples.ExampleBean">

  <constructor-arg index="0" value="7500000"/>

  <constructor-arg index="1" value="42"/>

</bean>

通过使用索引属性不但可以解决多个简单属性的混淆问题,还可以解决有可能有相同类型的2个构造参数的混淆问题了,注意index是从0开始

例子:

配置文件:APP1

<bean id="b1" class="ioc2.B"/>
<bean id="a" class="ioc2.A">
<constructor-arg index="0" ref="b1"/>
</bean>

类B

package ioc;

public class B {

	public B() {
System.out.println("B的无参构造");
}
public void f1() {
System.out.println("B的f1方法");
}
}

A类

package ioc;
public class A {
private B b; public B getB() {
System.out.println("A的getB方法");
return b;
} public void setB(B b) {
System.out.println("A的setB方法");
this.b = b;
} public A() {
System.out.println("A的无参构造");
} public void service() {
b.f1();
}
}

测试方法:

    @Test
public void test1() {
ApplicationContext ac = new ClassPathXmlApplicationContext("APP1.xml");
A a = ac.getBean("a", A.class);
a.service();
}

Setter注入

  通过调用无参构造器或无参static工厂方法实例化bean之后,调用该bean的setter方法,即可实现基于setter的DI。

下面的例子将展示只使用setter注入依赖。注意,这个类并没有什么特别之处,它就是普通的Java类。

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
<!--set注入-->
<bean id="compute" class="com.bean.Compute">
<property name="hdd" value="希捷"/>
<property name="ram" value="金士顿"/>
</bean>

还有自动装配在spring(三)里面讲。

处理bean依赖关系

  步骤:

  • 根据定义bean的配置(文件)创建并初始化BeanFactory实例(大部份的Spring用户使用支持XML格式配置文件的BeanFactory或ApplicationContext实现)。

  • 每个bean的依赖将以属性、构造器参数、或静态工厂方法参数的形式出现。当这些bean被实际创建时,这些依赖也将会提供给该bean。

  • 每个属性或构造器参数既可以是一个实际的值,也可以是对该容器中另一个bean的引用。

  • 每个指定的属性或构造器参数值必须能够被转换成特定的格式或构造参数所需的类型。默认情况下,Spring会以String类型提供值转换成各种内置类型,比如int、long、String、boolean等。

      Spring会在容器被创建时验证容器中每个bean的配置,包括验证那些bean所引用的属性是否指向一个有效的bean(即被引用的bean也在容器中被定义)。然而,在bean被实际创建之前,bean的属性并不会被设置。对于那些singleton类型和被设置为提前实例化的bean(比如ApplicationContext中的singleton bean)而言,bean实例将与容器同时被创建。而另外一些bean则会在需要的时候被创建,伴随着bean被实际创建,作为该bean的依赖bean以及依赖bean的依赖bean(依此类推)也将被创建和分配。

上一篇:云栖专辑 | 阿里开发者们的20个感悟,一通百通


下一篇:正版Win7永不崩溃的秘密 解密系统备份!