码迷,mamicode.com
首页 > 编程语言 > 详细

20191225 Spring官方文档(Core 1.6-1.8)

时间:2019-12-25 20:32:17      阅读:78      评论:0      收藏:0      [点我收藏+]

标签:相同   基于   mes   程序开发   support   code   eve   red   over   

1.6。自定义Bean的性质

Spring框架提供了许多接口,可用于自定义Bean的性质。

1.6.1。生命周期回调

为了与容器对bean生命周期的管理进行交互,可以实现Spring的 InitializingBeanDisposableBean接口。容器调用afterPropertiesSet()destroy()使bean在初始化和销毁bean时执行某些操作。

通常,JSR-250 @PostConstruct@PreDestroy注释被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注释意味着您的bean没有耦合到特定于Spring的接口。

如果你不希望使用JSR-250注解,但你仍然要删除的耦合,考虑<bean/>标签的init-methoddestroy-method或者@Bean注解的initMethoddestroyMethod属性。

在内部,Spring Framework使用BeanPostProcessor实现来处理它可以找到的任何回调接口并调用适当的方法。如果您需要自定义功能或其他生命周期行为,Spring默认不提供,您可以自己实现BeanPostProcessor

除了初始化和销毁回调外,Spring托管的对象还可以实现Lifecycle接口,以便这些对象可以在容器自身生命周期的驱动下参与启动和关闭过程。

同时使用@PostConstructInitializingBean时,先调用@PostConstruct注解的方法,@PostConstruct使用的是后置处理器,InitializingBean不是。@PreDestroy先调用,DisposableBean后调用。

建议使用注解@PostConstruct@PreDestroy

销毁回调

org.springframework.beans.factory.DisposableBean接口

@PreDestroy

<bean/>标签的destroy-method属性

@Bean注解的destroyMethod属性

您可以为<bean>元素的destroy-method属性分配一个特殊值(inferred),该值指示Spring自动检测特定bean类上的公共closeshutdown方法。(实现java.lang.AutoCloseablejava.io.Closeable匹配的任何类。)您还可以在<beans>元素的default-destroy-method属性上设置此特殊值(inferred),以将该行为应用于整个bean集。请注意,这是Java配置的默认行为。

默认初始化和销毁方法

当你写的初始化和销毁回调不使用Spring的InitializingBeanDisposableBean回调接口,通常的写入方法的名字如init()initialize()dispose(),等等。理想情况下,此类生命周期回调方法的名称应在整个项目中标准化,以便所有开发人员都使用相同的方法名称并确保一致性。

<beans default-init-method="init" default-destroy-method="destroy">

    <bean id="blogService" class="com.something.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>

</beans>

可以通过使用<bean/>自身的init-methoddestroy-method属性指定(在XML中)方法名称来覆盖默认值。

Spring容器保证在为bean提供所有依赖项后立即调用配置的初始化回调。因此,在原始bean引用上调用了初始化回调,这意味着AOP拦截器等尚未应用于bean。首先完全创建目标bean,然后应用带有其拦截器链的AOP代理(例如)。如果目标Bean和代理分别定义,则您的代码甚至可以绕过代理与原始目标Bean进行交互。因此,将拦截器应用于init方法将是不一致的,因为这样做会将目标Bean的生命周期耦合到其代理或拦截器,并且当您的代码直接与原始目标Bean进行交互时会留下奇怪的语义。

组合生命周期机制

从Spring 2.5开始,您可以使用三个选项来控制Bean生命周期行为:

  • InitializingBeanDisposableBean回调接口
  • 定制的init()destroy()方法
  • @PostConstruct@PreDestroy 注释。

如果为一个bean配置了多个生命周期机制,并且为每个机制配置了不同的方法名称,则将按照此注释后列出的顺序执行每个已配置的方法。但是,如果为多个生命周期机制中的多个生命周期机制(例如init(),对于初始化方法)配置了相同的方法名 ,则该方法将执行一次。

