How to access an object's public fields from a

2019-01-27 20:04发布

问题:

Here is my object class:

public class Address
{
    public final String line1;
    public final String town;
    public final String postcode;

    public Address(final String line1, final String town, final String postcode)
    {
        this.line1 = line1;
        this.town = town;
        this.postcode = postcode;
    }
}

I add it to the velocity context like this:

Address theAddress = new Address("123 Fake St", "Springfield", "SP123");
context.put("TheAddress", theAddress);

However, when writing the template, the following will not render the address fields (however, it works fine when I add getters to the Address class)

<Address>
    <Line1>${TheAddress.line1}</Line1>
    <Town>${TheAddress.town}</Town>
    <Postcode>${TheAddress.postcode}</Postcode>
</Address>

Is it possible to access public fields on objects from Velocity without adding getters?

回答1:

Not by default. You need to configure a different Uberspect implementation.



回答2:

The Velocity user guide suggests it's not possible. Quote:

[Velocity] tries out different alternatives based on several established naming conventions. The exact lookup sequence depends on whether or not the property name starts with an upper-case letter. For lower-case names, such as $customer.address, the sequence is

  1. getaddress()
  2. getAddress()
  3. get("address")
  4. isAddress()

For upper-case property names like $customer.Address, it is slightly different:

  1. getAddress()
  2. getaddress()
  3. get("Address")
  4. isAddress()


回答3:

http://wiki.apache.org/velocity/VelocityFAQ:

Q: How can i access my object's public fields in my templates?

A: Currently, you have three options:

  • Wrap your object with a FieldMethodizer

  • Configure your VelocityEngine to use a custom uberspector like the PublicFieldUberspect

  • Lobby the velocity-dev list to add public field introspection as a default fallback if no matching method is found :)

FieldMethodizer works only with public static fields.

PublicFieldUberspect example code is quite old and it just fails with error on nonexistent fields.

And forget about lobby at dev list. )


Meanwhile, there is good caching implementation of UberspectPublicFields in current velocity trunk. Unfortunately, there was no active development for years and no plans for next release are published. One would have to build it himself and bundle in private repository.


Another altervative is a fork with bonus scala compatibility that is available in central maven repository: http://maven-repository.com/artifact/com.sksamuel.scalocity/scalocity/0.9.

Drop in instead of usual velocity dependency:

<dependency>
  <groupId>com.sksamuel.scalocity</groupId>
  <artifactId>scalocity</artifactId>
  <version>0.9</version>
</dependency>

Then just add to velocity.properties:

runtime.introspector.uberspect = org.apache.velocity.util.introspection.UberspectPublicFields, org.apache.velocity.util.introspection.UberspectImpl

The caveat is that UberspectImpl is patched with additional support for scala properties and requires 8 MB scala jar.


Eventually, I just interned the following classes from velocity trunk into own project:

org.apache.velocity.runtime.parser.node.PublicFieldExecutor org.apache.velocity.runtime.parser.node.SetPublicFieldExecutor org.apache.velocity.util.introspection.ClassFieldMap org.apache.velocity.util.introspection.Introspector org.apache.velocity.util.introspection.IntrospectorBase org.apache.velocity.util.introspection.IntrospectorCache org.apache.velocity.util.introspection.IntrospectorCacheImpl org.apache.velocity.util.introspection.UberspectPublicFields

These work fine with Velocity 1.7.



回答4:

i do

import org.apache.velocity.util.introspection.UberspectImpl;
import org.apache.velocity.util.introspection.UberspectPublicFields;

....

properties.setProperty("runtime.introspector.uberspect",
  UberspectImpl.class.getName() + ", " +
  UberspectPublicFields.class.getName());

And all works OK!!!