I have a simple Spring bean
public class Widget {
public Widget(File rootDir) { ... }
}
and in my application context XML I want to create an instance of Widget:
<bean id="widget" class="com.example.Widget">
<constructor-arg type="java.io.File" value="classpath:/someDir"/>
</bean>
When I run in from my IDE it works, the string is converted to a File and passed to the ctor.
When I run it with mvn exec:java the file cannot be found, I get all sorts of errors, but revolving around:
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name widget defined in class path
resource [META-INF/context.xml]: Could not resolve matching
constructor (hint: specify index/type/nam e arguments for simple
parameters to avoid type ambiguities)
So, how to I pass a classpath File (actually, directory) to a bean constructor in Spring?
I need a directory becasue then I want to scan it / list all files inside it.
So, how to I pass a classpath File (actually, directory) to a bean
constructor in Spring? I need a directory becasue then I want to scan
it / list all files inside it.
I don't know if that's possible but you can get all of the resources on the classpath that match a pattern using Spring. See PathMatchingResourcePatternResolver. See the docs on using Ant-style wildcards.
Try this for your constructor:
import org.springframework.core.io.Resource;
...
public Widget(Resource[] resources) { ... }
and in your XML, something like this (change the Ant pattern to match your use case):
<bean id="widget" class="com.example.Widget">
<constructor-arg>
<list>
<value>classpath:/someDir/*.foo</value>
</list>
</constructor-arg>
</bean>
Before using this, I recommend you take a look at the caveats related to non-filesystem resources mentioned in the PathMatchingResourcePatternResolver Javadocs.
Related
Is there a way to easily use Spring Injection to grab one or more *.xml data files from a folder location (either in a deployed *.war or on a server folder) and inject that data from the *.xml files into a Java class (e.g. in a web service)? I have been asked by another programmer if I can do this.
I've had a look at a few links on stackoverflow, but so far the easiest way I've found is to put the *.xml files into a particular folder location (e.g. WEB-INF/classes) and use something like this to retrieve them:
Thread.currentThread().getContextClassLoader.getResourceAsStream("/WEB-INF/classes/data.xml")
The above method is easy; however, it is obviously not Spring Injection. Is there a way to do this using Spring Injection instead? I would have thought that since configuration files can be loaded this way, that xml data could also be loaded similarly.
Thanks.
Spring provides a class called Resource which you can use to inject resource files into a spring bean. So you can do this:
public class Consumer {
public void setResource(Resource resource) {
DataInputStream resourceStream = new DataInputStream(resource.getInputStream());
// ... use the stream as usual
}
...
}
Then:
<bean class="Consumer">
<property name="resource" value="classpath:path/to/file.xml"/>
</bean>
or,
<bean class="Consumer">
<property name="resource" value="file:path/to/file.xml"/>
</bean>
You can also directly use the #Value annotation:
public class Consumer {
#Value("classpath:path/to/file.xml")
private Resource resource;
...
}
<context:property-placeholder
location="a.properties,b.properties"
ignore-unresolvable="true"/>
result: both properties file are loaded
<context:property-placeholder
location="${properties_location}"
ignore-unresolvable="true"/>
where properties_location is "a.properties,b.properties"
result: Exception in thread "main" org.springframework.beans.factory.BeanInitializationException: Could not load properties; nested exception is java.io.FileNotFoundException: class path resource [a.properties,b.properties] cannot be opened because it does not exist
edit: ${properties_location} is set the following way:
System.getProperties().setProperty("properties_location", "a.properties,b.properties");
ApplicationContext ctx = new GenericXmlApplicationContext("context.xml");
...
How can I initialize my application the 2nd way? to have all the properties file's path defined in a placeholder.
You have to change this to:
<context:property-placeholder
location="classpath:a.properties,
classpath:b.properties"
ignore-unresolvable="true"/>
From the source of the parser for the property-placeholder element.
String location = element.getAttribute("location");
if (StringUtils.hasLength(location)) {
String[] locations = StringUtils.commaDelimitedListToStringArray(location);
builder.addPropertyValue("locations", locations);
}
First the location is retrieved, if that has a value it is converted to a String[]. Springs conversion service takes care of replacing any placeholders in the String[]. But at that moment the properties_location placeholder is just a single element in the array and that gets resolved to a.properties,b.properties without further processing.
So at the moment this isn't possible with placeholders I'm afraid.
One thing that might work is using SpEL if it is always going to be a system property you can use #{systemProperties['properties_location']} to resolve the value. That should be resolved before anything else.
You cant use a property placeholder as a value in a placeholder placeholder resolver. Its like saying, "hey, resolve the placeholder for the location of the all the properties, and then you can start resolving properties!".
Logically it just dosent make sense. I was experimenting with spring property placeholder resolution recently, and stumbled upon this same question. I attempted to use two property placeholder configurers, one to resolve the location of the properties for the second, and the second to resolve the rest of the properties. Of course this dosent work due to the way in which spring initialises its beans.
Initialise bean post processors
Construct them
Construct all other beans
Since the property placeholder configurer is a bean post processor, if you have more than one of them, they get initialised and constructed at the same time, so know nothing of each others properties at construction
Edit
Given that the property location is a system property you could have:
System.getProperties().setProperty("properties_location_a", "classpath:/a.properties");
System.getProperties().setProperty("properties_location_b", "classpath:/b.properties");
And then in your spring content.xml:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreUnresolvablePlaceholders" value="true"/>
<property name="locations">
<list>
<value>${properties_location_a}</value>
<value>${properties_location_b}</value>
</list>
</property>
</bean>
There is some information in the tomcat engine that we want to access run time, so we have the following in our app context (got this from this blog post):
<bean id="tomcatEngineProxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
<property name="objectName" value="Catalina:type=Engine" />
<property name="proxyInterface" value="org.apache.catalina.Engine" />
<property name="useStrictCasing" value="false" />
</bean>
In a controller, we then autowired it in like this:
#Autowired
private MBeanProxyFactoryBean tomcatEngineProxy = null;
We cannot wire in org.apache.catalina.Engine like in the blog post, because that class is not available to us at build time. It's only available at run time with all the different tomcat versions running on the different machines.
We were able to get the information we needed from this #Autowire using reflection. Now, we want to move this functionality into a service. I added this to our app context:
<bean id="myService" class="com.foo.bar.MyServiceImpl">
<constructor-arg ref="tomcatEngineProxy" />
</bean>
And the class looks like this:
public class MyServiceImpl implements MyService
{
public MyServiceImpl(MBeanProxyFactoryBean tomcatEngineProxy) throws Exception
{
//stuff with the proxy
}
.....
}
When I do this, I get the following error:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myService' defined in ServletContext resource [/WEB-INF/spring/root-context.xml]: Unsatisfied dependency expressed through constructor argument with index 0 of type [org.springframework.jmx.access.MBeanProxyFactoryBean]: Could not convert constructor argument value of type [$Proxy44] to required type [org.springframework.jmx.access.MBeanProxyFactoryBean]: Failed to convert value of type '$Proxy44 implementing org.apache.catalina.Engine,org.springframework.aop.SpringProxy,org.springframework.aop.framework.Advised' to required type 'org.springframework.jmx.access.MBeanProxyFactoryBean'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [$Proxy44 implementing org.apache.catalina.Engine,org.springframework.aop.SpringProxy,org.springframework.aop.framework.Advised] to required type [org.springframework.jmx.access.MBeanProxyFactoryBean]: no matching editors or conversion strategy found
Knowing basically nothing about how proxies work and how to use them, I'm not sure how to go about making this work. Is there some declaration I can use for my constructor arg that will match up? What is different between the #Autowire in the controller that does work and the constructor arg that doesn't work?
It's because your factory bean is exposing the result as the engine interface:
<property name="proxyInterface" value="org.apache.catalina.Engine" />
So if you try to wire in the "tomcatEngineProxy" bean itself, it's only compatible assignment is to "org.apache.catalina.Engine", since the created proxy implements only that interface.
try referencing the factory bean directly instead (notice the ampersand which is the syntax for finding the actual factory bean which created the object instead of the object itself):
<constructor-arg ref="&tomcatEngineProxy" />
How to inject FactoryBean instead of object it produces?
I believe that #Autowired works since the binding is done according to the type of the Bean (i.e. MBeanProxyFactoryBean), however the binding to the constructor argument is done by name and the name you provided tomcatEngineProxy does not match the type you expect since the type of a bean created using a FactoryBean is different than that of the FactoryBean.
It looks like the simplest solution will be to change MyServiceImpl to be:
public class MyServiceImpl implements MyService
{
#Autowired
private MBeanProxyFactoryBean tomcatEngineProxy;
public MyServiceImpl() throws Exception
{
//stuff with the proxy
}
.....
}
This should do the trick.
I have a class that must be initialized with an absolute pathname. The thing I want to initialize it with the pathname of is a file sitting in WEB-INF.
I am using the ContextLoaderListener from Spring to set this all into motion, so I can't run Java code to obtain the path from the context and stick it where a ${whatever} could find it.
Consider some bean definition like:
<bean class="my.class">
<property name="somePath" value="/WEB-INF/a.txt"/>
</bean>
I need a way, if possible, to make that pathname pass through the ServletContextResource mechanism. There doesn't seem to be a 'prefix' for those like classpath:
In this case, it won't help to have the item in the classpath, trust me.
EDIT:
I went and dug up the source of the bean class, and it already accepts Resources on the relevant properties. So Something Ridiculous is going on here, insofar as it complains as if it can't find things. Off to the debugger for me.
EDIT AGAIN:
So, this turns out to be a maven prank, unrelated to Spring. It's upvotes all around for your help, and open another question.
My preference would be to modify this class to take a Resource, rather than a pathname. You can then inject it using:
<property name="fileResource" value="/WEB-INF/path/to/file"/>
This is more flexible, and you can use the various methods on the Resource interface to get things like the underlying file pathname, such as getFile().getAbsolutePath().
However, if modifying the target class is not feasible, then you need some way of converting a Resource into a pathname. You could use a FactoryBean for this, something like:
public class ResourcePathFactoryBean extends AbstractFactoryBean<String> {
private Resource resource;
#Required
public void setResource(Resource resource) {
this.resource = resource;
}
#Override
protected String createInstance() throws Exception {
return resource.getFile().getAbsolutePath();
}
#Override
public Class<?> getObjectType() {
return String.class;
}
}
You can then use it to inject your path:
<bean id="myBean" class="com.MyBean">
<property name="path">
<bean class="com.ResourcePathFactoryBean">
<property name="resource" value="/WEB-INF/path/to/file"/>
</bean>
</property>
</bean>
Is the file on the classpath?
In your bean, you could have a property such as Resource fileResource, and then set it in your bean like
<property name="fileResource" value="classpath:/path/to/the/file"/>
The Spring Resource interface has a method named getFile(), which will return a File handle for the resource. From there you can simply call File.getAbsolutePath().
I have a bean that has a property of type File. I want that property to end up pointing to a file under WEB-INF.
It seems to me that the ServletContextResourceLoader should have that job, somehow, but nothing I try seems to do the job.
I'm trying to avoid resorting to something like this in the Java code.
If that property has to remain as type "File", then you're going to have to jump through some hoops.
It would be better, if possible, to refactor the bean to have a Resource property, in which case you can inject the resource path as a String, and Spring will construct a ServletContextResource for you. You can then obtain the File from that using the Resource.getFile() method.
public class MyBean {
private File file;
public void setResource(Resource resource) {
this.file = resource.getFile();
}
}
<bean class="MyBean">
<property name="resource" value="/WEB-INF/myfile">
</bean>