为同一个bean配置的具有不同初始化方法的多种生命周期机制如下:

  1. @PostConstruct注释的方法
  2. InitializingBean回调接口定义的afterPropertiesSet()
  3. 定制配置的init()方法

销毁方法的调用顺序相同:

  1. @PreDestroy注释的方法
  2. DisposableBean回调接口定义的destroy()
  3. 定制配置的destroy()方法

启动和关闭容器回调

org.springframework.context.Lifecycle 接口为具有自己的生命周期要求(例如启动和停止某些后台进程)的任何对象定义了基本方法。

任何Spring管理的对象都可以实现Lifecycle接口。然后,当 ApplicationContext自身接收到启动和停止信号时(例如,对于Java运行时的停止/重新启动场景),它会将那些调用级联到Lifecycle在该上下文中定义的所有实现。它通过委派给一个org.springframework.context.LifecycleProcessor,以实现此目的。

Spring会自动注册一个org.springframework.context.support.DefaultLifecycleProcessor对象。

LifecycleProcessorLifecycle 接口的扩展。它还添加了其他两种方法来对正在刷新和关闭的上下文做出反应。

常规org.springframework.context.Lifecycle接口是用于显式启动和停止通知的普通协议,并不意味着在上下文刷新时自动启动。为了对特定bean的自动启动进行精细控制(包括启动阶段),请考虑实现org.springframework.context.SmartLifecycle

另外,请注意,不能保证会在销毁之前发出停止通知。在常规关闭时,在传播常规销毁回调之前,所有Lifecycle Bean首先都会收到停止通知。但是,在上下文生命周期中进行热刷新或中止刷新尝试时,仅调用destroy方法。

启动和关闭调用的顺序可能很重要。如果任何两个对象之间存在“依赖”关系,则依赖方在其依赖对象之后开始,而在依赖对象之前停止。但是,有时直接依赖项是未知的。您可能只知道某种类型的对象应该先于另一种类型的对象开始。在这些情况下,SmartLifecycle接口定义另一个选项,即在其父接口Phased上定义的getPhase()方法 。

启动时,相位最低的对象首先启动。停止时,遵循相反的顺序。因此,实现SmartLifecycle的返回Integer.MIN_VALUEgetPhase()方法的对象将是第一个开始且最后一个停止的对象。在频谱的另一端,相位值 Integer.MAX_VALUE表示该对象应最后启动并首先停止(可能因为该对象取决于正在运行的其他进程)。当考虑相位值,同样重要的是要知道,对于任何“正常”的没有实现SmartLifecycleLifecycle的默认相位为0。因此,任何负相位值都表明对象应在这些标准组件之前开始(并在它们之后停止)。对于任何正相位值,反之亦然。SmartLifecycle的默认相位为Integer.MAX_VALUE

SmartLifecycle定义的stop方法接受回调。在该实现的关闭过程完成之后,任何实现都必须调用该回调的run()方法。由于LifecycleProcessor接口 的默认实现DefaultLifecycleProcessor会在每个阶段内的对象组等待其超时值,以调用该回调,因此可以在必要时启用异步关闭。默认的每阶段超时是30秒。您可以通过定义上下文中命名lifecycleProcessor 的bean来覆盖默认的生命周期处理器实例 。如果只想修改超时,则定义以下内容即可:

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
    <!-- timeout value in milliseconds -->
    <property name="timeoutPerShutdownPhase" value="10000"/>
</bean>

