Is there a difference in order of bean creation between dispatch servlet and context Loader Listener - spring

I faced an issue when accessing an instance variable when configuration xml is loaded through context Loader Listener.
Below is a configuration xml for creating beans
<bean id="A" class"org.sample.A">
</bean>
<bean id="B" class"org.sample.B">
<property name="cobj" ref="C"/>
</bean>
<bean id="C" class"org.sample.C"/>
Below is the java code
Class A{
C cobj = null;
public A(){
cobj = B.getInstance().getCobj();
}
void display(){
System.out.println(cobj);
}
}
Class B{
private static bobj = null;
C cobj = null;
public static B getInstance(){
if (bobj == null) {
return new B();
}
return bobj;
}
public setCobj(C obj){
this.cobj = obj
}
public C getCobj(){
return cobj;
}
}
Class C{
}
When the configuration xml is loaded through context Loader Listener , sysout prints null.
But when the same configuration is loaded through Dispatcher servlet the value is available.
Moreover if the display method is modified as below it shows result
public void display(){
B stackObj = new B();
System.out.println(stackObj.cobj);
}
Bean B is initialized later when configured through context Loader.
But its initialized earlier of Class A if configured through dispatcher servlet.
Can this order of creation be determined ?

You dont create objects by yourself. This is what Spring is supposed to do; provide objects to you. Load all the objects by making a call to xmlBeanFactory and then you make actual use of spring.

Related

Getting ClassCastException error

I have two classes ClientLogic1 and WelcomeBean1 as follows
public class ClientLogic1 {
public static void main(String[] args)
{
Resource res = new ClassPathResource("spconfig.xml");
BeanFactory factory = new XmlBeanFactory(res);
Object o = factory.getBean("id1");
WelcomeBean1 wb = (WelcomeBean1)o;
wb.show();
}
}
2nd class
public class WelcomeBean1 {
private Map data;
public void setData(Map data) {
this.data = data;
}
public void show()
{
Set s=data.entrySet();
Iterator it = s.iterator();
while(it.hasNext())
{
Map.Entry me = (Map.Entry)it.next();
System.out.println(me.getKey()+ " - "+me.getValue());
}
}
}
I have a xml file as
<beans>
<bean id="id1" class="WelcomeBean1">
<property name="data">
<map>
<entry key="k1">
<vlaue>1323</value>
</entry>
<entry key="k2">
<value>feed</value>
</entry>
</map>
</property>
</bean>
</bean>
I have given the right path.It's just when i run this program i get the following error
Exception in thread "main" java.lang.ClassCastException: WelcomeBean cannot be
cast to mapexmpl.WelcomeBean1 at mapexmpl.ClientLogic1.main(ClientLogic1.java:15)
I am not sure where i am going wrong.Can someone help me plz...
make sure there is no duplicate bean id in spring configuration file. for instance you might have bean WelcomeBean with id id1
change to full package name <bean id="id1" class="mapexmpl.WelcomeBean1">
Actually it is saying as WelcomeBean cannot be ..............But your code is showing all as WelcomeBean1.
You used WelcomeBean some where .Please check it once.
I think before you used WelComeBean.Then changed it to WelComeBean1.Please build agian with clean.

Populate spring servlet context on start up

It may be a duplicate question.
My question is: I want to read a property from properties file and put that in servlet context as soon as I start my application.
Could any one please help me on that?
Thanks in advance.
Implement Spring's ApplicationListener:
#Component
public class MyApplicationListener implements ApplicationListener {
/* if you want to set predefined properties you even don't have to load properties filed - you can directly inject properties values ... you can configure it in applicationContext.xml
<util:list id="locations">
<value>classpath:appconfig1.properties</value>
<value>classpath:appconfig2.properties</value>
</util:list>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
p:locations-ref="locations" />
*/
#Value("${myproperty1}") private String myProperty1;
#Value("${myproperty2}") private String myProperty2;
#Value("${myproperty3}") private String myProperty3;
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextClosedEvent) {
applicationClosed();
return;
}
if (!(event instanceof ContextRefreshedEvent)) return;
ContextRefreshedEvent e = (ContextRefreshedEvent) event;
ApplicationContext appContext = e.getApplicationContext();
if (!(appContext instanceof WebApplicationContext)) return;
WebApplicationContext ctx = (WebApplicationContext) e.getApplicationContext();
ServletContext context = ctx.getServletContext();
context.setAttribute("myProperty1", myProperty1);
context.setAttribute("myProperty2", myProperty2);
context.setAttribute("myProperty3", myProperty3);
}
}

