- 浏览: 94923 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
dylan0514sina.cn:
youjianbo_han_87 写道dylan0514sin ...
方法缓存 -
youjianbo_han_87:
dylan0514sina.cn 写道youjianbo_ha ...
方法缓存 -
dylan0514sina.cn:
youjianbo_han_87 写道缓存方法有意义吗,方法+ ...
方法缓存 -
youjianbo_han_87:
缓存方法有意义吗,方法+调用从缓存中取内容的方法 换成 方法+ ...
方法缓存 -
dylan0514sina.cn:
Shen.Yiyang 写道剔除策略只有方法执行的时候指定ke ...
方法缓存
PropertyResolver 是 Environment的顶层接口,主要提供属性检索和解析带占位符的文本。bean.xml配置中的所有占位符例如${}都由它解析。通过例子代码了解它的功能和使用
getProperty返回变量在Environment中对应的值
对带有${}占位符的文本解析算法如下
public class PropertySourcesPropertyResolverTests { private Properties testProperties; private MutablePropertySources propertySources; private ConfigurablePropertyResolver propertyResolver; @Before public void setUp() { propertySources = new MutablePropertySources(); propertyResolver = new PropertySourcesPropertyResolver(propertySources); testProperties = new Properties(); propertySources.addFirst(new PropertiesPropertySource("testProperties", testProperties)); } @Test public void containsProperty() { assertThat(propertyResolver.containsProperty("foo"), is(false)); testProperties.put("foo", "bar"); assertThat(propertyResolver.containsProperty("foo"), is(true)); } @Test public void getProperty() { assertThat(propertyResolver.getProperty("foo"), nullValue()); testProperties.put("foo", "bar"); assertThat(propertyResolver.getProperty("foo"), is("bar")); } @Test public void getProperty_withDefaultValue() { assertThat(propertyResolver.getProperty("foo", "myDefault"), is("myDefault")); testProperties.put("foo", "bar"); assertThat(propertyResolver.getProperty("foo"), is("bar")); } @Test public void getProperty_propertySourceSearchOrderIsFIFO() { MutablePropertySources sources = new MutablePropertySources(); PropertyResolver resolver = new PropertySourcesPropertyResolver(sources); sources.addFirst(new MockPropertySource("ps1").withProperty("pName", "ps1Value")); assertThat(resolver.getProperty("pName"), equalTo("ps1Value")); sources.addFirst(new MockPropertySource("ps2").withProperty("pName", "ps2Value")); assertThat(resolver.getProperty("pName"), equalTo("ps2Value")); sources.addFirst(new MockPropertySource("ps3").withProperty("pName", "ps3Value")); assertThat(resolver.getProperty("pName"), equalTo("ps3Value")); } @Test public void getProperty_withExplicitNullValue() { // java.util.Properties does not allow null values (because Hashtable does not) Map<String, Object> nullableProperties = new HashMap<String, Object>(); propertySources.addLast(new MapPropertySource("nullableProperties", nullableProperties)); nullableProperties.put("foo", null); assertThat(propertyResolver.getProperty("foo"), nullValue()); } @Test public void getProperty_withTargetType_andDefaultValue() { assertThat(propertyResolver.getProperty("foo", Integer.class, 42), equalTo(42)); testProperties.put("foo", 13); assertThat(propertyResolver.getProperty("foo", Integer.class, 42), equalTo(13)); } @Test public void getProperty_withStringArrayConversion() { testProperties.put("foo", "bar,baz"); assertThat(propertyResolver.getProperty("foo", String[].class), equalTo(new String[] { "bar", "baz" })); } @Test public void getProperty_withNonConvertibleTargetType() { testProperties.put("foo", "bar"); class TestType { } try { propertyResolver.getProperty("foo", TestType.class); fail("Expected IllegalArgumentException due to non-convertible types"); } catch (IllegalArgumentException ex) { // expected } } @Test public void getProperty_doesNotCache_replaceExistingKeyPostConstruction() { String key = "foo"; String value1 = "bar"; String value2 = "biz"; HashMap<String, Object> map = new HashMap<String, Object>(); map.put(key, value1); // before construction MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MapPropertySource("testProperties", map)); PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources); assertThat(propertyResolver.getProperty(key), equalTo(value1)); map.put(key, value2); // after construction and first resolution assertThat(propertyResolver.getProperty(key), equalTo(value2)); } @Test public void getProperty_doesNotCache_addNewKeyPostConstruction() { HashMap<String, Object> map = new HashMap<String, Object>(); MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MapPropertySource("testProperties", map)); PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources); assertThat(propertyResolver.getProperty("foo"), equalTo(null)); map.put("foo", "42"); assertThat(propertyResolver.getProperty("foo"), equalTo("42")); } @Test public void getPropertySources_replacePropertySource() { propertySources = new MutablePropertySources(); propertyResolver = new PropertySourcesPropertyResolver(propertySources); propertySources.addLast(new MockPropertySource("local").withProperty("foo", "localValue")); propertySources.addLast(new MockPropertySource("system").withProperty("foo", "systemValue")); // 'local' was added first so has precedence assertThat(propertyResolver.getProperty("foo"), equalTo("localValue")); // replace 'local' with new property source propertySources.replace("local", new MockPropertySource("new").withProperty("foo", "newValue")); // 'system' now has precedence assertThat(propertyResolver.getProperty("foo"), equalTo("newValue")); assertThat(propertySources.size(), is(2)); } @Test public void getRequiredProperty() { testProperties.put("exists", "xyz"); assertThat(propertyResolver.getRequiredProperty("exists"), is("xyz")); try { propertyResolver.getRequiredProperty("bogus"); fail("expected IllegalStateException"); } catch (IllegalStateException ex) { // expected } } @Test public void getRequiredProperty_withStringArrayConversion() { testProperties.put("exists", "abc,123"); assertThat(propertyResolver.getRequiredProperty("exists", String[].class), equalTo(new String[] { "abc", "123" })); try { propertyResolver.getRequiredProperty("bogus", String[].class); fail("expected IllegalStateException"); } catch (IllegalStateException ex) { // expected } } @Test public void resolvePlaceholders() { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("key", "value")); PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); assertThat(resolver.resolvePlaceholders("Replace this ${key}"), equalTo("Replace this value")); } @Test public void resolvePlaceholders_withUnresolvable() { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("key", "value")); PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); assertThat(resolver.resolvePlaceholders("Replace this ${key} plus ${unknown}"), equalTo("Replace this value plus ${unknown}")); } @Test public void resolvePlaceholders_withDefaultValue() { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("key", "value")); PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); assertThat(resolver.resolvePlaceholders("Replace this ${key} plus ${unknown:defaultValue}"), equalTo("Replace this value plus defaultValue")); } @Test(expected=IllegalArgumentException.class) public void resolvePlaceholders_withNullInput() { new PropertySourcesPropertyResolver(new MutablePropertySources()).resolvePlaceholders(null); } @Test public void resolveRequiredPlaceholders() { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("key", "value")); PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); assertThat(resolver.resolveRequiredPlaceholders("Replace this ${key}"), equalTo("Replace this value")); } @Test(expected=IllegalArgumentException.class) public void resolveRequiredPlaceholders_withUnresolvable() { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("key", "value")); PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); resolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown}"); } @Test public void resolveRequiredPlaceholders_withDefaultValue() { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("key", "value")); PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); assertThat(resolver.resolveRequiredPlaceholders("Replace this ${key} plus ${unknown:defaultValue}"), equalTo("Replace this value plus defaultValue")); } @Test(expected=IllegalArgumentException.class) public void resolveRequiredPlaceholders_withNullInput() { new PropertySourcesPropertyResolver(new MutablePropertySources()).resolveRequiredPlaceholders(null); } @Test public void getPropertyAsClass() throws ClassNotFoundException, LinkageError { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("some.class", SpecificType.class.getName())); PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); assertTrue(resolver.getPropertyAsClass("some.class", SomeType.class).equals(SpecificType.class)); } @Test public void getPropertyAsClass_withInterfaceAsTarget() throws ClassNotFoundException, LinkageError { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("some.class", SomeType.class.getName())); PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); assertTrue(resolver.getPropertyAsClass("some.class", SomeType.class).equals(SomeType.class)); } @Test(expected=ConversionException.class) public void getPropertyAsClass_withMismatchedTypeForValue() { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("some.class", "java.lang.String")); PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); resolver.getPropertyAsClass("some.class", SomeType.class); } @Test(expected=ConversionException.class) public void getPropertyAsClass_withNonExistentClassForValue() { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("some.class", "some.bogus.Class")); PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); resolver.getPropertyAsClass("some.class", SomeType.class); } @Test public void getPropertyAsClass_withObjectForValue() { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("some.class", new SpecificType())); PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); assertTrue(resolver.getPropertyAsClass("some.class", SomeType.class).equals(SpecificType.class)); } @Test(expected=ConversionException.class) public void getPropertyAsClass_withMismatchedObjectForValue() { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("some.class", new Integer(42))); PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); resolver.getPropertyAsClass("some.class", SomeType.class); } @Test public void getPropertyAsClass_withRealClassForValue() { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("some.class", SpecificType.class)); PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); assertTrue(resolver.getPropertyAsClass("some.class", SomeType.class).equals(SpecificType.class)); } @Test(expected=ConversionException.class) public void getPropertyAsClass_withMismatchedRealClassForValue() { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("some.class", Integer.class)); PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources); resolver.getPropertyAsClass("some.class", SomeType.class); } @Test public void setRequiredProperties_andValidateRequiredProperties() { // no properties have been marked as required -> validation should pass propertyResolver.validateRequiredProperties(); // mark which properties are required propertyResolver.setRequiredProperties("foo", "bar"); // neither foo nor bar properties are present -> validating should throw try { propertyResolver.validateRequiredProperties(); fail("expected validation exception"); } catch (MissingRequiredPropertiesException ex) { assertThat(ex.getMessage(), equalTo( "The following properties were declared as required " + "but could not be resolved: [foo, bar]")); } // add foo property -> validation should fail only on missing 'bar' property testProperties.put("foo", "fooValue"); try { propertyResolver.validateRequiredProperties(); fail("expected validation exception"); } catch (MissingRequiredPropertiesException ex) { assertThat(ex.getMessage(), equalTo( "The following properties were declared as required " + "but could not be resolved: [bar]")); } // add bar property -> validation should pass, even with an empty string value testProperties.put("bar", ""); propertyResolver.validateRequiredProperties(); } @Test public void resolveNestedPropertyPlaceholders() { MutablePropertySources ps = new MutablePropertySources(); ps.addFirst(new MockPropertySource() .withProperty("p1", "v1") .withProperty("p2", "v2") .withProperty("p3", "${p1}:${p2}") // nested placeholders .withProperty("p4", "${p3}") // deeply nested placeholders .withProperty("p5", "${p1}:${p2}:${bogus}") // unresolvable placeholder .withProperty("p6", "${p1}:${p2}:${bogus:def}") // unresolvable w/ default .withProperty("pL", "${pR}") // cyclic reference left .withProperty("pR", "${pL}") // cyclic reference right ); ConfigurablePropertyResolver pr = new PropertySourcesPropertyResolver(ps); assertThat(pr.getProperty("p1"), equalTo("v1")); assertThat(pr.getProperty("p2"), equalTo("v2")); assertThat(pr.getProperty("p3"), equalTo("v1:v2")); assertThat(pr.getProperty("p4"), equalTo("v1:v2")); try { pr.getProperty("p5"); } catch (IllegalArgumentException ex) { assertThat(ex.getMessage(), containsString( "Could not resolve placeholder 'bogus' in string value \"${p1}:${p2}:${bogus}\"")); } assertThat(pr.getProperty("p6"), equalTo("v1:v2:def")); try { pr.getProperty("pL"); } catch (StackOverflowError ex) { // no explicit handling for cyclic references for now } } @Test public void ignoreUnresolvableNestedPlaceholdersIsConfigurable() { MutablePropertySources ps = new MutablePropertySources(); ps.addFirst(new MockPropertySource() .withProperty("p1", "v1") .withProperty("p2", "v2") .withProperty("p3", "${p1}:${p2}:${bogus:def}") // unresolvable w/ default .withProperty("p4", "${p1}:${p2}:${bogus}") // unresolvable placeholder ); ConfigurablePropertyResolver pr = new PropertySourcesPropertyResolver(ps); assertThat(pr.getProperty("p1"), equalTo("v1")); assertThat(pr.getProperty("p2"), equalTo("v2")); assertThat(pr.getProperty("p3"), equalTo("v1:v2:def")); // placeholders nested within the value of "p4" are unresolvable and cause an // exception by default try { pr.getProperty("p4"); } catch (IllegalArgumentException ex) { assertThat(ex.getMessage(), containsString( "Could not resolve placeholder 'bogus' in string value \"${p1}:${p2}:${bogus}\"")); } // relax the treatment of unresolvable nested placeholders pr.setIgnoreUnresolvableNestedPlaceholders(true); // and observe they now pass through unresolved assertThat(pr.getProperty("p4"), equalTo("v1:v2:${bogus}")); // resolve[Nested]Placeholders methods behave as usual regardless the value of // ignoreUnresolvableNestedPlaceholders assertThat(pr.resolvePlaceholders("${p1}:${p2}:${bogus}"), equalTo("v1:v2:${bogus}")); try { pr.resolveRequiredPlaceholders("${p1}:${p2}:${bogus}"); } catch (IllegalArgumentException ex) { assertThat(ex.getMessage(), containsString( "Could not resolve placeholder 'bogus' in string value \"${p1}:${p2}:${bogus}\"")); } } interface SomeType { } static class SpecificType implements SomeType { } }
getProperty返回变量在Environment中对应的值
对带有${}占位符的文本解析算法如下
protected String parseStringValue( String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) { StringBuilder buf = new StringBuilder(strVal); int startIndex = strVal.indexOf(this.placeholderPrefix); while (startIndex != -1) { int endIndex = findPlaceholderEndIndex(buf, startIndex); if (endIndex != -1) { String placeholder = buf.substring(startIndex + this.placeholderPrefix.length(), endIndex); String originalPlaceholder = placeholder; if (!visitedPlaceholders.add(originalPlaceholder)) { throw new IllegalArgumentException( "Circular placeholder reference '" + originalPlaceholder + "' in property definitions"); } // Recursive invocation, parsing placeholders contained in the placeholder key. placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); // Now obtain the value for the fully resolved key... String propVal = placeholderResolver.resolvePlaceholder(placeholder); if (propVal == null && this.valueSeparator != null) { int separatorIndex = placeholder.indexOf(this.valueSeparator); if (separatorIndex != -1) { String actualPlaceholder = placeholder.substring(0, separatorIndex); String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length()); propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder); if (propVal == null) { propVal = defaultValue; } } } if (propVal != null) { // Recursive invocation, parsing placeholders contained in the // previously resolved placeholder value. propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders); buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal); if (logger.isTraceEnabled()) { logger.trace("Resolved placeholder '" + placeholder + "'"); } startIndex = buf.indexOf(this.placeholderPrefix, startIndex + propVal.length()); } else if (this.ignoreUnresolvablePlaceholders) { // Proceed with unprocessed value. startIndex = buf.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); } else { throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "'" + " in string value \"" + strVal + "\""); } visitedPlaceholders.remove(originalPlaceholder); } else { startIndex = -1; } } return buf.toString();
发表评论
-
支持不同运行环境下的属性处理--Environment 特性
2013-07-14 01:47 4124介绍 Environment架构是spring 3.1版本引 ... -
支持开发、测试、生产环境下bean配置切换的profiles特性
2013-07-11 12:31 5634介绍 Bean definition profiles ... -
方法缓存
2013-07-10 18:20 4246介绍 spring3.1之后提供了方法的缓存支持,透明的将 ... -
基于xml schema的扩展标签
2013-07-09 13:34 1573xml schema是spring 2.0版本之后引入的,在之 ... -
BeanDefinition数据流
2013-07-08 19:41 1864BeanDefinition是Spring配置文件中bean定 ... -
bean的创建周期回调
2013-06-29 16:24 1559初始化回调 实现org.springframework ... -
Scope实现原理
2013-06-27 18:02 3450内置Scope分类 Singleton 每个IOC容器对一个b ... -
MethodInjection 动态方法替换原理
2013-06-21 14:45 1436singleton实例依赖于prototy ... -
AbstractBeanFactory获取bean周期
2013-05-11 22:58 935AbstractBeanFactory是IOC容器实现的骨 ...
相关推荐
在Java开发中,有时我们需要处理Word文档,例如批量替换文档中的特定占位符文本,这在生成报告或自定义模板时非常常见。标题"java替换word占位符.zip"和描述"java 替换word 占位符"都指向了这个应用场景。在Java中...
Java字符串中${}或者{}等占位符替换工具类 Java字符串中${}或者{}等占位符替换工具类是一个功能强大且实用的工具类,它可以将Java字符串中的占位符依次替换为指定的值。该工具类的主要功能是实现占位符的替换,即将...
主要分为五种类型:文本占位符、表格占位符、图表占位符、媒体占位符和图片占位符。这些占位符帮助用户规范幻灯片的布局和内容组织。 文本占位符是其中最常见的一种,它允许用户在指定区域内添加文本内容。在幻灯片...
MyBatis允许在XML映射文件或注解中使用#{param}作为占位符,Hibernate则支持HQL(Hibernate Query Language)和Criteria API,它们都提供了更强大的动态查询能力。 例如,在MyBatis中,你可以这样写: ```xml ...
这表明该工具可能具有用户友好的界面,允许用户定义占位符规则和对应的替换值,或者它可能支持解析特定格式的文件(如JSON、XML或CSV),自动识别并处理其中的占位符。 至于压缩包内的“占位符替换工具”,很可能是...
在iOS开发中,`UITextField` 是用户输入文本的常见组件,它的占位符(placeholder)功能可以帮助用户理解输入框的目的。本教程将详细介绍如何自定义`UITextField`的占位符颜色、大小,并实现当用户开始编辑时占位符...
4. **支持多种占位符**:不同的数据库系统可能使用不同类型的占位符,如MySQL的`?`,Python的`%s`,或者Java的`PreparedStatement`的`?`。工具应能处理这些差异。 5. **错误处理**:如果占位符未找到对应的值,或者...
1. **占位符显示**:`LYYTextView` 在没有用户输入时,会在文本框内显示预设的占位符文本,为用户提供输入提示。 2. **占位符颜色自定义**:通过设置属性,开发者可以改变占位符的颜色,使之与应用的界面风格保持...
1. 创建一个属性来存储占位符文本,例如`@property (nonatomic, copy) NSString *placeholder;` 2. 再创建一个属性用于显示占位符的UILabel,如`@property (nonatomic, strong) UILabel *placeholderLabel;` 3. 初始...
本篇将深入探讨占位符的原理、作用以及在实际代码中的实现方式,以"占位符行为源码"为例进行解析。 占位符的用途: 1. 提供清晰的指示:占位符文本是用户首次看到输入框时看到的第一行文本,能直观地告知用户预期的...
它通过简单的语法和丰富的选项,可以生成与指定尺寸相符的占位符图片,显示为灰色背景和白色的文本,从而帮助开发者快速预览页面布局。 **基本使用** 在HTML中,你可以通过以下方式调用Holder.js: ```html 这是...
3. 多类型支持:rnplaceholder可能支持不同类型的占位符,包括文本、图片、列表等,覆盖常见的UI组件。 4. 易于集成:作为React Native组件,rnplaceholder应该提供简洁的API,方便开发者快速将其集成到项目中。 5. ...
标题“maven 占位符打包”指的是在Maven的POM.xml文件中使用特定的占位符,以便在不同环境中替换为相应的配置值。这样可以实现代码的复用和环境的隔离,提高开发效率并降低出错的可能性。 描述中的“工程运行环境有...
### 属性占位符配置器:Spring框架中的高级配置机制 #### 一、概念解析 在Spring框架中,**属性占位符配置器**(Property Placeholder Configurator)是一种强大的配置工具,它允许开发者在配置文件中使用占位符来...
在用freemaker模板的时候,第一步都会将word转换为xml格式文件,解析成xml文件经常会出现(个别、很多)字段占位符、变量值被分离,被分离的字段少的还好能手动改改,字段多了能让你直接发疯,此代码脚本轻松解决...
基于SPRINGBOOT配置文件占位符过程解析 Spring Boot 配置文件占位符是指在 Spring Boot 项目中使用占位符来配置应用程序的各种参数。在 Spring Boot 中,默认的配置文件是 application.properties,通过在该文件中...
它通常包含一个可选的占位文本,即当用户未输入任何内容时显示的提示性文本。本示例着重于如何重写QLineEdit,以自定义占位文本的颜色、选中背景色以及光标闪烁效果。以下是对这些知识点的详细解释: 1. **占位文本...
MessageFormat的解析机制是通过applyPattern方法来实现的,该方法将模式字符串解析成一个Segment数组,每个Segment对象包含了模式字符串中的一个部分,包括占位符和文本部分。 在applyPattern方法中,...
".properties文件读取及占位符${...}替换源码解析" .properties文件读取及占位符${...}替换是Java开发中常用的技术,通过使用.properties文件来存储配置信息,并使用占位符${...}来替换这些配置信息。下面将详细...
JVFloat.js, jquery/zepto插件模拟 Matt D Smith占位符文本浮动 #JVFloat.js 演示插件 jQuery和Zepto插件来模拟 JVFloatLabeledTextField的行为,这基于从Matt的概念。 史密斯 。在博客上阅读更多关于的文章。请注意...