如前所述,LifecycleProcessor接口还定义了用于刷新和关闭上下文的回调方法。后者驱动关闭过程,就好像已经显式调用了stop()一样,但是它在上下文关闭时发生。另一方面,“刷新”回调启用了SmartLifecycle bean的另一个功能 。刷新上下文时(在所有对象都被实例化和初始化之后),该回调将被调用。此时,默认的生命周期处理器将检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值 。如果为true,则在该点启动该对象,而不是等待上下文或其自身的显式调用start()方法(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。phase值与任何“依赖式”的关系确定为前面所述的启动顺序。

在非Web应用程序中正常关闭Spring IoC容器

本节仅适用于非Web应用程序。Spring的基于Web的 ApplicationContext实现已经有了相应的代码,可以在相关Web应用程序关闭时正常关闭Spring IoC容器。

如果您在非Web应用程序环境中(例如,在富客户端桌面环境中)使用Spring的IoC容器,请向JVM注册一个关闭钩子。这样做可以确保正常关机,并在您的Singleton bean上调用相关的destroy方法,以便释放所有资源。您仍然必须正确配置和实现这些destroy回调。

要注册关闭挂钩,请调用ConfigurableApplicationContext接口上声明的registerShutdownHook()方法,如以下示例所示:

ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

// add a shutdown hook for the above context...
ctx.registerShutdownHook();

1.6.2。ApplicationContextAware和BeanNameAware

ApplicationContextAware

org.springframework.context.support.ApplicationContextAwareProcessor#invokeAwareInterfaces

BeanNameAware

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#invokeAwareMethods

自动装配是获得对ApplicationContext的引用的另一种方法。传统的 constructor和 byType自动装配模式可以分别提供ApplicationContext类型的依赖于构造器参数或设置器方法参数。要获得更大的灵活性,包括能够自动装配字段和使用多个参数方法,请使用基于注释的自动装配功能。如果您这样做,则将ApplicationContext自动将其装配到需要该ApplicationContext类型的字段,构造函数参数或方法参数中(如果相关的字段,构造函数或方法带有@Autowired注释)。

org.springframework.beans.factory.BeanNameAware#setBeanName 回调在填充bean属性之后,在bean调用初始化方法之前。

1.6.3。其他Aware接口

除了ApplicationContextAware和BeanNameAware,Spring还提供了各种各样的Aware回调接口,这些接口使bean可以向容器指示它们需要某种基础结构依赖性。通常,名称表示依赖项类型。

名称 注入依赖
ApplicationContextAware ApplicationContext
ApplicationEventPublisherAware 封闭的ApplicationContext的事件发布者
BeanClassLoaderAware 类加载器,用于加载Bean类。
BeanFactoryAware BeanFactory。
BeanNameAware 声明bean的名称。
BootstrapContextAware 容器在BootstrapContext中运行的资源适配器。通常仅在支持JCA的ApplicationContext实例中可用。
LoadTimeWeaverAware 定义的编织器,用于在加载时处理类定义。
MessageSourceAware 解决消息的已配置策略(支持参数化和国际化)。
NotificationPublisherAware Spring JMX通知发布者。
ResourceLoaderAware 配置的加载程序,用于对资源的低级别访问。
ServletConfigAware 当前ServletConfig容器在其中运行。仅在Web环境的Spring ApplicationContext中有效。
ServletContextAware 当前ServletContext容器在其中运行。仅在Web环境的Spring ApplicationContext中有效。

再次注意,使用这些接口会将您的代码与Spring API绑定在一起,并且不遵循“控制反转”样式。因此,我们建议将它们用于需要以编程方式访问容器的基础结构Bean。

1.7。Bean定义继承

子bean定义从父定义继承配置数据。子定义可以覆盖某些值或根据需要添加其他值。使用父bean和子bean定义可以节省很多输入。实际上,这是一种模板形式。

如果您以编程方式使用ApplicationContext接口,则子bean定义由ChildBeanDefinition类表示。大多数用户不在此级别上使用它们。而是在诸如ClassPathXmlApplicationContext之类的类中声明性地配置Bean定义。当使用基于XML的配置元数据时,可以通过使用parent属性来指定子bean定义,并将父bean指定为该属性的值。

如果未指定子bean类定义,则使用父定义中的bean类,但也可以覆盖它。在后一种情况下,子bean类必须与父类兼容(也就是说,它必须接受父类的属性值)。

如果父定义未指定类,请根据需要显式标记父bean定义abstract

<bean id="inheritedTestBeanWithoutClass" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name" value="override"/>
    <!-- age will inherit the value of 1 from the parent bean definition-->
</bean>

父bean不能单独实例化,因为它不完整,并且也被显式标记为abstract。当定义为abstract时,只能用作纯模板bean定义,用作子定义的父定义。尝试通过将其称为另一个bean的ref属性来单独使用这样的abstract父bean或getBean()使用父bean ID 进行显式调用会返回错误。同样,容器的内部preInstantiateSingletons()方法将忽略定义为抽象的bean定义。

默认情况下ApplicationContext预先实例化所有单例。因此,重要的是(至少对于单例bean),如果有一个(父)bean定义仅打算用作模板,并且此定义指定了一个类,则必须确保将abstract属性设置为true,否则应用程序上下文将实际上(试图)预先实例化abstract bean。

1.8。容器扩展点

通常,应用程序开发人员不需要对ApplicationContext 实现类进行子类化。相反,可以通过插入特殊集成接口的实现来扩展Spring IoC容器。

1.8.1。使用 BeanPostProcessor 自定义Bean

BeanPostProcessor接口定义了回调方法,您可以实现这些回调方法,以提供自己的(或覆盖容器的默认值)实例化逻辑,依赖关系解析逻辑等。如果您想在Spring容器完成实例化,配置和初始化bean之后实现一些自定义逻辑,则可以插入一个或多个自定义BeanPostProcessor实现。

您可以配置多个BeanPostProcessor实例,并且可以通过设置order属性来控制这些BeanPostProcessor实例的执行顺序。仅当BeanPostProcessor实现Ordered 接口时才可以设置此属性。

Spring IoC容器实例化一个bean实例,然后BeanPostProcessor 实例执行其工作。

BeanPostProcessor实例是按容器划分作用域的。仅在使用容器层次结构时,这才有意义。如果在一个容器中定义一个BeanPostProcessor,它将仅对该容器中的bean进行后处理。换句话说,一个容器中定义的bean不会被另一个容器中的BeanPostProcessor定义进行后处理,即使这两个容器是同一层次结构的一部分也是如此。

要更改实际的bean定义(即,定义bean的蓝图),您需要使用BeanFactoryPostProcessor

org.springframework.beans.factory.config.BeanPostProcessor接口由两个回调方法组成。当此类被注册为容器的后处理器时,对于容器创建的每个bean实例,后处理器都会在容器初始化方法(例如InitializingBean.afterPropertiesSet()或任何声明的init方法)被使用之前和之后被调用。后处理器可以对bean实例执行任何操作,包括完全忽略回调。Bean后处理器通常检查回调接口,或者可以用代理包装Bean。一些Spring AOP基础结构类被实现为bean后处理器,以提供代理包装逻辑。

ApplicationContext自动检测实现BeanPostProcessor接口的配置元数据中定义的所有bean 。ApplicationContext将这些Bean注册为后处理器,以便以后在创建Bean时调用它们。Bean后处理器可以与其他Bean以相同的方式部署在容器中。

请注意,在配置类上通过使用@Bean工厂方法声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身或至少是org.springframework.beans.factory.config.BeanPostProcessor 接口,以清楚地表明该bean的后处理器性质。否则, ApplicationContext无法在完全创建之前按类型自动检测它。由于需要将BeanPostProcessor进行早期实例化以应用于上下文中其他bean的初始化,因此这种早期类型检测至关重要。

虽然推荐的BeanPostProcessor注册方法是通过 ApplicationContext自动检测(如前所述),但是您可以使用ConfigurableBeanFactoryaddBeanPostProcessor 方法通过编程方式对它们进行注册。当您需要在注册之前评估条件逻辑,甚至需要跨层次结构的上下文复制Bean后处理器时,这将非常有用。但是请注意,以编程方式添加的BeanPostProcessor实例不遵守Ordered接口。在这里,注册的顺序决定了执行的顺序。还要注意,以编程方式注册的BeanPostProcessor实例始终在通过自动检测注册的实例之前进行处理,而不考虑任何明确的顺序。

实现BeanPostProcessor接口的类是特殊的,并且容器对它们的处理方式有所不同。它们直接引用的所有BeanPostProcessor实例和bean在启动时都会实例化,作为ApplicationContext的特殊启动阶段的一部分。接下来,以排序方式注册所有BeanPostProcessor实例,并将其应用于容器中的所有其他bean。因为AOP自动代理本身是BeanPostProcessor实现的,所以BeanPostProcessor 实例或它们直接引用的bean都没有资格进行自动代理,并且因此没有编织的切面。

对于任何此类bean,您应该看到一条参考日志消息:Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)

