JavaBeans and introspection - messed up on boolean and indexed properties?
A former colleague of mine started a discussion half an hour ago about JavaBeans, and why they didn't quite work the way he wants in JSF. The particular case is about boolean properties.
1. For a boolean property named isUrl
Eclipse generates this
private boolean isUrl;
public boolean isUrl() {..}
public boolean setUrl(boolean url) {..}
But this does not work in JSF. He made it work by adding public boolean getIsUrl()
The implementation might be buggy, so let's make sure who's right, by using the introspection API.:
BeanInfo info = Introspector.getBeanInfo(ClassTest.class);
for (PropertyDescript开发者_运维知识库or pd : info.getPropertyDescriptors()) {
System.out.println(pd.getName() + ": " + pd.getReadMethod() +
" : " + pd.getWriteMethod());
}
For the above code, this prints both methods - i.e. Eclipse is right, JSF is wrong. But that sounded suspicious to me, since the specification doesn't mention anything about the double "is".
But while looking through the spec, I saw something I've never used - the so called indexed properties. You can have private String[] bar
and then public String getBar(int idx)
. So:
2. I tried that with the Introspector
, and it didn't find a read method for bar. The result from the above code was: bar: null : null
. So I came to think - now the introspector does not follow the spec. Perhaps it didn't follow it in the previous case, and ultimately, JSF is right. In fact, indexed properties can make so that there are two read methods for a given property. And that's not possible with the PropertyDescriptor
class of the introspection API.
What does this lead us to - we have a possibly broken API that does not conform to the spec. Which leads to other implementations of the spec (JSF uses a custom one obviously). Which leads to further misunderstandings and confusions.
A sidenote for something that bothered me - in the JavaBeans spec they call the naming conventions for the methods "design patterns". This sounds wrong to me.
So, now onto the questions:
- is the JavaBeans spec clear
- is the introspection API correct
- is a new JavaBeans specification needed, at least to clarify the behaviour of booleans (that's subjective to an extent)
Update. it appears the JSF usage is bean.isUrl
rathern than bean.url
. Which makes perfects sense not to work with isUrl()
accessor.
P.S. JDK 1.6.0_20, JSF 1.2, MyFaces
Java Bean Properties are defined by methods, not by fields. For this reason the PropertyDescriptor
class has getReadMethod()
and getWriteMethod()
methods, but no getField()
methods.
Personally, I think your colleague is using a bad practice.
a) is
is a verb. Fields should not be named after verbs.
b) while it's not required, good practice is to name the field like the property, which lets you write code like this:
PropertyDescriptor pd; // let's assume this is set
Method referenceMethod = pd.getReadMethod() == null
// at least one of these is not null
? pd.getWriteMethod() : pd.getReadMethod();
Field underLyingField = referenceMethod
.getDeclaringClass()
.getDeclaredField(pd.getName());
While this code is not standardized, it is following conventions and can come in very handy. If you don't follow conventions like this, you have no way of associating a field with a property (which is intentional, I know).
e.g. I use code like the above to check if the field has annotations
About indexed properties:
You can use the index syntax on array or list (or map) properties, but only if they are defined as standard bean properties.
So if you have a property like this:
private String[] bar;
public String[] getBar(){
return bar;
}
public void setBar(String[] bar){
this.bar = bar;
}
or like this:
private List<String> bar;
public List<String> getBar(){
return bar;
}
public void setBar(List<String> bar){
this.bar = bar;
}
you can access the first member with the expression ${bar[0]}
And with a map property like this:
private Map<String, String> bar;
public Map<String, String> getBar(){
return bar;
}
public void setBar(Map<String, String> bar){
this.bar = bar;
}
You can access the value mapped to "baz"
like this ${bar['baz']}
.
This functionality builds on top of standard beans functionality, so it requires regular getters / setters.
As mentioned by @Peter Lawrey, the name of the field is irrelevant as far as JavaBeans is concerned. It needn't even exist, or you can give it a silly name such as prefixing with m_
.
You didn't provide a suitable read or write methods for the bar
property as a whole, so these are not going to show up. You can't synthesize a Method
to an existing class at runtime. I believe indexed properties were a late edition, even though there isn't an @since
apparent, so they aren't used by java.beans
interfaces.
I don't have the JSF spec (and it'll be behind the jcp.org lawyer wall), so don't know what it claims. It may specify something different to the JavaBeans spec [probable], or you may be using an implementation with bugs [I guess also probable].
"Design pattern" is just a phrase. As in a pattern that occurs in our design. It's not Design Pattern as in GoF.
精彩评论