1

I've made some custom beans that implement the Map interface for easy access to arbitrary data in my JSP.

Example:

${person["cellPhoneNumber"]}

This extra data may or may not be added on the back end so a Map seems like a nice flexible way to store this.

The problem arises when I try to use the getters on my bean. My Person class has a getName() method. When using the following in my JSP, the Map.get() method is called instead of my method.

${person.name}

Is there a way to get around this call to Map's get("name") and call getName() instead?

Here is my basic (stripped down) Java class:

class Person implements Map
{
    private HashMap<String, Object> myMap;
    private String name;
    public Object get(Object key)
    {
        return myMap.get(key);
    }
    public String getName()
    {
        return this.name;
    }
}

Using JSTL 1.1

Matthew
  • 8,183
  • 10
  • 37
  • 65
  • I think your problem is a bad design decision. Your object fails the IS-A Map test. Don't implement that interface, no matter how "convenient" you think it is. It's a bad abstraction. – duffymo Sep 29 '12 at 15:13
  • @duffymo Do you mean that it fails the IS-A Map because I haven't implemented all the methods or is it something else? I only posted the relevant parts of the class as an example to keep it brief. – Matthew Sep 29 '12 at 15:17
  • 2
    No, it's not true that Person IS-A Map. Don't implement that interface. If your Person is a Java Bean it'll work fine in the JSP. – duffymo Sep 29 '12 at 15:18
  • I guess because I'm saying that Person IS-A Map, JSP wants to treat it as a Map only. Is it instead possible to have JSP treat my object as a Bean first, Map second? – Matthew Sep 29 '12 at 16:38

1 Answers1

5

Looks like servlet container treats every class implementing Map interface as a map and completely discards other methods, falling back to by key lookup. I see two solutions:

1) get(Object key) should be aware of normal properties:

class Person implements Map
{
    private HashMap<String, Object> myMap;
    private String name;
    public Object get(Object key)
    {
        switch(key) {
            case "name": return getName();
            default: myMap.get(key)
        }
    }
    public String getName()
    {
        return this.name;
    }
}

This is a bit clumsy and not very scalable. Also it's probably easier to either use reflection and find all fields automatically or convert your bean to map (see: How to convert a Java object (bean) to key-value pairs (and vice versa)?). Even worse.

2) Expose your map as a special bean attribute:

class Person
{
    private HashMap<String, Object> optional;
    private String name;
    public Map<String, Object> getOptional()
    {
        return optional;
    }
    public String getName()
    {
        return this.name;
    }
}

Then your EL expression looks like this:

${person.optional['cellPhoneNumber']}

This is a much better approach since:

  • it's faster and more scalable
  • better represents your intent
  • it's a better design (as highlighted by duffymo in comments) - Person has optional attributes like cell phone number. Person is not a ``Map` of optional attributes.
Community
  • 1
  • 1
Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • I agree, nice explaination. I will probably fall back to your way of using the optional map data. I was just surprised when none of my bean accessors were getting called after I implemented Map. – Matthew Sep 29 '12 at 15:32