如果您使用自动装配或@Resource(可能会退回到自动装配)将Bean装配到您的BeanPostProcessor bean中,则Spring在搜索类型匹配的依赖项候选对象时可能会访问意外的bean,因此使它们不适合进行自动代理或其他类型的bean 后处理。例如,如果您有一个依赖项,其注释@Resource中的字段或设置器名称不直接与bean的声明名称相对应,并且不使用name属性,那么Spring将访问其他bean以按类型匹配它们。

1.8.2。使用BeanFactoryPostProcessor自定义配置元数据

org.springframework.beans.factory.config.BeanFactoryPostProcessor接口的语义与BeanPostProcessor相似,但有一个主要区别:BeanFactoryPostProcessor对Bean配置元数据进行操作。也就是说,Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据,并有可能在容器实例化BeanFactoryPostProcessor实例以外的任何Bean 之前更改它。

您可以配置多个BeanFactoryPostProcessor实例,并且可以通过设置order属性来控制这些BeanFactoryPostProcessor实例的运行顺序。但是,只有在BeanFactoryPostProcessor实现Ordered接口的情况下才能设置此属性。如果您编写自己的代码BeanFactoryPostProcessor,则也应该考虑实现Ordered接口。

如果要更改实际的bean实例(即,从配置元数据创建的对象),则需要使用 BeanPostProcessor。尽管在技术上可以使用BeanFactoryPostProcessor中的bean实例(例如,通过使用 BeanFactory.getBean()),但是这样做会导致bean实例化过早,从而违反了标准容器的生命周期。这可能会导致负面影响,例如绕过bean后处理。

