[spring]03_装配Bean
3.1 JavaBean
3.1.1 JavaBean 是什么
JavaBean 是一种JAVA语言写成的可重用组件。
为写成JavaBean,类必须是具体的和公共的,并且具有无参数的构造器。
JavaBean 通过提供符合一致性设计模式的公共方法将内部域暴露成员属性。
例
以下是一个简单的JavaBean类。
定义一个Person类,有 name 和 age 两个属性,以及这两个属性的 get、set 方法。
package com.demo.web.controllers; public class Person { private String name = "zhangsan"; private int age = 28; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
3.1.2 JavaBean 特点
- JavaBean 是一个 public 类型的类
- JavaBean 含无参数的构造函数
- JavaBean 提供 set 和 get 方法
3.1.3 JavaBean的生命周期
传统JavaBean生命周期
传统的Java应用,Bean的生命周期很简单。
使用new进行实例化,然后该Bean就可以使用了。程序结束后,Java会自动进行垃圾回收。
在Spring容器中的Bean的生命周期要复杂多了,步骤如下:
(1)Spring对Bean进行实例化。
(2)Spring将值和Bean的引用注入进Bean对应的属性中。
(3)如果 Bean 实现了 BeanNameAware 接口,Spring将 Bean 的ID传递给 setBeanName() 接口方法。
(4)如果 Bean 实现了 BeanFactoryAware 接口,Spring将调用 setBeanFactory() 接口方法,将BeanFactory 容器实例传入。
(5)如果 Bean 实现了 ApplicationContextAware 接口,Spring将调用 setApplicationContext() 接口方法,将应用上下文的引用传入。
(6)如果 Bean 实现了 BeanPostProcessor 接口,Spring将调用它们的 post-ProcessBeforeInitialization接口方法。
(7)如果 Bean 实现了 InitializingBean 接口,Spring将调用它们的 afterPropertiesSet 接口方法。类似地,如果 Bean 使用 init-method 声明了初始化方法,该方法也会被调用。
(8) 如果 Bean 实现了 BeanPostProcessor接口,Spring将调用它们的 post-ProcessAfterInitialization接口方法。
(9)此时此刻,Bean 已经准备就绪,可以被应用程序是用来,它们将一直驻留在应用上下文中,直到该应用上下文被销毁。
(10)如果 Bean 实现了 DisposableBean 接口,Spring将调用它的 destroy() 接口方法。同样,如果Bean使用 destroy-method 声明了销毁方法,该方法也会被调用。
3.2 声明Bean
创建应用对象之间协作关系的行为通常被称为装配,这也是依赖注入的本质。
Spring是一个基于容器的框架。但是如果没有配置Spring,那它就是一个空容器,当然也毫无用处。
所以需要配置Spring,以告诉容器需要加载哪些Bean和如何装配这些Bean。
从Spring3.0开始,Spring容器提供了两种配置Bean的方式。
- 使用XML文件作为配置文件
- 基于Java注解的配置方式
注:本文先不介绍注解的配置方式,而是重点介绍传统的XML配置方式。
3.2.1 创建Spring配置
以下是一个典型的XML配置文件(一般为<servlet名>-servlet.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-3.0.xsd"> <!-- Bean declarations go here --> </beans>
在 <beans> 元素内,可以放置所有的Spring配置信息。
实际上,beans这个标签是 Spring的一种命名空间。
Spring框架自带了10个命名空间,如下表所示:
命名空间 |
用途 |
aop |
为声明切面以及将@AspectJ注解的类代理为Spring切面提供了配置元素 |
beans |
支持声明Bean和装配Bean,是Spring最核心也是最原始的命名空间 |
context |
为配置Spring应用上下文提供了配置元素,包括自动检测盒自动装配Bean、注入非Spring直接管理的对象 |
jee |
提供了与Java EE API的集成,例如JNDI和EJB |
jms |
为声明消息驱动的POJO提供了配置元素 |
lang |
支持配置由Groovy、Jruby或BeanShell等脚本是实现的Bean |
mvc |
启用SpringMVC的能力,例如面向注解的控制器、试图控制器和拦截器 |
oxm |
支持Spring的对象到XML映射配置 |
tx |
提供声明式事务配置 |
util |
提供各种各样的工具类元素,包括把集合配置为Bean、支持属性占位符元素 |
除了Spring框架自带的命名空间,Spring Portfolio的许多成员,例如Spring security、Spring Web Flow和Spring Dynamic Modules,同样提供了他们自己的命名空间配置。
3.2.2 声明JavaBean
以上文提到的Person 类为例,定义了JavaBean后,还需要在xml文件中声明,形式如下:
<bean id="person" class="com.demo.web.controllers.Person"/>
当Spring容器加载这个Bean的时候,会使用默认构造器来实例化person对象,相当于:
com.demo.web.controllers.Person person = new com.demo.web.controllers.Person();
备注实际上,Spring是使用反射机制来创建Bean的。
3.2.3 构造器注入
很多应用场景下,我们希望在初始化实例时,传入关键性的参数,这就需要带参数的构造函数了。
为了解决这个问题,在构造Bean的时候,可以使用 <constructor-arg> 元素来指定构造器参数。
<bean id="person" class="com.demo.web.controllers.Person"> <constructor-arg value="lisi" /> <constructor-arg value="28" /> </bean>
如果不使用这个标签,spring将使用默认构造函数。
以上参数都是直接传入值,如果想传入对象引用,那要怎么做呢?
请参考下面的例子:
首先定义一个Car类
package com.demo.web.controllers; public class Car { private String model; public Car() { } public Car(String model) { this.model = model; } public void run() { System.out.println(model + "
正在行驶"); } }
再定义一个Driver类
package com.demo.web.controllers; public class Driver { private Car car; public Driver() { } public Driver(Car car) { this.car = car; } public void drive() { car.run(); } }
接下来,我们可以在XML中配置一个Car的变量
<bean id="car" class="com.demo.web.controllers.Car"> <constructor-arg value="宝马5系" /> </bean>
有了Car的变量car,我们就可以在声明Driver变量时,使用 <constructor-arg> 标签引用它。
<bean id="driver" class="com.demo.web.controllers.Driver"> <constructor-arg ref="car" /> </bean>
3.2.4 通过工厂方法创建Bean
有时候一个类并没有public型的构造方法(典型的如单例模式里的类),对于这种情况如何在spring中实例化呢?
这时候静态工厂方法是实例化对象的唯一方法。Spring支持通过 <bean> 元素的 factory-method 属性来装配工厂创建的Bean。
package com.demo.web.controllers; public class Singleton { static Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } }
为了在Spring中将Singleton配置为Bean,可以按照下面的方式来配置
<bean id="instance" class="com.demo.web.controllers.Singleton" factory-method="getInstance" />
3.2.5 Bean的作用域
所有的Spring Bean默认都是单例。当容器分配一个Bean时,它总是返回Bean的同一个实例。
但有时我们需要每次请求时都获得唯一的Bean实例,如何做到呢?
当在Spring中配置 <bean> 元素时,我们可以为Bean声明一个作用域。为了让Spring在每次请求时都为Bean产生一个新的实例,我们只需要配置Bean的scope属性为
prototype即可。
<bean id="person" class="com.demo.web.controllers.Person" scope="prototype" />
除了prototype,Spring还提供了其他几个作用域选项,如下:
作用域 |
定义 |
singleton |
在每一个Spring容器中,一个Bean定义只有一个对象实例(默认) |
prototype |
运行Bean的定义可以被实例化任意次(每次调用都创建一个实例) |
request |
在一次HTTP请求中,每个Bean定义对应一个实例,该作用域仅在基于Web的Spring上下文(例如Spring MVC)中才有效 |
session |
在一个HTTP Session中,每个Bean定义对应一个实例,该作用域仅在基于Web的Spring上下文(例如Spring MVC)中才有效 |
global-session |
在一个全局HTTP Session中,每个Bean定义对应一个实例,该作用域仅在Portlet上下文中才有效 |
例。
3.2.6 初始化和销毁Bean
当实例化一个Bean时,可能需要执行一些初始化操作来确保该Bean处于可用状态。同样地,当不再需要Bean,将其从容器中移除时,我们可能还需要按
顺序执行一些清除工作。为了满足初始化和销毁Bean的需求,Spring提供了Bean生命周期的钩子方法。
为Bean定义初始化和销毁操作,只需要使用 init-method 和 destroy-method 参数来配置 <bean> 元素。 init-method 属性指定了在初始化 Bean 时要调用的方法。类似地,destroy-method 属性指定了 Bean 从容器移除之前要调用的方法。
假设,为一个灯泡的功能写一个类。
package com.demo.web.controllers; public class Light { public void turnOn() { // ... } public void turnOff() { // ... } }
接着,我们在XML中做如下配置
<bean id="light" class="com.demo.web.controllers.Light" init-method="turnOn" destroy-method="turnOff" />
这样,就能保证让灯泡类在点亮之前调用turnOn(),结束时调用turnOff()。
3.3 注入Bean属性
通常,JavaBean中的属性都是私有的,同时提供一组get、set方法。
(1)注入简单值
在Spring中,除了用前面介绍的构造器注入方式,还可以使用 <property> 元素配置 Bean 的属性。
还是以Person类为例
<bean id="person" class="com.demo.web.controllers.Person"> <property name="name" value="wangwu" /> <property name="age" value="30" /> </bean>
(2)引用其他Bean
还记得我们在构造器注入部分提到的Driver和Car类的例子吗,如果使用property的方式,则按如下方式表达
<bean id="baoma" class="com.demo.web.controllers.Car"> <constructor-arg value="宝马5系" /> </bean> <bean id="driver" class="com.demo.web.controllers.Driver"> <property name="car" ref="baoma" /> </bean>
(3)内部注入
内部注入是通过直接声明一个 <bean> 元素作为 <property>元素的子节点而定义的。
<bean id="driver" class="com.demo.web.controllers.Driver"> <property name="car"> <bean class="com.demo.web.controllers.Car" /> </property> </bean>
(4)使用Sping的命名空间p装配属性
Spring还提供了一个命名空间p以简化<property>元素的装配方式。
命名空间p的schema URI为 http://www.springframework.org/schema/p。
如果要使用命名空间p ,需要在Spring的XML配置中增加一段声明
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" 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-3.0.xsd">
有了以上声明,我们就可以使用p:作为<bean>元素所有属性的前缀来装配Bean的属性。
用p:重新定义上节Driver类的配置, 如下:
<bean id="baoma" class="com.demo.web.controllers.Car"> <constructor-arg value="宝马5系" /> </bean> <bean id="driver" class="com.demo.web.controllers.Driver" p:car-ref="baoma"/>
3.3.1 装配集合
Spring不仅可以装配单个值,也可以装配集合。
Spring提供4种类型的集合配置元素。
集合元素 |
用途 |
对应实际数据类型 |
<list> |
装配list类型的值,允许重复 |
数组或java.util.Collection |
<set> |
装配set类型的值,不允许重复 |
|
<map> |
装配map类型的值,名称和值可以是任意类型 |
java.util.Map |
<props> |
装配properties类型的值,名称和值必须都是String型 |
java.util.Properties |
(1)<list> 和 <set>
两者的区别在于:list 允许成员重复; set 必须保证成员不重复。
需要注意的是,不是说如果属性是java.util.Set 类型,则用户必须用 <set> 装配。
例
定义一个 instruments 属性,它是一个Collection,保存各种乐器
<property name="instruments"> <list> <ref bean="guitar" /> <ref bean="piano" /> <ref bean="piano" /> </list> </property> <property name="instruments"> <set> <ref bean="guitar" /> <ref bean="piano" /> <ref bean="piano" /><!-- 自动忽略重复的属性 --> </set> </property>
(2)<map>
属性 |
用途 |
key |
指定map中entry的键为String |
key-ref |
指定map中entry的键为Spring上下文中其他Bean的引用 |
value |
指定map中entry的值为String |
value-ref |
指定map中entry的值为Spring上下文中其他Bean的引用 |
例
<property name="instruments"> <map> <entry key="GUITAR" value-ref="guitar"> <entry key="PIANO" value-ref="piano"> </map> </property>
(3)<prop>
如
果 Map 的每一个 entry 的键和值都为 String 类型时,可以考虑使用 java.util.Properties 代替 Map。Properties 类提供了和 Map 大致相同的功能,但是它限定了键和值必须为 String 类型。
例
<property name="instruments"> <props> <prop key="GUITAR">guitar sound</prop> <prop key="PIANO">piano sound</prop> </props> </property>
3.4 使用表达式装配
Spring 3引入了Spring表达式语言(Spring Expression Lanuage, SpEL)。
它通过运行期执行的表达式将值装配到 Bean 的属性或构造器参数中。
3.4.1 SpEL 的基本原理
SpEL 表达式的首要目标是通过计算获得某个值。最简单的SpEL求值或许是对字面值、Bean的属性或某个类的常量进行求值。
字面值
例
<property name="count" value="#{5}" />
<property name="count" value="The value is #{5}" />
引用Bean、Properties 和方法
SpEL 表达式可以通过 ID 引用其他 Bean。
例
<property name="fruit" value="#{apple}" />
这和以下语句的功能等价
<property name="fruit" ref="apple" />
除了直接引用其他Bean,也可以引用 Bean 对象的属性和方法
例
<property name="song" value="#{singer.song}" /> <property name="song" value="#{singer.selectSong().toUpperCase()}" />
需要注意的是,singer.selectSong().toUpperCase()存在一个问题,如果selectSong()方法返回的是null,
那么SpEL表达式求值时会抛出一个 NullPointerException 异常。
为了避免这种情况,可以使用null-safe存取器(?.)
例
<property name="song" value="#{singer.selectSong()?.toUpperCase()}" />
使用 ?. 代替 . 来访问 toUpperCase() 方法。?. 可以确保只有当 selectSong() 不为 null 时才去调用 toUpperCase() 方法。
操作类
现在,我们了解了在 SpE L中,如何去调用 Bean 对象,以及对象的属性和方法。
但是,如何去访问类的静态方法或常量引用呢?
在 SpEL 中,使用 T() 运算符去调用类作用域的方法和常量。
例
以下演示了如何调用 java.lang.Math 类中的静态方法和属性。
<property name="multiplier" value="#{T{java.lang.Math}.PI}" /> <property name="multiplier" value="#{T{java.lang.Math}.random}" />
在 SpEL 值上执行运算操作
SpEL提供了几种运算符,这些运算符可以用在SpEL表达式中的值上。
运算符类型 |
运算符 |
算术运算 |
+、-、*、/、%、^ |
关系运算 |
<、>、==、<=、>=、lt、gt、eq、le、ge |
逻辑运算 |
and、or、not、| |
条件运算 |
?: (ternary)、?: (Elvis) |
正则表达式 |
matches |
3.4.2 在 SpEL 中筛选集合
假设针对前面的Person类,我们定义一个List集合,如下:
<util:list id="persons"> <bean class="com.demo.web.controllers.Person" p:name="zhangsan" p:age="17"/> <bean class="com.demo.web.controllers.Person" p:name="lisi" p:age="24"/> <bean class="com.demo.web.controllers.Person" p:name="wangwu" p:age="30"/> <bean class="com.demo.web.controllers.Person" p:name="zhaoliu" p:age="16"/> <bean class="com.demo.web.controllers.Person" p:name="liuqi" p:age="23"/> </util:list>
访问集合成员
可以使用 [] 运算符来访问集合成员
例
<property name="choosePerson" value="#{persons[T{java.lang.Math}.random() * persons.size()]]}" />
以上表示,随机选取一个人。
也可以按下面方式选取
<property name="choosePerson" value="#{persons['zhangsan']}" /><!-- 选取集合中叫张三的人 --> <property name="choosePerson" value="#{persons[2]}" /><!-- 选取集合中第二个人 -->
查询集合成员
如果想要在persons集合中查询年龄大于18岁的人。
在SpEL中,只需使用一个查询运算符(.?[]) 就可以简单做到,如下所示:
<property name="adults" value="#{persons.?[age gt 18]}" />
查询运算符会创建一个新的集合,集合中只存放符合括号中表达式的成员。
SpEL
还提供两种运算符:.^[] 和 .$[],从集合中查询出第一个匹配项和最后一个匹配项。
投影集合
在SpEL中, 提供了投影运算符(.![])将集合中每个成员的特定属性放入一个新的集合中。
<property name="personNames" value="#{persons.![name]}" />
以上,将所有人的名字取出来,建立一个新的集合personNames。
3.5 JavaBean的一个简单应用实例
以下
以 [Spring]01_
环境配置 中的 HelloWorld 工程为基础,演示一下 JavaBean 的简单使用方法。
打开 HelloWorld 工程。
(1)新建一个 java 文件,名为 Person.java,完整内容如下:
public class Person {
private String name = "zhangsan";
private int age = 28;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
(2)修改 index.jsp 文件,完整内容如下:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
import="com.demo.web.controllers.Person" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Spring Hello World</title>
</head>
<body>
<!--
<h1>${message}</h1>
-->
<jsp:useBean id="person" class="com.demo.web.controllers.Person" scope="session" />
<!-- 获取对象属性 -->
name: <jsp:getProperty name="person" property="name" /><br>
age: <jsp:getProperty name="person" property="age" /><br>
<!-- 设置对象属性 -->
<jsp:setProperty name="person" property="name" value="lisi" />
<jsp:setProperty name="person" property="age" value="30" /><!-- 这里有自动的类型转换:String2int-->
<jsp:setProperty name="person" property="age" value="<%=20 + 20%>" /><!-- value值还可以用表达式 -->
<!-- 再次获取对象属性 -->
<br>
name after change: <jsp:getProperty name="person" property="name" /><br>
age after change: <jsp:getProperty name="person" property="age" /><br>
<!-- 用表达式输出对象属性 -->
<br>
name again: <%=person.getName()%><br>
<!-- 脚本段声明对象 -->
<br>
another person:
<%
Person person2 = new Person(); //这里不能把对象叫做person,提示重复的局部变量,进一步说明这种方法和上面
的方法,本质上是一样的
out.println(person2.getName());
%>
<!-- 再声明一个Person类对象,注意id必须和前面的不同,声明的是同一个类的不同的对
象-->
<jsp:useBean id="person3" class="com.demo.web.controllers.Person" scope="session"/>
</body>
</html>
(3)运行
结果如下:
age: 28
name after change: lisi
age after change: 40
name again: lisi
another person: zhangsan
参考资料
Spring实战(第3版)
[spring]03_装配Bean的更多相关文章
- Spring 自动装配 Bean
Spring3系列8- Spring 自动装配 Bean 1. Auto-Wiring ‘no’ 2. Auto-Wiring ‘byName’ 3. Auto-Wiri ...
- Spring自动装配Bean详解
1. Auto-Wiring ‘no’ 2. Auto-Wiring ‘byName’ 3. Auto-Wiring ‘byType 4. Auto-Wirin ...
- spring IOC装配Bean(注解方式)
1 Spring的注解装配Bean (1) Spring2.5 引入使用注解去定义Bean @Component 描述Spring框架中Bean (2) Spring的框架中提供了与@Componen ...
- Spring自动化装配bean
1. 场景 用CD(Compact disc)和CD播放器(CDPlayer)阐述DI(依赖注入). 如果不将CD插入(注入)到CDPlayer中,那么CDPlayer其实没有太大的用处,所以,可以这 ...
- Spring高级装配bean
目录 spring profile 条件化的bean声明 自动装配与歧义性 bean的作用域 Spring表达式语言 一.环境与profile 配置profile bean 在软件开发的时候,有一个 ...
- Spring容器装配Bean的三种方式
欢迎查看Java开发之上帝之眼系列教程,如果您正在为Java后端庞大的体系所困扰,如果您正在为各种繁出不穷的技术和各种框架所迷茫,那么本系列文章将带您窥探Java庞大的体系.本系列教程希望您能站在上帝 ...
- Spring自动装配Bean的五种方式
在Spring中,支持 5 自动装配模式. no – 缺省情况下,自动配置是通过“ref”属性手动设定,在项目中最常用byName – 根据属性名称自动装配.如果一个bean的名称和其他bean属性的 ...
- Spring自动装配bean
Spring推荐面向接口编程,这样可以很好的解耦具体的实现类. CompactDisc.class 文件: public interface CompactDisc { void play(); } ...
- 二、spring中装配bean
在spring框架中提供了三种 bean的装配方式,当然这三种装配方式是可以灵活的进行组合使用的,项目中使用最多的是自动装配bean的方式,也就是通过注解的方式进行bean的装配,一下是四种装配方式的 ...
随机推荐
- 【C#代码实战】群蚁算法理论与实践全攻略——旅行商等路径优化问题的新方法
若干年前读研的时候,学院有一个教授,专门做群蚁算法的,很厉害,偶尔了解了一点点.感觉也是生物智能的一个体现,和遗传算法.神经网络有异曲同工之妙.只不过当时没有实际需求学习,所以没去研究.最近有一个这样 ...
- C#多线程之线程同步篇3
在上一篇C#多线程之线程同步篇2中,我们主要学习了AutoResetEvent构造.ManualResetEventSlim构造和CountdownEvent构造,在这一篇中,我们将学习Barrier ...
- MJRefresh 源码解读 + 使用
MJRefresh这个刷新控件是一款非常好用的框架,我们在使用一个框架的同时,最好能了解下它的实现原理,不管是根据业务要求在原有的基础上修改代码,还是其他的目的,弄明白作者的思路和代码风格,会受益匪浅 ...
- AFNetworking 3.0 源码解读(十一)之 UIButton/UIProgressView/UIWebView + AFNetworking
AFNetworking的源码解读马上就结束了,这一篇应该算是倒数第二篇,下一篇会是对AFNetworking中的技术点进行总结. 前言 上一篇我们总结了 UIActivityIndicatorVie ...
- Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part2:clusterware安装和升级
Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part2:clusterware安装和升级 环境:OEL 5.7 + Oracle 10.2.0.5 RAC 3.安装Clus ...
- iOS之App Store上架被拒Legal - 5.1.5问题
今天在看到App Store 上架过程中,苹果公司反馈的拒绝原因发现了这么一个问题: Legal - 5.1.5 Your app uses background location services ...
- java 泛型
1.Student stu =tool.getObj();右边得到的是Object类型,需要向下转型,强转换. 2. 3. 4.泛型方法不能被静态修饰这样写 5.如果想定义定义静态泛型方法,只能这样写 ...
- ASP.NET 5 和Entity Framework 7公告仓库
ASP.NET 5 有一个公告仓库来介绍ASP.NET 5和 Entity Framework 7的主要变更说明,这个对于日夜更新的项目来说,很多人经常会遇到问题但是不知道去哪里寻找帮助,很多同学在做 ...
- 使用C# 和Consul进行分布式系统协调
随着大数据时代的到来,分布式是解决大数据问题的一个主要手段,随着越来越多的分布式的服务,如何在分布式的系统中对这些服务做协调变成了一个很棘手的问题.今天我们就来看看如何使用C# ,利用开源对分布式服务 ...
- SQL SERVER全面优化-------Expert for SQL Server 诊断系列
现在很多用户被数据库的慢的问题所困扰,又苦于花钱请一个专业的DBA成本太高.软件维护人员对数据库的了解又不是那么深入,所以导致问题迟迟不能解决,或只能暂时解决不能得到根治.开发人员解决数据问题基本又是 ...