How can I get a list of instantiated beans from Spring?

I have several beans in my Spring context that have state, so I'd like to reset that state before/after unit tests.
My idea was to add a method to a helper class which just goes through all beans in the Spring context, checks for methods that are annotated with #Before or #After and invoke them.
How do I get a list of instantiated beans from the ApplicationContext?
Note: Solutions which simply iterate over all defined beans are useless because I have many lazy beans and some of them must not be instantiated because that would fail for some tests (i.e. I have a beans that need a java.sql.DataSource but the tests work because they don't need that bean).
For example:
public static List<Object> getInstantiatedSigletons(ApplicationContext ctx) {
List<Object> singletons = new ArrayList<Object>();
String[] all = ctx.getBeanDefinitionNames();
ConfigurableListableBeanFactory clbf = ((AbstractApplicationContext) ctx).getBeanFactory();
for (String name : all) {
Object s = clbf.getSingleton(name);
if (s != null)
singletons.add(s);
}
return singletons;
}
I had to improve it a little
#Resource
AbstractApplicationContext context;
#After
public void cleanup() {
resetAllMocks();
}
private void resetAllMocks() {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
for (String name : context.getBeanDefinitionNames()) {
Object bean = beanFactory.getSingleton(name);
if (Mockito.mockingDetails(bean).isMock()) {
Mockito.reset(bean);
}
}
}
I am not sure whether this will help you or not.
You need to create your own annotation eg. MyAnnot.
And place that annotation on the class which you want to get.
And then using following code you might get the instantiated bean.
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(MyAnnot.class));
for (BeanDefinition beanDefinition : scanner.findCandidateComponents("com.xxx.yyy")){
System.out.println(beanDefinition.getBeanClassName());
}
This way you can get all the beans having your custom annotation.
applicationContext.getBeanDefinitionNames() does not show the beans which are registered without BeanDefinition instance.
package io.velu.core;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan
public class Core {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Core.class);
String[] singletonNames = context.getDefaultListableBeanFactory().getSingletonNames();
for (String singleton : singletonNames) {
System.out.println(singleton);
}
}
}
Console Output
environment
systemProperties
systemEnvironment
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor.importRegistry
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
messageSource
applicationEventMulticaster
lifecycleProcessor
As you can see in the output, environment, systemProperties, systemEnvironment beans will not be shown using context.getBeanDefinitionNames() method.
Spring Boot
For spring boot web applications, all the beans can be listed using the below endpoint.
#RestController
#RequestMapping("/list")
class ExportController {
#Autowired
private ApplicationContext applicationContext;
#GetMapping("/beans")
#ResponseStatus(value = HttpStatus.OK)
String[] registeredBeans() {
return printBeans();
}
private String[] printBeans() {
AutowireCapableBeanFactory autowireCapableBeanFactory = applicationContext.getAutowireCapableBeanFactory();
if (autowireCapableBeanFactory instanceof SingletonBeanRegistry) {
String[] singletonNames = ((SingletonBeanRegistry) autowireCapableBeanFactory).getSingletonNames();
for (String singleton : singletonNames) {
System.out.println(singleton);
}
return singletonNames;
}
return null;
}
}
[
"autoConfigurationReport",
"springApplicationArguments",
"springBootBanner",
"springBootLoggingSystem",
"environment",
"systemProperties",
"systemEnvironment",
"org.springframework.context.annotation.internalConfigurationAnnotationProcessor",
"org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory",
"org.springframework.boot.autoconfigure.condition.BeanTypeRegistry",
"org.springframework.context.annotation.ConfigurationClassPostProcessor.importRegistry",
"propertySourcesPlaceholderConfigurer",
"org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.store",
"preserveErrorControllerTargetClassPostProcessor",
"org.springframework.context.annotation.internalAutowiredAnnotationProcessor",
"org.springframework.context.annotation.internalRequiredAnnotationProcessor",
"org.springframework.context.annotation.internalCommonAnnotationProcessor",
"org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor",
"org.springframework.scheduling.annotation.ProxyAsyncConfiguration",
"org.springframework.context.annotation.internalAsyncAnnotationProcessor",
"methodValidationPostProcessor",
"embeddedServletContainerCustomizerBeanPostProcessor",
"errorPageRegistrarBeanPostProcessor",
"messageSource",
"applicationEventMulticaster",
"org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration$EmbeddedTomcat",
"tomcatEmbeddedServletContainerFactory",
"org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration$TomcatWebSocketConfiguration",
"websocketContainerCustomizer",
"spring.http.encoding-org.springframework.boot.autoconfigure.web.HttpEncodingProperties",
"org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration",
"localeCharsetMappingsCustomizer",
"org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration",
"serverProperties",
"duplicateServerPropertiesDetector",
"spring.resources-org.springframework.boot.autoconfigure.web.ResourceProperties",
"org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration",
"conventionErrorViewResolver",
"org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration",
"errorPageCustomizer",
"servletContext",
"contextParameters",
"contextAttributes",
"spring.mvc-org.springframework.boot.autoconfigure.web.WebMvcProperties",
"spring.http.multipart-org.springframework.boot.autoconfigure.web.MultipartProperties",
"org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration",
"multipartConfigElement",
"org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration",
"org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration$DispatcherServletConfiguration",
"dispatcherServlet",
"dispatcherServletRegistration",
"requestContextFilter",
"org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration",
"hiddenHttpMethodFilter",
"httpPutFormContentFilter",
"characterEncodingFilter",
"org.springframework.context.event.internalEventListenerProcessor",
"org.springframework.context.event.internalEventListenerFactory",
"reportGeneratorApplication",
"exportController",
"exportService",
"org.springframework.boot.autoconfigure.AutoConfigurationPackages",
"org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration",
"org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$Jackson2ObjectMapperBuilderCustomizerConfiguration",
"spring.jackson-org.springframework.boot.autoconfigure.jackson.JacksonProperties",
"standardJacksonObjectMapperBuilderCustomizer",
"org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration",
"org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration",
"jsonComponentModule",
"jacksonObjectMapperBuilder",
"org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperConfiguration",
"jacksonObjectMapper",
"org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration",
"org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration",
"org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration",
"org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration",
"defaultValidator",
"org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration",
"error",
"beanNameViewResolver",
"errorAttributes",
"basicErrorController",
"org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration",
"org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter",
"mvcContentNegotiationManager",
"org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration",
"stringHttpMessageConverter",
"org.springframework.boot.autoconfigure.web.JacksonHttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfiguration",
"mappingJackson2HttpMessageConverter",
"org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration",
"messageConverters",
"mvcConversionService",
"mvcValidator",
"requestMappingHandlerAdapter",
"mvcResourceUrlProvider",
"requestMappingHandlerMapping",
"mvcPathMatcher",
"mvcUrlPathHelper",
"viewControllerHandlerMapping",
"beanNameHandlerMapping",
"resourceHandlerMapping",
"defaultServletHandlerMapping",
"mvcUriComponentsContributor",
"httpRequestHandlerAdapter",
"simpleControllerHandlerAdapter",
"handlerExceptionResolver",
"mvcViewResolver",
"org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter$FaviconConfiguration",
"faviconRequestHandler",
"faviconHandlerMapping",
"defaultViewResolver",
"viewResolver",
"welcomePageHandlerMapping",
"org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration",
"objectNamingStrategy",
"mbeanServer",
"mbeanExporter",
"org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration",
"springApplicationAdminRegistrar",
"org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration",
"org.springframework.boot.autoconfigure.web.JacksonHttpMessageConvertersConfiguration",
"spring.info-org.springframework.boot.autoconfigure.info.ProjectInfoProperties",
"org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration",
"multipartResolver",
"org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration$RestTemplateConfiguration",
"restTemplateBuilder",
"org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration",
"spring.devtools-org.springframework.boot.devtools.autoconfigure.DevToolsProperties",
"org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration$RestartConfiguration",
"fileSystemWatcherFactory",
"classPathRestartStrategy",
"classPathFileSystemWatcher",
"hateoasObjenesisCacheDisabler",
"org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration$LiveReloadConfiguration$LiveReloadServerConfiguration",
"org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration$LiveReloadConfiguration",
"optionalLiveReloadServer",
"org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration",
"lifecycleProcessor"
]
I've created a gist ApplicationContextAwareTestBase.
This helper class does two things:
It sets all internal fields to null. This allows Java to free memory that isn't used anymore. It's less useful with Spring (the Spring context still keeps references to all the beans), though.
It tries to find all methods annotated with #After in all beans in the context and invokes them after the test.
That way, you can easily reset state of your singletons / mocks without having to destroy / refresh the context.
Example: You have a mock DAO:
public void MockDao implements IDao {
private Map<Long, Foo> database = Maps.newHashMap();
#Override
public Foo byId( Long id ) { return database.get( id ) );
#Override
public void save( Foo foo ) { database.put( foo.getId(), foo ); }
#After
public void reset() { database.clear(); }
}
The annotation will make sure reset() will be called after each unit test to clean up the internal state.
Using the previous answers, I've updated this to use Java 8 Streams API:
#Inject
private ApplicationContext applicationContext;
#Before
public void resetMocks() {
ConfigurableListableBeanFactory beanFactory = ((AbstractApplicationContext) applicationContext).getBeanFactory();
Stream.of(applicationContext.getBeanDefinitionNames())
.map(n -> beanFactory.getSingleton(n))
// My ConfigurableListableBeanFactory isn't compiled for 1.8 so can't use method reference. If yours is, you can say
// .map(ConfigurableListableBeanFactory::getSingleton)
.filter(b -> Mockito.mockingDetails(b).isMock())
.forEach(Mockito::reset);
}