同样,BeanFactoryPostProcessor实例是按容器划分作用域的。仅在使用容器层次结构时才有意义。如果在一个容器中定义BeanFactoryPostProcessor,则仅将其应用于该容器中的bean定义。一个容器中的Bean定义不会被另一个容器中的BeanFactoryPostProcessor实例后处理,即使两个容器都属于同一层次结构也是如此。

ApplicationContext将Bean工厂后处理器声明后,它将自动执行 ,以便将更改应用于定义容器的配置元数据。Spring包含许多预定义的bean工厂后处理器,例如PropertyOverrideConfigurerPropertySourcesPlaceholderConfigurer。您还可以使用自定义BeanFactoryPostProcessor,例如注册自定义属性编辑器。

ApplicationContext自动检测所有部署到其中的实现BeanFactoryPostProcessor接口的bean 。它在适当的时候将这些bean用作bean工厂的后处理器。您可以像部署其他任何bean一样部署这些后处理器bean。

与BeanPostProcessors一样,您通常不希望将BeanFactoryPostProcessors 配置为延迟初始化。如果没有其他bean引用 Bean(Factory)PostProcessor,则该后处理器将完全不会实例化。因此,将其标记为延迟初始化将被忽略,并且即使您在<beans />元素声明中将default-lazy-init属性设置为true,Bean(Factory)PostProcessor也会立即实例化。

示例:类名替换 PropertySourcesPlaceholderConfigurer

您可以使用PropertySourcesPlaceholderConfigurer,使用标准Java Properties格式来外部化在单独的文件中的Bean定义中的属性值。这样做使部署应用程序的人员可以自定义特定于环境的属性,例如数据库URL和密码,而无需为修改容器的主要XML定义文件而复杂或冒风险。

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

