Page 1 of 2 12 LastLast
Results 1 to 10 of 14

Thread: Custom PropertyEditor for Resource

  1. #1
    Join Date
    Jul 2009
    Posts
    5

    Default Custom PropertyEditor for Resource

    Hi all:

    I'm trying to use a new type of resource for configuration files, that are environment-dependant. The idea is to use something like this:

    <property name="location" value="config:application.properties"/>

    where location would be a property of type Resource. The idea is to use these kind of resources in PropertyPlaceHolderConfigurers:

    Code:
        <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="location" value="config:application.properties"/>
        </bean>
    I'm trying to use the CustomEditorConfigurer so I can register a custom property editor that allows for a custom ResourceLoader to resolve resources, instead of using the Application Context's ResourceLoader:

    Code:
    	<bean id="configurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    		<property name="customEditors">
    			<map>
    				<entry key="org.springframework.core.io.Resource">
    					<bean class="org.springframework.core.io.ResourceEditor">
    					   <constructor-arg>
    					       <bean class="es.map.sgtic.ext.ove.core.config.ConfigResourceLoader"/>
    					   </constructor-arg>
    					</bean>
    				</entry>
    			</map>
    		</property>
    	</bean>
    The problem is that the custom PropertyEditor gets registered AFTER the Resource property is injected into the PropertyPlaceHolderConfigurer, so the Resource gets loaded using the Application Context instead of using the PropertyEditor that would allow me to override this behaviour.

    So my questions are:

    1) Is it possible to register a custom PropertyEditor before the Resource gets injected? If so, how?
    2) Am I missing some other way to achieve what I'm looking for? Is there another way to override the Application Context resource loading mechanism?

    Thanks in advance!

  2. #2
    Join Date
    Jun 2006
    Location
    The Netherlands
    Posts
    13,632

    Default

    You can set the order property on both BeanFactoryPostProcessors (CustomEditorConfigurer and PropertyPlaceholderConfigurer) and make sure yours gets executed first.

    Not sure if that will work because they are both BeanFactoryPostProcessors.
    Marten Deinum
    Java Consultant / Pragmatist / Open Source Enthousiast / Author


    Pro Spring MVC: With Web Flow
    Conspect

    Have you read the reference guide.
    Use the [ code ] tags, young padawan

  3. #3
    Join Date
    Jul 2009
    Posts
    5

    Default

    Hello Marten, thanks for your response:

    Quote Originally Posted by Marten Deinum View Post
    You can set the order property on both BeanFactoryPostProcessors (CustomEditorConfigurer and PropertyPlaceholderConfigurer) and make sure yours gets executed first.

    Not sure if that will work because they are both BeanFactoryPostProcessors.
    I'm afraid it doesn't work...

    The PropertyPlaceHolderConfigurer has its resource(s) injected before the BeanFactoryPostProcessors have a chance to execute their postProcessBeanFactory() method. AbstractApplicationContext's invokeBeanFactoryPostProcessors() calls the beanFactory.getBean() method, that injects the properties for the beans:

    Code:
    			if (isTypeMatch(postProcessorNames[i], PriorityOrdered.class)) {
    				priorityOrderedPostProcessors.add(beanFactory.getBean(postProcessorNames[i]));
    			}
    ...so the resource is injected before the custom PropertyEditor may be registered, independently of the postprocessor priority order.

    It seems that priority order in a postprocessor has no effect over a bean dependency injection, as dependency injection happens before postprocessor beans have its chance.

  4. #4
    Join Date
    May 2007
    Location
    Saint Petersburg, Russian Federation
    Posts
    1,189

    Default

    Spring uses the following algorithm:
    1. Create all bean factory post processors;
    2. Invoke target methods postProcessBeanFactory() on every post processor;


    So, the main principle here is that bean definition changes performed by one bean factory post processor doesn't affect another bean factory post processors (see org.springframework.context.support.AbstractApplic ationContext.invokeBeanFactoryPostProcessors())

    I can offer the following workaround:
    1. Create custom bean factory post processor class that serves as a wrapper for the PropertyPlaceholderConfigurer;
    2. When postProcessBeanFactory() method of that class is called it creates necessary Resource object on the basis of configured resource type/name;
    3. Custom post processor creates standard PropertyPlaceholderConfigurer and injects created resource to it;
    4. Custom post processor calls postProcessBeanFactory() method on the created PropertyPlaceholderConfigurer instance;


    You can implement a custom spring xml extension for that, i.e. you can use something like <resourceroperty-placeholder location="config:application.properties"/> instead of standard <contextroperty-placeholder location="config:application.properties"/>

  5. #5
    Join Date
    Jun 2006
    Location
    The Netherlands
    Posts
    13,632

    Default

    That is what I figured would happen (although the flow is a bit different then you are explaining here ). But the general idea is the same.

    Dependency injections happens AFTER the BeanFactoryPostProcessor have fired. BeanFactoryPostProcessors operate on BeanDefinitions as they fire there are no instances yet only configuration meta data.

    The simplest solution here would be to create a custom extension to the CustomEditorConfigurer which adds the PriorityOrdered interface, that way it is taken into account.

    You also might want to register a JIRA issue for this because IMHO this specific class was missed when they introduced the PriorityOrdered interface.
    Last edited by Marten Deinum; Jul 8th, 2009 at 06:34 AM.
    Marten Deinum
    Java Consultant / Pragmatist / Open Source Enthousiast / Author


    Pro Spring MVC: With Web Flow
    Conspect

    Have you read the reference guide.
    Use the [ code ] tags, young padawan

  6. #6
    Join Date
    Jul 2009
    Posts
    5

    Default

    Hello Marten, thank you very much for your response:

    Quote Originally Posted by Marten Deinum View Post
    That is what I figured would happen (although the flow is a bit different then you are explaining here ). But the general idea is the same.

    Dependency injections happens AFTER the BeanFactoryPostProcessor have fired. BeanFactoryPostProcessors operate on BeanDefinitions as they fire there are no instances yet only configuration meta data.
    I don't understand this... I've been debugging the ApplicationContext initilization process and the Resource gets created using the (previously registered) ResourceEditor, and injects it in the PropertyPlaceHolderConfigurer...

    I'm sure there's something I don't grasp here, but the call stack for the resource creation is this (sorry for dumping the stack trace, but I don't figure a better way of explaining :-) )

    Code:
    GenericApplicationContext(DefaultResourceLoader).getResource(String) line: 90	
    GenericApplicationContext.getResource(String) line: 179	
    ResourceEditor.setAsText(String) line: 74	
    TypeConverterDelegate.doConvertTextValue(Object, String, PropertyEditor) line: 382	
    TypeConverterDelegate.doConvertValue(Object, Object, Class, PropertyEditor) line: 358	
    TypeConverterDelegate.convertIfNecessary(String, Object, Object, Class, PropertyDescriptor, MethodParameter) line: 173	
    TypeConverterDelegate.convertIfNecessary(Object, Object, PropertyDescriptor) line: 138	
    BeanWrapperImpl.convertForProperty(Object, String) line: 386	
    DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).convertForProperty(Object, String, BeanWrapper, TypeConverter) line: 1289	
    DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).applyPropertyValues(String, BeanDefinition, BeanWrapper, PropertyValues) line: 1250	
    DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).populateBean(String, AbstractBeanDefinition, BeanWrapper) line: 1010	
    DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).doCreateBean(String, RootBeanDefinition, Object[]) line: 472	
    AbstractAutowireCapableBeanFactory$1.run() line: 409	
    AccessController.doPrivileged(PrivilegedAction<T>, AccessControlContext) line: not available [native method]	
    DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).createBean(String, RootBeanDefinition, Object[]) line: 380	
    AbstractBeanFactory$1.getObject() line: 264	
    DefaultListableBeanFactory(DefaultSingletonBeanRegistry).getSingleton(String, ObjectFactory) line: 222	
    DefaultListableBeanFactory(AbstractBeanFactory).doGetBean(String, Class, Object[], boolean) line: 261	
    DefaultListableBeanFactory(AbstractBeanFactory).getBean(String, Class, Object[]) line: 185	
    DefaultListableBeanFactory(AbstractBeanFactory).getBean(String) line: 164	
    GenericApplicationContext(AbstractApplicationContext).invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory) line: 515
    So the property gets injected and it doesn't get affected by the CustomEditorConfigurer afterwards (i'm afraid I get a bit lost in Spring core internals).

    Am I missing something here? Could you tell me to some resources where I can learn more about Spring Application Context initialization process?

    Quote Originally Posted by Marten Deinum View Post
    The simplest solution here would be to create a custom extension to the CustomEditorConfigurer which adds the PriorityOrdered interface, that way it is taken into account.
    I've tried this solution (creating a subclass that implements PriorirtyOrdered), and the result is the same... The problem is that the PropertyPlaceHolderConfigurer has its location resource already initialized in the beanFactory.getBean(), and the property doesn't get injected after invokeBeanFactoryPostProcessors

  7. #7
    Join Date
    Jun 2006
    Location
    The Netherlands
    Posts
    13,632

    Default

    The real problem here is that you want to post process a BeanFactoryPostProcessor with another BeanFactoryPostProcessor. I was hoping that making the CustomEditorConfigurer a PriorityOrdered instance would fix it (especially if it would run before the PPHC), but this is obviously not the case. It is quite difficult actually to make a BeanFactoryPostProcessor that will process other BeanFactoryPostProcessors.

    Also the problem runs even deeper than you might think the ResourceEditorRegistrar (used to register resource loaders etc.) is invoked and registered programmatically before everything else (check the prepareBeanFactoryMethod). Which is then used to set the properties on the PPHC (due to the getBean) before it is being post processed (as I stated this is quite difficult).

    The basic lifecylce is quite easy to understand

    1) Load and parse bean definition metadata (can be xml, classes, properties files)
    2) Register and execute BeanFactoryPostProcessors
    3) Create instances of beans and inject dependencies
    4) Apply BeanPostProcessors

    That is basically it (Chapter 3 of the reference guide explains the BeanFactory/ApplicationContext stuff in more detail).
    Marten Deinum
    Java Consultant / Pragmatist / Open Source Enthousiast / Author


    Pro Spring MVC: With Web Flow
    Conspect

    Have you read the reference guide.
    Use the [ code ] tags, young padawan

  8. #8
    Join Date
    Jul 2009
    Posts
    5

    Default

    Thank you very much for the explanation, Marten.

    Quote Originally Posted by Marten Deinum View Post
    The real problem here is that you want to post process a BeanFactoryPostProcessor with another BeanFactoryPostProcessor. I was hoping that making the CustomEditorConfigurer a PriorityOrdered instance would fix it (especially if it would run before the PPHC), but this is obviously not the case. It is quite difficult actually to make a BeanFactoryPostProcessor that will process other BeanFactoryPostProcessors.

    Also the problem runs even deeper than you might think the ResourceEditorRegistrar (used to register resource loaders etc.) is invoked and registered programmatically before everything else (check the prepareBeanFactoryMethod). Which is then used to set the properties on the PPHC (due to the getBean) before it is being post processed (as I stated this is quite difficult).

    The basic lifecylce is quite easy to understand

    1) Load and parse bean definition metadata (can be xml, classes, properties files)
    2) Register and execute BeanFactoryPostProcessors
    3) Create instances of beans and inject dependencies
    4) Apply BeanPostProcessors

    That is basically it (Chapter 3 of the reference guide explains the BeanFactory/ApplicationContext stuff in more detail).

  9. #9
    Join Date
    Jul 2009
    Posts
    5

    Default

    Hello Denis:

    I've tried your solution and it works perfectly.

    I've seen quite a few posts that ask for using different configuration files according to which environment (development, production, etc) is being used. I thought of this solution for that problem (being able to load properties files using a new ResourceLoader, that loads resources from an environment-specified path).

    Do you think there is a better approach for this?

    Thank you very much to you (and Marten) for your responses


    Quote Originally Posted by denis.zhdanov View Post
    Spring uses the following algorithm:
    1. Create all bean factory post processors;
    2. Invoke target methods postProcessBeanFactory() on every post processor;


    So, the main principle here is that bean definition changes performed by one bean factory post processor doesn't affect another bean factory post processors (see org.springframework.context.support.AbstractApplic ationContext.invokeBeanFactoryPostProcessors())

    I can offer the following workaround:
    1. Create custom bean factory post processor class that serves as a wrapper for the PropertyPlaceholderConfigurer;
    2. When postProcessBeanFactory() method of that class is called it creates necessary Resource object on the basis of configured resource type/name;
    3. Custom post processor creates standard PropertyPlaceholderConfigurer and injects created resource to it;
    4. Custom post processor calls postProcessBeanFactory() method on the created PropertyPlaceholderConfigurer instance;


    You can implement a custom spring xml extension for that, i.e. you can use something like <resourceroperty-placeholder location="config:application.properties"/> instead of standard <contextroperty-placeholder location="config:application.properties"/>

  10. #10
    Join Date
    May 2007
    Location
    Saint Petersburg, Russian Federation
    Posts
    1,189

    Default

    Quote Originally Posted by bones1976 View Post
    Hello Denis:

    I've tried your solution and it works perfectly.

    I've seen quite a few posts that ask for using different configuration files according to which environment (development, production, etc) is being used. I thought of this solution for that problem (being able to load properties files using a new ResourceLoader, that loads resources from an environment-specified path).

    Do you think there is a better approach for this?

    Thank you very much to you (and Marten) for your responses
    Welcome

    I see two possible ways to get environment-specific behavior:
    1. Define all settings at the environment-level, i.e. set necessary environment variables/jvm properties before the application start and have a delivery unit that is common for all environments;
    2. Define necessary settings at the VCS-managed files and parametrize build procedure, i.e. when you build delivery unit you define the environment it's going to be deployed at and the build script automatically applies necessary settings;


    As for me, I prefer the second way because I can observe all settings from the IDE then.

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •