<p dir="auto"><strong><a href="https://jira.spring.io/secure/ViewProfile.jspa?name=ericacm" rel="nofollow">Eric Pederson</a></strong> opened <strong><a href="https://jira.spring.io/browse/SPR-7164?redirect=false" rel="nofollow">SPR-7164</a></strong> and commented</p>
<p dir="auto">Using <code class="notranslate">@Bean</code> style bean definitions with placeholders introduces a lot of noise.   For example, replacing</p>
<p dir="auto">&lt;bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"&gt;<br>
&lt;property name="driverClassName" value="${dataSource.driverClassName:com.mysql.jdbc.Driver}"/&gt;<br>
&lt;property name="url" value="${dataSource.url:jdbc:mysql://localhost:3306/auth}"/&gt;<br>
&lt;property name="username" value="${dataSource.username:root}"/&gt;<br>
&lt;property name="password" value="${dataSource.password:root}"/&gt;<br>
&lt;/bean&gt;</p>
<p dir="auto">with a <code class="notranslate">@Bean</code> and <code class="notranslate">@Values</code> you get</p>
<p dir="auto"><code class="notranslate">@Value</code>("${dataSource.driverClassName:com.mysql.jdbc.Driver}")<br>
var driverClassName: String = null<br>
<code class="notranslate">@Value</code>("${dataSource.url:jdbc:mysql://localhost:3306/auth}")<br>
var url: String = null<br>
<code class="notranslate">@Value</code>("${dataSource.username:root}")<br>
var username: String = null<br>
<code class="notranslate">@Value</code>("${dataSource.password:root}")<br>
var password: String = null</p>
<p dir="auto"><code class="notranslate">@Bean</code>(destroyMethod = "close")<br>
def dataSource: DataSource = {<br>
var dataSource: BasicDataSource = new BasicDataSource<br>
dataSource.setDriverClassName(driverClassName)<br>
dataSource.setUrl(url)<br>
dataSource.setUsername(username)<br>
dataSource.setPassword(password)<br>
return dataSource<br>
}</p>
<p dir="auto">Yikes!</p>
<p dir="auto">The PropertyPlaceholderConfigurer knows</p>
<ul dir="auto">
<li>what the merged properties are</li>
<li>what the SystemPropertiesMode is</li>
<li>what the placeholder prefix, suffix and default delimiter are</li>
<li>about handling default values (new in 3.0 thanks!!)</li>
</ul>
<p dir="auto">What I want to be able to do is something like (where ppc is a reference to the PropertyPlaceholderConfigurer)</p>
<p dir="auto"><code class="notranslate">@Bean</code>(destroyMethod = "close")<br>
def dataSource: DataSource = {<br>
var dataSource: BasicDataSource = new BasicDataSource<br>
dataSource.setDriverClassName(ppc.resolve("${dataSource.driverClassName:com.mysql.jdbc.Driver}"))<br>
dataSource.setUrl(ppc.resolve("${dataSource.url:jdbc:mysql://localhost:3306/auth}"))<br>
dataSource.setUsername(ppc.resolve("${dataSource.username:root}"))<br>
dataSource.setPassword(ppc.resolve("${dataSource.password:root}"))<br>
return dataSource<br>
}</p>
<p dir="auto">There are workarounds but they involve subclassing 2 or 3 Spring classes to get access to protected or private data, and other unDRY unpleasantness.  See example below.</p>
<p dir="auto">Thanks,</p>
<p dir="auto">// Example</p>
<p dir="auto"><code class="notranslate">@Configuration</code><br>
class AppConfig {<br>
var log: Logger = Logger.getLogger(this.getClass().getName())</p>
<p dir="auto"><code class="notranslate">@Bean</code><br>
def propertyPlaceholderConfigurer: PropertyPlaceholderConfigurer = {<br>
val resources = new ArrayBuffer[Resource]<br>
if (AppConfig.environment != "development") resources.append(new ClassPathResource(AppConfig.environment + "-environment.properties"))<br>
if (AppConfig.server != null) resources.append(new ClassPathResource(AppConfig.server + "-server.properties"))<br>
val propertiesSupport = new PropertiesLoaderSupport {<br>
def merge: Properties = super.mergeProperties<br>
}<br>
propertiesSupport.setIgnoreResourceNotFound(true)<br>
propertiesSupport.setLocations(resources.toArray)<br>
AppConfig.mergedProperties = propertiesSupport.merge<br>
val ppc = AppConfig.propertyPlaceholderConfigurer<br>
ppc.setProperties(AppConfig.mergedProperties)<br>
ppc.setSystemPropertiesMode(PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_OVERRIDE)<br>
return ppc<br>
}</p>
<p dir="auto">}</p>
<p dir="auto">object AppConfig {<br>
var propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer {<br>
def resolve(placeholder: String) = super.resolvePlaceholder(placeholder, mergedProperties, PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_OVERRIDE)<br>
}<br>
var mergedProperties: Properties = null<br>
var placeholderResolver = new PlaceholderResolver {<br>
def resolvePlaceholder(placeholderName: String) = {<br>
propertyPlaceholderConfigurer.resolve(placeholderName)<br>
}<br>
}<br>
var placeholderHelper = new PropertyPlaceholderHelper("${", "}", ":", false)<br>
def resolve(placeholder: String) = placeholderHelper.replacePlaceholders(placeholder, placeholderResolver)</p>
<p dir="auto">var environmentName: String = null<br>
def environment: String = {<br>
if (environmentName == null) {<br>
val systemEnvironmentName = System.getProperty("config.environment")<br>
if (systemEnvironmentName != null) {<br>
environmentName = systemEnvironmentName<br>
} else {<br>
environmentName = "development"<br>
}<br>
}<br>
return environmentName<br>
}</p>
<p dir="auto">def server: String = System.getProperty("config.server")<br>
}</p>
<p dir="auto"><code class="notranslate">@Configuration</code><br>
class DataConfig {</p>
<p dir="auto"><code class="notranslate">@Bean</code>(destroyMethod = "close")<br>
def dataSource: DataSource = {<br>
var dataSource: BasicDataSource = null<br>
if (AppConfig.environment == "development") {<br>
dataSource = new BasicDataSource<br>
dataSource.setDriverClassName(AppConfig.resolve("${dataSource.driverClassName:com.mysql.jdbc.Driver}"))<br>
dataSource.setUrl(AppConfig.resolve("${dataSource.url:jdbc:mysql://localhost:3306/auth}"))<br>
dataSource.setUsername(AppConfig.resolve("${dataSource.username:root}"))<br>
dataSource.setPassword(AppConfig.resolve("${dataSource.password:root}"))<br>
} else {<br>
// Lookup via JNDI<br>
}<br>
return dataSource<br>
}<br>
}</p>
<hr>
<p dir="auto"><strong>Affects:</strong> 3.0.2</p>
<p dir="auto"><strong>Issue Links:</strong></p>
<ul dir="auto">
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="398078580" data-permission-text="Title is private" data-url="https://github.com/spring-projects/spring-framework/issues/8242" data-hovercard-type="issue" data-hovercard-url="/spring-projects/spring-framework/issues/8242/hovercard" href="https://github.com/spring-projects/spring-framework/issues/8242">#8242</a> org.springframework.beans.factory.config.PropertyPlaceholderConfigurer should add a public method 'parseStringValue' (<em><strong>"is duplicated by"</strong></em>)</li>
</ul>
<p dir="auto"><strong><a href="https://jira.spring.io/secure/ViewProfile.jspa?name=dgschwind" rel="nofollow">Doug Gschwind</a></strong> opened <strong><a href="https://jira.spring.io/browse/SPR-7585?redirect=false" rel="nofollow">SPR-7585</a></strong> and commented</p>
<p dir="auto">The RequestMapping annotation does a great job of helping the reader of a Spring MVC Controller understand which method(s) on that Controller will be invoked given a URI such as "/account", the HTTP method (e.g. GET or POST), as well as given the presence/absence of certain identified HTTP request parameters.</p>
<p dir="auto">I believe this implementation suffers from the following though, and hence could be improved to address these concerns :</p>
<ol dir="auto">
<li>These URIs embedded in a Spring MVC Controller are the source of Don't Repeat Yourself violations since that same URI has to be repeated elsewhere in say a JSP that has a hyperlink or a button that intends to invoke a given method on a given type of Controller when the hyperlink or button is clicked upon.</li>
<li>Spring MVC does not appear to have a facility to be able to generate a URL in say a JSP, that intends to invoke a given method on a given type of Controller. This is closely related to the first item.</li>
<li>By having these URIs sprinkled throughout your Spring MVC Controller classes, you have a little bit of research to do if you plan to introduce a new Controller that should have a distinct URI that it responds to. A text search through the source files would solve that problem, but that is slow and error prone. I understand the Spring MVC plugin for Eclipse has some support for this, but once again requires the human eye to review all the existing URIs to determine if there exists a collision or partial URI match of some sort.</li>
</ol>
<p dir="auto">I have tried to solve one and two by placing information in the Spring MVC descriptor, but that involves interrogating the ApplicationContext, and of course, my own custom code. I would like the framework to provide this support for me, so that I have to specify a URI for a Controller and Method, once and only once. If that were possible, I could make a URI change in one place, and the app would continue to run fine (assuming that change introduced no [partial] collisions) without any further effort.</p>
<p dir="auto">Thank you,</p>
<p dir="auto">Doug Gschwind</p>
<hr>
<p dir="auto"><strong>Affects:</strong> 3.0 GA</p>
<p dir="auto"><strong>Issue Links:</strong></p>
<ul dir="auto">
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="398095218" data-permission-text="Title is private" data-url="https://github.com/spring-projects/spring-framework/issues/10449" data-hovercard-type="issue" data-hovercard-url="/spring-projects/spring-framework/issues/10449/hovercard" href="https://github.com/spring-projects/spring-framework/issues/10449">#10449</a> Allow links to <code class="notranslate">@Controller</code> methods from views without hardcoding URLs (<em><strong>"duplicates"</strong></em>)</li>
</ul>