在运行时,将PropertySourcesPlaceholderConfigurer应用于替换数据源的某些属性的元数据。将要替换的值指定为${property-name}形式的占位符,该形式遵循Ant,log4j和JSP EL样式。

因此,在运行时${jdbc.username}将替换为值sa,并且其他与属性文件中的键匹配的占位符值也适用。PropertySourcesPlaceholderConfigurer为大多数属性和bean定义的属性进行占位符检查。此外,您可以自定义占位符前缀和后缀。

使用Spring 2.5中引入的context名称空间,您可以使用专用配置元素配置属性占位符。您可以在location属性中提供一个或多个位置作为逗号分隔的列表,如以下示例所示:

<context:property-placeholder location="classpath:com/something/jdbc.properties"/>

PropertySourcesPlaceholderConfigurer不仅将查找在Properties文件中指定的属性。默认情况下,如果无法在指定的属性文件中找到属性,则会检查Spring Environment属性和常规Java System属性。

您可以使用PropertySourcesPlaceholderConfigurer代替类名,当您必须在运行时选择特定的实现类时,这有时很有用。以下示例显示了如何执行此操作:

<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
    <property name="locations">
        <value>classpath:com/something/strategy.properties</value>
    </property>
    <property name="properties">
        <value>custom.strategy.class=com.something.DefaultStrategy</value>
    </property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>

如果无法在运行时将该类解析为有效的类,则将要创建的bean的解析将失败,这是在非延迟初始化bean的ApplicationContext的preInstantiateSingletons()阶段期间进行的。

示例: PropertyOverrideConfigurer

PropertyOverrideConfigurer,另一个bean工厂后处理器,类似 PropertySourcesPlaceholderConfigurer,但不同于后者,对于所有的bean属性原本的定义可以有缺省值或者根本没有值。如果覆盖 Properties文件没有某个bean属性的条目,则使用默认的上下文定义。

注意,bean定义不知道会被覆盖,因此从XML定义文件中不能立即看出正在使用覆盖配置器。如果有多个PropertyOverrideConfigurer实例为同一bean属性定义了不同的值,则由于覆盖机制,最后一个实例将获胜。

属性文件配置行采用以下格式

beanName.property=value

支持复合属性:

tom.fred.bob.sammy = 123

指定的替代值始终是文字值。它们不会转换为bean引用。当XML bean定义中的原始值指定bean引用时,此约定也适用。

使用Spring 2.5中引入的context名称空间,可以使用专用的配置元素配置属性覆盖,如以下示例所示:

<context:property-override location="classpath:override.properties"/>

1.8.3。FactoryBean自定义实例化逻辑

您可以为本身就是工厂的对象实现org.springframework.beans.factory.FactoryBean接口。

FactoryBean接口是可插入Spring IoC容器的实例化逻辑的一点。如果您拥有复杂的初始化代码,而不是(可能)冗长的XML,可以用Java更好地表达,则可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将自定义FactoryBean插入容器。

FactoryBean接口提供了三种方法:

  • Object getObject():返回此工厂创建的对象的实例。实例可以共享,具体取决于该工厂返回单例还是原型。
  • boolean isSingleton():如果FactoryBean返回单例,则返回true,否则false。
  • Class getObjectType():返回getObject()方法返回的对象类型,或者如果类型未知,则返回null。

Spring框架中的许多地方都使用了FactoryBean的概念和接口。Spring本身附带了50多种FactoryBean接口实现。

当您需要向容器询问FactoryBean本身而不是由其产生的bean的实际实例时,请在调用getBean()方法时在该bean的id前面加上符号 &

20191225 Spring官方文档(Core 1.6-1.8)

标签:相同   基于   mes   程序开发   support   code   eve   red   over   

原文地址:https://www.cnblogs.com/huangwenjie/p/12098568.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!