I have a controller that provides RESTful access to information:
#RequestMapping(method = RequestMethod.GET, value = Routes.BLAH_GET + "/{blahName}")
public ModelAndView getBlah(#PathVariable String blahName, HttpServletRequest request,
HttpServletResponse response) {
The problem I am experiencing is that if I hit the server with a path variable with special characters it gets truncated. For example:
http://localhost:8080/blah-server/blah/get/blah2010.08.19-02:25:47
The parameter blahName will be blah2010.08
However, the call to request.getRequestURI() contains all the information passed in.
Any idea how to prevent Spring from truncating the #PathVariable?
Try a regular expression for the #RequestMapping argument:
RequestMapping(method = RequestMethod.GET, value = Routes.BLAH_GET + "/{blahName:.+}")
This is probably closely related to SPR-6164. Briefly, the framework tries to apply some smarts to the URI interpretation, removing what it thinks are file extensions. This would have the effect of turning blah2010.08.19-02:25:47 into blah2010.08, since it thinks the .19-02:25:47 is a file extension.
As described in the linked issue, you can disable this behaviour by declaring your own DefaultAnnotationHandlerMapping bean in the app context, and setting its useDefaultSuffixPattern property to false. This will override the default behaviour, and stop it molesting your data.
Spring considers that anything behind the last dot is a file extension such as .jsonor .xml and truncate it to retrieve your parameter.
So if you have /{blahName}:
/param, /param.json, /param.xml or /param.anything will result in a param with value param
/param.value.json, /param.value.xml or /param.value.anything will result in a param with value param.value
If you change your mapping to /{blahName:.+} as suggested, any dot, including the last one, will be considered as part of your parameter:
/param will result in a param with value param
/param.json will result in a param with value param.json
/param.xml will result in a param with value param.xml
/param.anything will result in a param with value param.anything
/param.value.json will result in a param with value param.value.json
...
If you don't care of extension recognition, you can disable it by overriding mvc:annotation-driven automagic:
<bean id="handlerMapping"
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="contentNegotiationManager" ref="contentNegotiationManager"/>
<property name="useSuffixPatternMatch" value="false"/>
</bean>
So, again, if you have /{blahName}:
/param, /param.json, /param.xml or /param.anything will result in a param with value param
/param.value.json, /param.value.xml or /param.value.anything will result in a param with value param.value
Note: the difference from the default config is visible only if you have a mapping like /something.{blahName}. See Resthub project issue.
If you want to keep extension management, since Spring 3.2 you can also set the useRegisteredSuffixPatternMatch property of RequestMappingHandlerMapping bean in order to keep suffixPattern recognition activated but limited to registered extension.
Here you define only json and xml extensions:
<bean id="handlerMapping"
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="contentNegotiationManager" ref="contentNegotiationManager"/>
<property name="useRegisteredSuffixPatternMatch" value="true"/>
</bean>
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="false"/>
<property name="favorParameter" value="true"/>
<property name="mediaTypes">
<value>
json=application/json
xml=application/xml
</value>
</property>
</bean>
Note that mvc:annotation-driven accepts now a contentNegotiation option to provide a custom bean but the property of RequestMappingHandlerMapping has to be changed to true (default false) (cf. https://jira.springsource.org/browse/SPR-7632).
For that reason, you still have to override all the mvc:annotation-driven configuration. I opened a ticket to Spring to ask for a custom RequestMappingHandlerMapping: https://jira.springsource.org/browse/SPR-11253. Please vote if you are interested in.
While overriding, be careful to consider also custom Execution management overriding. Otherwise, all your custom Exception mappings will fail. You will have to reuse messageCoverters with a list bean:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean" />
<util:list id="messageConverters">
<bean class="your.custom.message.converter.IfAny"></bean>
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"></bean>
<bean class="org.springframework.http.converter.StringHttpMessageConverter"></bean>
<bean class="org.springframework.http.converter.ResourceHttpMessageConverter"></bean>
<bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"></bean>
<bean class="org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter"></bean>
<bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"></bean>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean>
</util:list>
<bean name="exceptionHandlerExceptionResolver"
class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver">
<property name="order" value="0"/>
<property name="messageConverters" ref="messageConverters"/>
</bean>
<bean name="handlerAdapter"
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="webBindingInitializer">
<bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
<property name="conversionService" ref="conversionService" />
<property name="validator" ref="validator" />
</bean>
</property>
<property name="messageConverters" ref="messageConverters"/>
</bean>
<bean id="handlerMapping"
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
</bean>
I implemented, in the open source project Resthub that I am part of, a set of tests on these subjects: see https://github.com/resthub/resthub-spring-stack/pull/219/files and https://github.com/resthub/resthub-spring-stack/issues/217
Everything after the last dot is interpreted as file extension and cut off by default.
In your spring config xml you can add DefaultAnnotationHandlerMapping and set useDefaultSuffixPattern to false (default is true).
So open your spring xml mvc-config.xml (or however it is called) and add
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="useDefaultSuffixPattern" value="false" />
</bean>
Now your #PathVariable blahName (and all other, too) should contain the full name including all dots.
EDIT: Here is a link to the spring api
Using the correct Java configuration class :
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter
{
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer)
{
configurer.favorPathExtension(false);
}
#Override
public void configurePathMatch(PathMatchConfigurer configurer)
{
configurer.setUseSuffixPatternMatch(false);
}
}
I also ran into the same issue, and setting the property to false didn't help me either. However, the API says:
Note that paths which include a ".xxx" suffix or end with "/" already
will not be transformed using the default suffix pattern in any case.
I tried adding "/end" to my RESTful URL, and the problem went away. I'm not please with the solution, but it did work.
BTW, I don't know what the Spring designers were thinking when they added this "feature" and then turned it on by default. IMHO, it should be removed.
I resolved by this hack
1) Added HttpServletRequest in #PathVariable like below
#PathVariable("requestParam") String requestParam, HttpServletRequest request) throws Exception {
2) Get the URL directly (At this level no truncation) in the request
request.getPathInfo()
Spring MVC #PathVariable with dot (.) is getting truncated
adding the ":.+" worked for me, but not until I removed outer curly brackets.
value = {"/username/{id:.+}"} didn't work
value = "/username/{id:.+}" works
Hope I helped someone :]
I just ran into this and the solutions here didn't generally work as I expected.
I suggest using a SpEL expression and multiple mappings, e.g.
#RequestMapping(method = RequestMethod.GET,
value = {Routes.BLAH_GET + "/{blahName:.+}",
Routes.BLAH_GET + "/{blahName}/"})
The file extension problem only exists if the parameter is in the last part of the URL. Change
#RequestMapping(method = RequestMethod.GET, value = Routes.BLAH_GET + "/{blahName}")
to
#RequestMapping(
method = RequestMethod.GET, value = Routes.BLAH_GET + "/{blahName}/safe")
and all will be well again-
If you can edit the address that requests are sent to, simple fix would be to add a trailing slash to them (and also in the #RequestMapping value):
/path/{variable}/
so the mapping would look like:
RequestMapping(method = RequestMethod.GET, value = Routes.BLAH_GET + "/{blahName}/")
See also Spring MVC #PathVariable with dot (.) is getting truncated.
//in your xml dispatcher add this property to your default annotation mapper bean as follow
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="alwaysUseFullPath" value="true"></property>
</bean>
The problem that you are facing is due to spring interpreting the last part of the uri after the dot (.) as a file extension like .json or .xml . So when spring tries to resolve the path variable it simply truncates the rest of the data after it encounters a dot (.) at the end of the uri.
Note: also this happens only if you keep the path variable at the end of the uri.
For example consider uri : https://localhost/example/gallery.df/link.ar
#RestController
public class CustomController {
#GetMapping("/example/{firstValue}/{secondValue}")
public void example(#PathVariable("firstValue") String firstValue,
#PathVariable("secondValue") String secondValue) {
// ...
}
}
In the above url firstValue = "gallery.df" and secondValue="link" , the last bit after the . gets truncated when the path variable gets interpreted.
So, to prevent this there is two possible ways:
1.) Using a regexp mapping
Use a regex at the end part of mapping
#GetMapping("/example/{firstValue}/{secondValue:.+}")
public void example(
#PathVariable("firstValue") String firstValue,
#PathVariable("secondValue") String secondValue) {
//...
}
By using + , we indicate any value after the dot will also be part of the path variable.
2.) Adding a slash at the end of our #PathVariable
#GetMapping("/example/{firstValue}/{secondValue}/")
public void example(
#PathVariable("firstValue") String firstValue,
#PathVariable("secondValue") String secondValue) {
//...
}
This will enclose our second variable protecting it from Spring’s default behavior.
3) By overriding Spring's default webmvc configuration
Spring provides ways to override the default configurations that gets imported by using the annotations #EnableWebMvc.We can customize the Spring MVC configuration by declaring our own DefaultAnnotationHandlerMapping bean in the application context and setting its useDefaultSuffixPattern property to false.
Example:
#Configuration
public class CustomWebConfiguration extends WebMvcConfigurationSupport {
#Bean
public RequestMappingHandlerMapping
requestMappingHandlerMapping() {
RequestMappingHandlerMapping handlerMapping
= super.requestMappingHandlerMapping();
handlerMapping.setUseSuffixPatternMatch(false);
return handlerMapping;
}
}
Keep in mind that overriding this default configuration, affects all urls.
Note : here we are extending the WebMvcConfigurationSupport class to override the default methods. There is one more way to override the deault configurations by implementing the WebMvcConfigurer interface.
For more details on this read : https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/config/annotation/EnableWebMvc.html
Java based configuration solution to prevent truncation (using a not-deprecated class):
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
#Configuration
public class PolRepWebConfig extends WebMvcConfigurationSupport {
#Override
#Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
final RequestMappingHandlerMapping handlerMapping = super
.requestMappingHandlerMapping();
// disable the truncation after .
handlerMapping.setUseSuffixPatternMatch(false);
// disable the truncation after ;
handlerMapping.setRemoveSemicolonContent(false);
return handlerMapping;
}
}
Source: http://www.javacodegeeks.com/2013/01/spring-mvc-customizing-requestmappinghandlermapping.html
UPDATE:
I realized having some problems with Spring Boot auto-configuration when I used the approach above (some auto-configuration doesn't get effective).
Instead, I started to use the BeanPostProcessor approach. It seemed to be working better.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
private static final Logger logger = LoggerFactory
.getLogger(MyBeanPostProcessor.class);
#Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof RequestMappingHandlerMapping) {
setRemoveSemicolonContent((RequestMappingHandlerMapping) bean,
beanName);
setUseSuffixPatternMatch((RequestMappingHandlerMapping) bean,
beanName);
}
return bean;
}
private void setRemoveSemicolonContent(
RequestMappingHandlerMapping requestMappingHandlerMapping,
String beanName) {
logger.info(
"Setting 'RemoveSemicolonContent' on 'RequestMappingHandlerMapping'-bean to false. Bean name: {}",
beanName);
requestMappingHandlerMapping.setRemoveSemicolonContent(false);
}
private void setUseSuffixPatternMatch(
RequestMappingHandlerMapping requestMappingHandlerMapping,
String beanName) {
logger.info(
"Setting 'UseSuffixPatternMatch' on 'RequestMappingHandlerMapping'-bean to false. Bean name: {}",
beanName);
requestMappingHandlerMapping.setUseSuffixPatternMatch(false);
}
}
Inspired from: http://ronaldxq.blogspot.com/2014/10/spring-mvc-setting-alwaysusefullpath-on.html
if you are sure that your text will not match any of default extensions you can use below code:
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseRegisteredSuffixPatternMatch(true);
}
}
My preferable solution to prevent the Spring MVC #PathVariable to get truncated is to add trailing slash at the end of the path variable.
For example:
#RequestMapping(value ="/email/{email}/")
So, the request will look like:
http://localhost:8080/api/email/test#test.com/
Related
If my understanding is correct, you can add/remove params for saveEmployee() freely. For example, when you add "loc" as follows, saveEmployee() receives the "non-null object" when the event happens. And the same goes for queryParams.
#Controller
public class Employee {
#RequestMapping("/save")
public void saveEmployee(Locale loc,
#RequestParam Map<String, String> queryParams) {
// saving employee
}
}
How could this method receive non-null Locale object by just adding a param "loc" here?
I would like to know the logic behind this.
Spring does it for you by using LocaleResolver or LocaleContextResolver, for the current request locale, determined by the most specific locale resolver available, in effect, the configured LocaleResolver / LocaleContextResolver in an MVC environment.
21.3.3 Defining #RequestMapping handler methods
An #RequestMapping handler method can have a very flexible signatures. The supported method arguments and return values are described in the following section. Most arguments can be used in arbitrary order with the only exception of BindingResult arguments.
Supported method argument types
java.util.Locale for the current request locale, determined by the most specific locale resolver available, in effect, the configured LocaleResolver / LocaleContextResolver in an MVC environment.
Spring's DispatcherServlet which forwards request from client to your controller gives you that parameters. In order to do that, it search the object from ApplicationContext to which bean(Controller) belongs.
Spring looks at the method arguments, their types and annotations, then determines if it can provide an object of that type/annotation.
If it cannot, it'll throw an exception, otherwise it will call the method with the object it decided fits the type/annotation.
For the list of supported types/annotations, read the documentation:
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-arguments
As you can see, java.util.Locale is listed.
I think you need xml setting for your locale
Do you want to try the followning xml setting
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
<property name="defaultLocale" value="en" />
</bean>
<mvc:interceptors>
<bean id="localeChangeInterceptor"
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="language" />
</bean>
</mvc:interceptors>
In a Spring Security 3.2 based application I have a explicit configured UsernamePasswordAuthenticationFilter, that need an reference to the sessionAuthenticationStrategy (in order to invoke .onAuthentication).*
The sessionAuthenticationStrategy is the default one created by <security:http> (HttpSecurityBeanDefinitionParser).
My question: Is how can I get an reference to the SessionAuthenticationStrategy without configuring the complete SessionAuthenticationStrategy explicite, so that I can inject this reference in XML configuration?
<security:http auto-config="false" use-expressions="true"
entry-point-ref="loginUrlAuthenticationEntryPoint"
access-decision-manager-ref="httpAccessDecisionManager">
...
<security:custom-filter
ref="usernamePasswordAuthenticationFilter"
position="FORM_LOGIN_FILTER"/>
...
</security:http>
...
<bean id="usernamePasswordAuthenticationFilter"
class=" o.s.scurity.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="sessionAuthenticationStrategy" ref="????"> <!-- ?? ->
...
</bean>
*my real UsernamePasswordAuthenticationFilter is a customized subclass, but that should not matter for this question
I have had a look at the HttpSecurityBeanDefinitionParser (and the HttpConfigurationBuilder.createSessionManagementFilters()) that is the class responsible to parse the security:http tag and for creating of SessionAuthenticationStrategy bean.
Therefore I know that Spring Security 3.2.5.RELEASE create (in my configuration) a CompositeSessionAuthenticationStrategy bean and uses this as session strategy. This bean will get the default name: org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy#0
So my current workaround is to have a reference to this bean, by its name:
<bean id="usernamePasswordAuthenticationFilter"
class=" o.s.scurity.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="sessionAuthenticationStrategy">
<ref
bean="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy#0"/>
</property>
...
</bean>
This workaround has some serious limitations:
when a newer version of spring security works in an other way (creating an other bean) then it will fail.
when there is an other CompositeSessionAuthenticationStrategy thats name is created with ReaderContext.generateBeanName then this approach may fail, because of #0 maybe become #1 (depends on the order in which the beans are created)
I'm afraid there is no obvious way to get it.
But all the examples in Spring-Security reference manual are coherent on that : you should not even want to get it : all show an explicit SessionAuthenticationStrategy injected in the UserNamePasswordAuthenticationFilter and if appropriate in the SessionManagementFilter.
According to the javadocs of these 2 classes, the default SessionAuthenticationStrategy are :
SessionFixationProtectionStrategy for Servlet < 3.1
ChangeSessionIdAuthenticationStrategy for Servlet 3.1+
So the correct way is to create a bean implementing SessionAuthenticationStrategy either one of the above defaults, or another implementation if you have special needs and use it wherever you need to.
Of course, it is always possible to use reflection to access private members of Spring security implementation classes, but you know it is bad and comes with high risk of getting broken on next release of Spring security.
When working with JavaConfig (I'm afraid is not your case) you can get a reference by doing
http.getConfigurer(SessionManagementConfigurer.class).init(http);
http.getSharedObject(SessionAuthenticationStrategy.class);
Expanding on Ralph's answer, you can use a FactoryBean to get a reference to the AuthenticationStrategy.
public class SessionAuthenticationStrategyFactoryBean implements BeanFactoryAware, FactoryBean<SessionAuthenticationStrategy> {
private BeanFactory beanFactory;
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
#Override
public SessionAuthenticationStrategy getObject() throws Exception {
final CompositeSessionAuthenticationStrategy sas = beanFactory.getBean(CompositeSessionAuthenticationStrategy.class);
return sas;
}
#Override
public Class<?> getObjectType() {
return SessionAuthenticationStrategy.class;
}
#Override
public boolean isSingleton() {
return true;
}
}
... and make it available on you XML configuration:
<bean id="sas" class="com.example.SessionAuthenticationStrategyFactoryBean" />
<bean id="usernamePasswordAuthenticationFilter"
class=" o.s.scurity.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="sessionAuthenticationStrategy" ref="sas">
...
</bean>
I have a class extending the AbstractExcelView class of Spring which renders an XML file. Within this class, I am injecting my Service bean for use. I am autowiring and component scanning my classes, and I would like to also do the same with this view class, but I am not clear how (or if it can be done). Here's what I'm trying to annotate from the config:
<bean id="export.xls" class="com.my.views.ReportExcelView">
<property name="url">
<value>/excel/template</value>
</property>
<property name="service" ref="testingService"/>
I am able to annotate the class with #Component, and the service with #Autowired, but I don't know of a strategy to annotate the URL. What I'd really like to do is condition it within the buildExcelWorkbook() call (based on something in the request), but it seems there is some initialization done before this, as I get an error trying to use my excel template with this method that indicates it does not have a handle to the Excel sheet. Any recommendations?
So your ReportExcelView probably looks like this right now. Make sure you use #Resource to wire a simple String.
package com.ex.springbasicex.view;
#Component
public class ReportExcelView{
#Resource(name="myUrl")
String url;
#Autowired
Service service;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
Your context config probably should be like what is below using scanning. Below is how to set the myUrl String resource.
<context:component-scan base-package="com.ex.springbasicex.view" />
<bean id="myUrl" class="java.lang.String" >
<constructor-arg>
<value>/excel/template</value>
</constructor-arg>
</bean>
My existing Spring Web MVC application has the following handler mapping in the Controller.
#RequestMapping(method = RequestMethod.GET, value = "/welcome")
I trigger the following requesthttp://www.example.com/welcomeand this works fine.
The problem is
http://www.example.com/welcome.check.blah
also works!!!
Also, a HTTP GET request URL to the application with script tag is getting redisplayed though it fails the authorization.
Example http://www.example.com/welcome<script>alert("hi")</script> gets redisplayed as such in the browser window and as a result of my authorization logic "Not authorized" message is displayed.
I wonder if this is a security issue and should I need do any encoding/filtering in the code?
This behavior is due to the option useSuffixPatternMatch which is true by default inside the RequestMappingHandlerMapping (I assume you use Spring MVC 3.1).
useSuffixPatternMatch :
Whether to use suffix pattern match (".*") when matching patterns to requests. If enabled a method mapped to "/users" also matches to "/users.*". The default value is "true".
To set useSuffixPatternMatch to false, the easiest way is to use #Configuration :
#Configuration
#EnableWebMvc
public class Api extends WebMvcConfigurationSupport {
#Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping mapping = super.requestMappingHandlerMapping();
mapping.setUseSuffixPatternMatch(false);
return mapping;
}
}
In current Spring Java config, there is a slightly easier way to configure the same thing:
#Configuration
public class DispatcherConfig extends WebMvcConfigurationSupport {
#Override
protected void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false);
}
}
When you use Spring to request a mapping of that type (i.e. "/anything") Spring actually maps your controller to several URLs:
/welcome
/welcome.*
/welcome/
To prevent this - either be more specific when you RequestMapping (i.e. /welcome.htm ), or manually map the URL to controller in your Xml config:
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/welcome">YourControllerBean</prop>
</props>
</property>
</bean>
Cheers, Pete
You can also restrict this in the web.xml by mentioning the url pattern. Instead of giving "/", you can mention "/.htm" in your web.xml.
Something like
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/application/*.htm</url-pattern>
</servlet-mapping>
You can use the useDefaultSuffixPattern property.
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="useDefaultSuffixPattern" value="false" />
</bean>
Also refer URL Pattern Restricting in SPRING MVC
Starting from Spring framework 5.3 useDefaultSuffixPattern is deprecated and turned off by default. Spring upgrade notes, section "Use of Path Extensions Deprecated in Spring MVC"
I'm using PropertiesFactoryBean to load properties from a typical Properties file. Is there anyway to get Spring to automatically trim trailing white space from the prop value?
As this can often be a source of confusion when using Spring Boot, I want to add that you do not need XML configuration to provide your own PropertyPlaceholderConfigurer.
Simply put this in your main class:
#Bean
public static PropertySourcesPlaceholderConfigurer createPropertyConfigurer()
{
PropertySourcesPlaceholderConfigurer propertyConfigurer = new PropertySourcesPlaceholderConfigurer();
propertyConfigurer.setTrimValues(true);
return propertyConfigurer;
}
This is sufficient for trimming the values from application.properties.
You can customize the Properties loading functionality by passing in a custom PropertiesPersister into your PropertiesFactoryBean configuration. The PropertiesPersister instance is used by the PropertiesFactoryBean to parse the Properties file data. The default implementation follows the native parsing of java.util.Properties. You can customize the parsing logic by providing your own implementation of the PropertiesPersister interface.
As Chad said, Spring solved this problem with version 4.3RC1. But you need to manually set on trim function with parameter "trimValues" like so (default if "false"):
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="trimValues" value="true"/>
<property name="locations">
<list>
...
</list>
</property>
I do not found any documentation about this but I deduce it from Spring API.
With latest Spring version(4.3+), you can simply call setTrimValues() with true when you create PropertySourcesPlaceholderConfigurer bean in your configuration. That will remove any extra leading or trailing spaces from the value you got from the properties file.
You can define your own property configurer:
package your.company.package;
public class TrimPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
#Override
protected String resolvePlaceholder( String placeholder, Properties props ) {
String value = super.resolvePlaceholder( placeholder, props );
return (value != null ? value.trim() : null );
}
}
Then you must define it in your bean_config.xml
<bean id="applicationProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="classpath:main.properties" />
</bean>
<bean id="trimPropertyPlaceholderConfigurer" class="your.company.package.TrimPropertyPlaceholderConfigurer">
<property name="properties" ref="applicationProperties" />
</bean>
Another way if you're using #Value annotations to set the properties into the fields:
#Value( value = "#{applicationProperties['my.app.property'].trim()}" )
NullPointerException is thrown if the property doesn't exists in the file
One easy way to do it would be to "hack" the spEl Expression to force the use of the String.trim() function.
Let's say you have a property test.myvalue equal to azerty (with trailing spaces) in the application.properties file, then you could inject this property in your class like this :
#Value("#{'${test.myvalue}'.trim()}")
String myvalue;
The resulting myvalue will be equal to azerty (no trailing spaces) once injected in your class.
Obviously this trimming won't be set globally to all injected values in your app, and you'll have to do it to all injected value, but I think this approach gives more flexibility.