When using Spring, is it possible to set a property only if the value passed is not null?
Example:
<bean name="myBean" class="some.Type">
<property name="abc" value="${some.param}"/>
</bean>
The behavior I'm looking for is:
some.Type myBean = new some.Type();
if (${some.param} != null) myBean.setAbc(${some.param});
The reason I need this is since abc has a default value which I don't want to override with a null.
And the Bean I am creating is not under my source control - so I cannot change its behavior. (Also, abc for this purpose might be a primitive, so I can't set it with a null anyway.
EDIT:
According to the answers I think my question requires clarification.
I have bean I need to instantiate and pass to 3rd party I use. This bean has many properties (12 currently) of various types (int, boolean, String, etc.)
Each property has a default value - I don't know what it is and would prefer not needing to know unless it becomes an issue.
What I'm looking for is a generic solution that comes from Spring's abilities - currently the only solution I have is a reflection based.
Configuration
<bean id="myBean" class="some.TypeWrapper">
<property name="properties">
<map>
<entry key="abc" value="${some.value}"/>
<entry key="xyz" value="${some.other.value}"/>
...
</map>
</property>
</bean>
Code
public class TypeWrapper
{
private Type innerBean;
public TypeWrapper()
{
this.innerBean = new Type();
}
public void setProperties(Map<String,String> properties)
{
if (properties != null)
{
for (Entry<String, Object> entry : properties.entrySet())
{
String propertyName = entry.getKey();
Object propertyValue = entry.getValue();
setValue(propertyName, propertyValue);
}
}
}
private void setValue(String propertyName, Object propertyValue)
{
if (propertyValue != null)
{
Method method = getSetter(propertyName);
Object value = convertToValue(propertyValue, method.getParameterTypes()[0]);
method.invoke(innerBean, value);
}
}
private Method getSetter(String propertyName)
{
// Assume a valid bean, add a "set" at the beginning and toUpper the 1st character.
// Scan the list of methods for a method with the same name, assume it is a "valid setter" (i.e. single argument)
...
}
private Object convertToValue(String valueAsString, Class type)
{
// Check the type for all supported types and convert accordingly
if (type.equals(Integer.TYPE))
{
...
}
else if (type.equals(Integer.TYPE))
{
...
}
...
}
}
The real "difficulty" is in implementing convertToValue for all possible value types.
I have done this more than once in my life - so it is not a major issue to implement it for all possible types that I need (mostly primitives and a few enums) - but I hoped a more intelligent solution existed.
You can use SpEL and placeholder and default value for placeholder mechanisms together as following:
<bean name="myBean" class="some.Type">
<property name="abc" value="${some.param:#{null}}"/>
</bean>
For solve your problem, you have to use SEL(Spring Expression Language).
By this feature (added in SPring 3.0) you can such as other dynamic language writing your condition. For your context, answer is:
<bean name="myBean" class="some.Type">
<property name="abc" value="#(if(${some.param} != null) ${some.param})"/>
</bean>
for more information see(this tutorial says what use SEL in context file):
http://static.springsource.org/spring/docs/3.0.5.RELEASE/reference/expressions.html
You can use default value concept in property configurer in Spring framework as following:
<bean name="myBean" class="some.Type">
<property name="abc" value="${some.param : your-default-value}"/>
</bean>
you can set default value by this approach. By this context config if some.param key exist so its value set in abc property and if don't exist your-default-value set in abc property.
Note: Another benefit of this approah is:"In POJO programming model better approzh is member of class don't have any default value,and default value injected from out of class."
You can create a Utility class that will act as a Factory class for some.Type, and wrap the logic there
For Example :
public class TypeFactory {
public static Type craeteType(SomeType param){
Type t = new Type();
if(param!=null)
t.setParam(param);
}
}
and on XML configure the bean creation using this Factory method
<bean id="myBean" class="some.Type"
factory-method="craeteType">
<constructor-arg ref="param"/>
</bean>
This looks like a job for Java-based container configuration. You'll be able to do what you do in your XML config, but with all the power of Java.
I've got it working with following snippet:
<bean name="myBean" class="some.Type">
<property name="abc" value="#{'${some.param}'=='' ? null : '${some.param}'}" />
</bean>
Related
I am bringing the beans that are being loaded in container with the help of below function. I got this from this SO question: Getting all beans in context.
Arrays.asList(context.getBeanDefinitionNames())
this is returning me bean names as a list as shown below
[helloWorld, helloWorld2, helloRandomCountry, beforeAfterPrint]
for the beans configuration file snapshot as below
<bean id = "helloWorld" class = "com.springspp.HelloWorld">
<property name = "message" value = "Hello World!"/>
</bean>
<bean id = "helloWorld2" class = "com.springspp.HelloWorld">
<property name = "message" value = "Hello Hello World!"/>
</bean>
<bean id="helloRandomCountry"
class="com.springspp.countries.HelloJapan"/>
<bean id = "beforeAfterPrint" class =
"com.springspp.BeforeAfterPrint">
</bean>
Now I'm having a list of these beans with me and they are in STRING Format. If I want to call any method in that class or use any variable that is being defined in that class how could I do that. I checked for linked and relative questions in Stack Overflow but I didn't find any answers.
Typical use case is as follows. I want to find out the variable
country
from below class that is configured in metadata file as helloRandomCountry as its bean name as shown above
class HelloJapan implements Countries{
String country="Japan";
private String thisClassString="Hello this is Japan";
public void displayString() {
System.out.println(thisClassString);
}
}
You really wouldn't want to do this in production code, but I am assuming you are simply learning.
You can call context.getBean("beanName"). That will return a raw Object. So you would need to cast it the correct object. There are other methods that you can attempt to use, but since you are using the same class type for them, you might run into issues.
You can get the expected bean like this
HelloJapan helloJapan = context.getBeansOfType(HelloJapan.class).get("helloRandomCountry");
And then call the method you want on it.
I've an Enum class
public enum MyEnum{
ABC;
}
And i have this spring xml configuration.
<util:map id="myMap">
<entry key="ABC" value-ref="myObj" />
</util:map>
<bean id="mick" class="com.x.Mick">
<property name="myMap" ref="myMap" />
</bean>
Above setup works fine and key to the map is passed as enum. But now my requirement is to pass the Key in String type from enum i.e. My map will look Map<String,String> instead of Map<MyEnum,String>
Any suggestions on how to go about this?
Take a look at this post:
Using Enum values as String literals
I guess that is just the Thing you want if i understand you correctly.
I am trying to use below spring injection for list of strings.
<bean name="myBean" class="java.util.HashSet">
<constructor-arg type="java.util.Collection" value="#{'${my.list.of.strings}'.split(',')}"/>
</bean>
I am getting String "'${my.list.of.strings}'.split(',')" as constructor argument instead of List of Strings.
Is there any Version Issue?
I am using Spring Release 2.5, spring-beans 2.0.xsd
If your bean wasn't a HashSet and had a constructor that accepted an array as an argument, you could do something like this:
<bean name="myBean" class="mypackage.MyBeanClass">
<constructor-arg type="java.lang.String[]" value="${my.list.of.strings}" />
</bean>
Or this:
<bean name="myBean" class="mypackage.MyBeanClass">
<constructor-arg>
<value type="java.lang.String[]">${my.list.of.strings}</value>
</constructor-arg>
</bean>
(I don't remember which is the correct way for Spring 2.5, actually I don't remember if you can even use a placeholder. If this is the case, please consider using the last version of Spring, which by these days is 4.1.5.RELEASE).
But you want myBean bean to be of class HashSet. The only solution I can think of is to use your own class that wraps HashSet or maybe extends from it:
package mypackage;
// imports ommited
public class MyBeanClass extends HashSet<String> {
public MyBeanClass(String[] values) {
super(Arrays.asList(values));
}
}
If you don't like the idea to inherit directly from HashSet, then wrap it and make the wrapper delegate all the methods you want to it:
package mypackage;
// imports ommited
public class MyBeanClass implements Set<String> {
private final Set<String> delegate;
public MyBeanClass(String[] values) {
this.delegate = new HashSet(Arrays.asList(values));
}
public boolean add(String element) {
return this.delegate.add(element);
}
public boolean contains(Object element) {
return this.delegate.contains(element);
}
// TODO rest of delegate methods
}
The Spring Expression Language (spring-el) only appeared in Spring 3.0. Your expressions aren't going to be evaluated with Spring 2.5, you'll need to do that a different way.
Even with Spring 3, I'd recommend finding another way to build your set, such as using a FactoryBean (see Customizing instantiation logic using FactoryBeans)
I'm interested in reusing this logic for something I'm working on.
Given
<bean name="myBeanWithSetXyz" class="com.blah.Something/>
<property name="xyz" value="3"/>
is there a spring class someplace that figures out setXyz() is an int, and sets it on an instance of the bean?
Or perhaps some other utility? Yeah, I could write it myself, but I'm a fan of reuse.
DirectFieldAccessor and BeanWrapperImpl are your most likely candidates.
ConfigurablePropertyAccessor fieldAccessor = new DirectFieldAccessor (someObject);
beanWrapper.setPropertyValue(fieldName,
fieldAccessor.convertForProperty(value, fieldName) );
ConfigurablePropertyAccessor beanWrapper = new BeanWrapperImpl(someObject);
beanWrapper.setPropertyValue(propertyName,
beanWrapper.convertForProperty(value, propertyName) );
I'm using JBoss 4.3.2.GA
I've added method to my MBean service. Method has several arguments in signature. It works fine but I want more.
Problem: when I see method signature in jmx-console, I don't know what every of this input fields means, because jmx-console doesn't show arguments names, only input fields for values.
Is there ability add description of every argument (in Java code, not xml) allowing to show this description in jmx-console of JBOSS?
I've tried to use Spring annotation: #ManagedOperation to add at least method description but no results (description is not showed in jmx-console).
May be some one have resolved such issue...
In Java, you can do this, if you don't use standard MBeans, but e.g. DynamicMBeans for which you need to implement getMBeanInfo() which is returning all that data.
This is a generic way, not limited to JBoss. But it is also a lot of work, which (IMO) only makes sense if you really need the dynamic features of a DynamicMBean.
For completeness sake (and as this may be the easier approach):
You can write an xmbean-descriptor and put that e.g. into $SERVER/conf/xmdesc/
In addition to this you need to enhance the standard MBeean-descriptor like this (note the xmbean-dd attribute:
<mbean code="org.jnp.server.NamingBeanImpl"
name="jboss:service=NamingBeanImpl"
xmbean-dd="resource:xmdesc/NamingBean-xmbean.xml">
</mbean>
This example is taken from $SERVER/conf/jboss-service.xml and the NamingBean-xmban.xml is in the path described by the attribute.
I have created a small wrapper that will create a dynamic MBean out of a normal Java class through annotations. With it you can also add descriptions to beans, attributes, operations and parameters.
It also supports externalization and localization of names and descriptions using Java ResourceBundles.
Example of an annotated class:
#JMXBean(description = "My first JMX bean test")
public class MyBean {
int level = 0;
#JMXBeanAttribute(name = "Floor Level", description = "The current floor level")
public int getLevel() {
return level;
}
#JMXBeanAttribute
public void setLevel(int newLevel) {
level = newLevel;
}
#JMXBeanOperation(name = "Echo Test", description = "Echoes the parameter back to you")
public String myMethod(
#JMXBeanParameter(name = "Input", description = "String of what to echo") String param) {
return "You said " + param;
}
}
Example of an annotated class using ResourceBundles:
#JMXBean(resourceBundleName="com.example.my.package.BundleName")
public class MyBean {
int level = 0;
#JMXBeanAttribute(nameKey="level", descriptionKey="levelDescription")
public int getLevel() {
return level;
}
}
How to use it:
MyBean bean = new MyBean();
JMXBeanWrapper wrappedBean = new JMXBeanWrapper(bean);
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
mbs.registerMBean(wrappedBean, new Objectname("com.example.my.package:type=TestBean,name=My Bean"));
You can find the source on GitHub
I've had success with mixing Spring XML and Spring annotations where I had multiple MBeans of the same Java class. The approach allowed tight control over bean names and allowed me to define descriptions etc at the class level. I needed to define an annotation based bean assembler for an MBeanExporter, and supply a map of bean names and bean references:
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"
lazy-init="false">
<property name="server" ref="mbeanServer" />
<property name="assembler">
<!-- will create management interface using annotation metadata -->
<bean class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
<property name="attributeSource">
<bean class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
</property>
</bean>
</property>
<property name="beans">
<map>
<!-- entries -->
</map>
</property>
</bean>
An example of what I read from Java annotations might be:
#ManagedAttribute(description = "A detailed description to show in JConsole tooltips etc")
public String getFoo() {
return foo;
}
I had the assemblers defined privately to the exporter, but you could share those beans more widely I'm sure.
BTW this was on Tomcat.