Spring Environment Property Source Configuration

I'm working on an application library with a utility class called "Config" which is backed by the Spring Environment object and provides strongly typed getters for all the applications configuration values.
The property sources for the configuration can vary depending on environment (DEV/PROD) and usage (standalone/test/webapp), and can range from the default ones (system & env props) to custom database and JNDI sources.
What I'm struggling with is how to let the apps consuming this library easily configure the property source(s) used by Environment, such that the properties are available for use in our Config class and via the PropertySourcesPlaceholderConfigurer.
We're still using XML configuration, so ideally this could be configured in XML something like.
<bean id="propertySources" class="...">
<property name="sources">
<list>
<ref local="jndiPropertySource"/>
<ref local="databasePropertySource"/>
</list>
</property>
</bean>
...and then injected somehow into the Environment's property sources collection.
I've read that something like this may not be possible due to the timing of the app context lifecycle, and that this may need to be done using an application initializer class.
Any ideas?
It depends on how you want to use the properties, if it is to inject the properties using ${propertyname} syntax, then yes just having PropertySourcesPlaceHolderConfigurer will work, which internally has access to the PropertySources registered in the environment.
If you plan to use Environment directly, using say env.getProperty(), then you are right - the properties using PropertySourcesPlaceHolderConfigurer are not visible here. The only way then is to inject it using Java code, there are two ways that I know of:
a. Using Java Config:
#Configuration
#PropertySource("classpath:/app.properties")
public class SpringConfig{
}
b. Using a custom ApplicationContextInitializer, the way it is described here
I came up with the following which seems to work, but I'm fairly new to Spring, so I'm not so sure how it will hold up under different use cases.
Basically, the approach is to extend PropertySourcesPlaceholderConfigurer and add a setter to allow the user to easily configure a List of PropertySource objects in XML. After creation, the property sources are copied to the current Environment.
This basically allows the property sources to be configured in one place, but used by both placholder configuration and Environment.getProperty scenarios.
Extended PropertySourcesPlaceholderConfigurer
public class ConfigSourcesConfigurer
extends PropertySourcesPlaceholderConfigurer
implements EnvironmentAware, InitializingBean {
private Environment environment;
private List<PropertySource> sourceList;
// Allow setting property sources as a List for easier XML configuration
public void setPropertySources(List<PropertySource> propertySources) {
this.sourceList = propertySources;
MutablePropertySources sources = new MutablePropertySources();
copyListToPropertySources(this.sourceList, sources);
super.setPropertySources(sources);
}
#Override
public void setEnvironment(Environment environment) {
// save off Environment for later use
this.environment = environment;
super.setEnvironment(environment);
}
#Override
public void afterPropertiesSet() throws Exception {
// Copy property sources to Environment
MutablePropertySources envPropSources = ((ConfigurableEnvironment)environment).getPropertySources();
copyListToPropertySources(this.sourceList, envPropSources);
}
private void copyListToPropertySources(List<PropertySource> list, MutablePropertySources sources) {
// iterate in reverse order to insure ordering in property sources object
for(int i = list.size() - 1; i >= 0; i--) {
sources.addFirst(list.get(i));
}
}
}
beans.xml file showing basic configuration
<beans>
<context:annotation-config/>
<context:component-scan base-package="com.mycompany" />
<bean class="com.mycompany.ConfigSourcesConfigurer">
<property name="propertySources">
<list>
<bean class="org.mycompany.CustomPropertySource" />
<bean class="org.springframework.core.io.support.ResourcePropertySource">
<constructor-arg value="classpath:default-config.properties" />
</bean>
</list>
</property>
</bean>
<bean class="com.mycompany.TestBean">
<property name="stringValue" value="${placeholder}" />
</bean>
</beans>
The following worked for me with Spring 3.2.4 .
PropertySourcesPlaceholderConfigurer must be registered statically in order to process the placeholders.
The custom property source is registered in the init method and as the default property sources are already registered, it can itself be parameterized using placeholders.
JavaConfig class:
#Configuration
#PropertySource("classpath:propertiesTest2.properties")
public class TestConfig {
#Autowired
private ConfigurableEnvironment env;
#Value("${param:NOVALUE}")
private String param;
#PostConstruct
public void init() {
env.getPropertySources().addFirst(new CustomPropertySource(param));
}
#Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public TestBean1 testBean1() {
return new TestBean1();
}
}
Custom property source:
public class CustomPropertySource extends PropertySource<Object> {
public CustomPropertySource(String param) {
super("custom");
System.out.println("Custom property source initialized with param " + param + ".");
}
#Override
public Object getProperty(String name) {
return "IT WORKS";
}
}
Test bean (getValue() will output "IT WORKS"):
public class TestBean1 {
#Value("${value:NOVALUE}")
private String value;
public String getValue() {
return value;
}
}
I had a similar problem, in my case I'm using Spring in a standalone application, after load the default configurations I may need apply another properties file (lazy load configs) present in a config directory. My solution was inspired this Spring Boot documentation, but with no dependency of Spring Boot. See below the source code:
#PropertySources(#PropertySource(value = "classpath:myapp-default.properties"))
public class PersistenceConfiguration {
private final Logger log = LoggerFactory.getLogger(getClass());
private ConfigurableEnvironment env;
#Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurerDev(ConfigurableEnvironment env) {
return new PropertySourcesPlaceholderConfigurer();
}
#Autowired
public void setConfigurableEnvironment(ConfigurableEnvironment env) {
for(String profile: env.getActiveProfiles()) {
final String fileName = "myapp-" + profile + ".properties";
final Resource resource = new ClassPathResource(fileName);
if (resource.exists()) {
try {
MutablePropertySources sources = env.getPropertySources();
sources.addFirst(new PropertiesPropertySource(fileName,PropertiesLoaderUtils.loadProperties(resource)));
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
throw new RuntimeException(ex.getMessage(), ex);
}
}
}
this.env = env;
}
...
}
I recently ran into the issue of how to register custom property sources in the environment. My specific problem is that I have a library with a Spring configuration that I want to be imported into the Spring application context, and it requires custom property sources. However, I don't necessarily have control over all of the places where the application context is created. Because of this, I do not want to use the recommended mechanisms of ApplicationContextInitializer or register-before-refresh in order to register the custom property sources.
What I found really frustrating is that using the old PropertyPlaceholderConfigurer, it was easy to subclass and customize the configurers completely within the Spring configuration. In contrast, to customize property sources, we are told that we have to do it not in the Spring configuration itself, but before the application context is initialized.
After some research and trial and error, I discovered that it is possible to register custom property sources from inside of the Spring configuration, but you have to be careful how you do it. The sources need to be registered before any PropertySourcesPlaceholderConfigurers execute in the context. You can do this by making the source registration a BeanFactoryPostProcessor with PriorityOrdered and an order that is higher precedence than the PropertySourcesPlaceholderConfigurer that uses the sources.
I wrote this class, which does the job:
package example;
import java.io.IOException;
import java.util.Properties;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.PropertiesLoaderSupport;
/**
* This is an abstract base class that can be extended by any class that wishes
* to become a custom property source in the Spring context.
* <p>
* This extends from the standard Spring class PropertiesLoaderSupport, which
* contains properties that specify property resource locations, plus methods
* for loading properties from specified resources. These are all available to
* be used from the Spring configuration, and by subclasses of this class.
* <p>
* This also implements a number of Spring flag interfaces, all of which are
* required to maneuver instances of this class into a position where they can
* register their property sources BEFORE PropertySourcesPlaceholderConfigurer
* executes to substitute variables in the Spring configuration:
* <ul>
* <li>BeanFactoryPostProcessor - Guarantees that this bean will be instantiated
* before other beans in the context. It also puts it in the same phase as
* PropertySourcesPlaceholderConfigurer, which is also a BFPP. The
* postProcessBeanFactory method is used to register the property source.</li>
* <li>PriorityOrdered - Allows the bean priority to be specified relative to
* PropertySourcesPlaceholderConfigurer so that this bean can be executed first.
* </li>
* <li>ApplicationContextAware - Provides access to the application context and
* its environment so that the created property source can be registered.</li>
* </ul>
* <p>
* The Spring configuration for subclasses should contain the following
* properties:
* <ul>
* <li>propertySourceName - The name of the property source this will register.</li>
* <li>location(s) - The location from which properties will be loaded.</li>
* <li>addBeforeSourceName (optional) - If specified, the resulting property
* source will be added before the given property source name, and will
* therefore take precedence.</li>
* <li>order (optional) - The order in which this source should be executed
* relative to other BeanFactoryPostProcessors. This should be used in
* conjunction with addBeforeName so that if property source factory "psfa"
* needs to register its property source before the one from "psfb", "psfa"
* executes AFTER "psfb".
* </ul>
*
* #author rjsmith2
*
*/
public abstract class AbstractPropertySourceFactory extends
PropertiesLoaderSupport implements ApplicationContextAware,
PriorityOrdered, BeanFactoryPostProcessor {
// Default order will be barely higher than the default for
// PropertySourcesPlaceholderConfigurer.
private int order = Ordered.LOWEST_PRECEDENCE - 1;
private String propertySourceName;
private String addBeforeSourceName;
private ApplicationContext applicationContext;
private MutablePropertySources getPropertySources() {
final Environment env = applicationContext.getEnvironment();
if (!(env instanceof ConfigurableEnvironment)) {
throw new IllegalStateException(
"Cannot get environment for Spring application context");
}
return ((ConfigurableEnvironment) env).getPropertySources();
}
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
public String getPropertySourceName() {
return propertySourceName;
}
public void setPropertySourceName(String propertySourceName) {
this.propertySourceName = propertySourceName;
}
public String getAddBeforeSourceName() {
return addBeforeSourceName;
}
public void setAddBeforeSourceName(String addBeforeSourceName) {
this.addBeforeSourceName = addBeforeSourceName;
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/**
* Subclasses can override this method to perform adjustments on the
* properties after they are read.
* <p>
* This should be done by getting, adding, removing, and updating properties
* as needed.
*
* #param props
* properties to adjust
*/
protected void convertProperties(Properties props) {
// Override in subclass to perform conversions.
}
/**
* Creates a property source from the specified locations.
*
* #return PropertiesPropertySource instance containing the read properties
* #throws IOException
* if properties cannot be read
*/
protected PropertySource<?> createPropertySource() throws IOException {
if (propertySourceName == null) {
throw new IllegalStateException("No property source name specified");
}
// Load the properties file (or files) from specified locations.
final Properties props = new Properties();
loadProperties(props);
// Convert properties as required.
convertProperties(props);
// Convert to property source.
final PropertiesPropertySource source = new PropertiesPropertySource(
propertySourceName, props);
return source;
}
#Override
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
// Create the property source, and get its desired position in
// the list of sources.
if (logger.isDebugEnabled()) {
logger.debug("Creating property source [" + propertySourceName
+ "]");
}
final PropertySource<?> source = createPropertySource();
// Register the property source.
final MutablePropertySources sources = getPropertySources();
if (addBeforeSourceName != null) {
if (sources.contains(addBeforeSourceName)) {
if (logger.isDebugEnabled()) {
logger.debug("Adding property source ["
+ propertySourceName + "] before ["
+ addBeforeSourceName + "]");
}
sources.addBefore(addBeforeSourceName, source);
} else {
logger.warn("Property source [" + propertySourceName
+ "] cannot be added before non-existent source ["
+ addBeforeSourceName + "] - adding at the end");
sources.addLast(source);
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Adding property source ["
+ propertySourceName + "] at the end");
}
sources.addLast(source);
}
} catch (Exception e) {
throw new BeanInitializationException(
"Failed to register property source", e);
}
}
}
Of note here is that the default order of this property source factory class is higher precedence than the default order of PropertySourcesPlaceholderConfigurer.
Also, the registration of the property source happens in postProcessBeanFactory, which means that it will execute in the correct order relative to the PropertySourcesPlaceholderConfigurer. I discovered the hard way that InitializingBean and afterPropertiesSet do not respect the order parameter, and I gave up on that approach as being wrong and redundant.
Finally, because this is a BeanFactoryPostProcessor, it is a bad idea to try to wire much in the way of dependencies. Therefore, the class accesses the environment directly through the application context, which it obtains using ApplicationContextAware.
In my case, I needed the property source to decrypt password properties, which I implemented using the following subclass:
package example;
import java.util.Properties;
/**
* This is a property source factory that creates a property source that can
* process properties for substituting into a Spring configuration.
* <p>
* The only thing that distinguishes this from a normal Spring property source
* is that it decrypts encrypted passwords.
*
* #author rjsmith2
*
*/
public class PasswordPropertySourceFactory extends
AbstractPropertySourceFactory {
private static final PasswordHelper passwordHelper = new PasswordHelper();
private String[] passwordProperties;
public String[] getPasswordProperties() {
return passwordProperties;
}
public void setPasswordProperties(String[] passwordProperties) {
this.passwordProperties = passwordProperties;
}
public void setPasswordProperty(String passwordProperty) {
this.passwordProperties = new String[] { passwordProperty };
}
#Override
protected void convertProperties(Properties props) {
// Adjust password fields by decrypting them.
if (passwordProperties != null) {
for (String propName : passwordProperties) {
final String propValue = props.getProperty(propName);
if (propValue != null) {
final String plaintext = passwordHelper
.decryptString(propValue);
props.setProperty(propName, plaintext);
}
}
}
}
}
Finally, I specifed the property source factory in my Spring configuration:
<!-- Enable property resolution via PropertySourcesPlaceholderConfigurer.
The order has to be larger than the ones used by custom property sources
so that those property sources are registered before any placeholders
are substituted. -->
<context:property-placeholder order="1000" ignore-unresolvable="true" />
<!-- Register a custom property source that reads DB properties, and
decrypts the database password. -->
<bean class="example.PasswordPropertySourceFactory">
<property name="propertySourceName" value="DBPropertySource" />
<property name="location" value="classpath:db.properties" />
<property name="passwordProperty" value="db.password" />
<property name="ignoreResourceNotFound" value="true" />
<!-- Order must be lower than on property-placeholder element. -->
<property name="order" value="100" />
</bean>
To be honest, with the defaults for order in PropertySourcesPlaceholderConfigurer and AbstractPropertySourceFactory, it is probably not even necessary to specify order in the Spring configuration.
Nonetheless, this works, and it does not require any fiddling with the application context initialization.

HttpInvokerServiceExporter + HttpInvokerProxyFactoryBean - Could not access HTTP invoker remote service

I'm trying to use HttpInvokerServiceExporter + HttpInvokerProxyFactoryBean, but whatever I do I get an exception:
org.springframework.remoting.RemoteAccessException: Could not access HTTP invoker remote service at [http://localhost:9999/testcaseapp/testcaseservice]; nested exception is java.io.IOException: Did not receive successful HTTP response: status code = 404, status message = [Not Found]
For the simplicity, I've created a test case.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class RemoteTest {
private static final Logger logger = LoggerFactory.getLogger("TestsLogger");
static interface TestCaseService {
public Integer add(Integer arg1, Integer arg2);
}
static class TestCaseServiceImpl implements TestCaseService {
public Integer add(Integer arg1, Integer arg2) {
return (arg1 != null ? arg1.intValue() : 0) + (arg2 != null ? arg2.intValue() : 0);
}
}
#Configuration
static class Config {
#Bean
public HttpInvokerServiceExporter httpInvokerServiceExporter() {
HttpInvokerServiceExporter httpInvokerServiceExporter = new HttpInvokerServiceExporter();
httpInvokerServiceExporter.setService(new TestCaseServiceImpl());
httpInvokerServiceExporter.setServiceInterface(TestCaseService.class);
return httpInvokerServiceExporter;
}
#Bean
public HttpInvokerProxyFactoryBean httpInvokerProxyFactoryBean() {
HttpInvokerProxyFactoryBean httpInvokerProxyFactoryBean = new HttpInvokerProxyFactoryBean();
httpInvokerProxyFactoryBean.setServiceInterface(TestCaseService.class);
httpInvokerProxyFactoryBean.setServiceUrl("http://localhost:9999/testcaseapp/testcaseservice");
httpInvokerProxyFactoryBean.afterPropertiesSet();
return httpInvokerProxyFactoryBean;
}
}
#Autowired
private TestCaseService[] testCaseServices;
private static Server server;
#BeforeClass
public static void setUp() {
try {
server = new Server();
SelectChannelConnector connector = new SelectChannelConnector();
connector.setPort(9999);
server.addConnector(connector);
//
WebAppContext webAppContext = new WebAppContext();
webAppContext.setContextPath("/testcaseapp");
webAppContext.setWar("src/test/java/" + RemotingTest.class.getPackage().getName().replace('.', '/'));
server.setHandler(webAppContext);
//
server.start();
} catch (Exception ex) {
logger.info("Could not permorm the set up: {}", ex.toString());
}
}
#AfterClass
public static void destroy() {
try {
server.stop();
} catch (Exception e) {
}
}
#Test
public void addTest() {
for (TestCaseService testCaseService : testCaseServices) {
Integer sum = testCaseService.add(10, 5);
Assert.assertNotNull(sum);
Assert.assertEquals(15, sum.intValue());
}
}
}
I've also tried to create a TestCaseService bean
#Bean public TestCaseService testCaseService() ...
and provide it as a httpInvokerServiceExporter argument
#Bean public HttpInvokerServiceExporter httpInvokerServiceExporter(TestCaseService testCaseService)
...
httpInvokerServiceExporter.setService(testCaseService);
but the result is still the same.
What am I doing wrong? Thanks!
I think the problem is that the Servlet is not accesible.
SERVER SIDE
Make sure you have in your WEB-INF/web.xml (on the app that is exposing the methods -SERVER-) this code:
<web-app>
...
<servlet>
<servlet-name>remoting</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>remoting</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
...
</web-app>
Here, the remote methods are served under "services", that is, for calling the method, the URL should be:
http://localhost:8080/sample/services/list
And you have to define this Servlet as accesible, by creating a bean (in my case under WEB-INF/remoting-servlet.xml):
<bean name="/list" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="myObjectQueryService" />
<property name="serviceInterface" value="com.kategor.myapp.sample.service.ObjectQueryService" />
</bean>
CLIENT SIDE
If your using Spring under the client (not as in your example), you must define a bean for accessing the remote resources, defining some beans (one for each public resource):
In this case, it would be:
<bean id="listService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl" value="http://localhost:8080/sample/services/list" />
<property name="serviceInterface" value="com.kategor.myapp.sample.service.ObjectQueryService" />
</bean>
In your example is right.
This way, calling the Service "listService", you would have all the methods available in the class com.kategor.myapp.sample.service.ObjectQueryService
#Controller
public class HomeController {
// This is the remote service definition
#Autowired
private ObjectQueryService<MyObject, Long> objectQueryService;
/* .... */
/**
* List all Objects retrieved through Web Service from a remote Server
*/
#RequestMapping(value = "listRemoteWS", method = RequestMethod.GET)
public String listRemoteWS(Locale locale, Model model) {
StringBuilder result = new StringBuilder();
try {
// The Remote Service is called
List objs = objectQueryService.findAll(0, 10);
result.append(objs.size() + " objs found");
for (MyObject o : objs) {
result.append("<br>* ").append(o.getId()).append(" = ").append(o.getName());
}
} catch (Exception e) {
result.append("No objs have been found");
e.printStackTrace();
}
model.addAttribute("result", result);
return "index";
}
}
So I think the problem comes from the URL: maybe the service is not visible or this is not the correct path to it.
For more information, check this links (the first is really useful):
https://github.com/JamesEarlDouglas/barebones-spring-mvc/tree/master/reference/spring-remoting
http://www.ibm.com/developerworks/web/library/wa-spring3webserv/index.html
For me the problem was tomcat picked up two versions of the same applications. This raised the above error on running the client from STS in debug mode.
So solution is to clean up all the expanded webapp folders in tomcat for the application. Then redeploy the application.

Resources