轻量Groovy与JSF(JSF2/PrimeFaces)集成方案咨询
Great question! Since you're sticking to a TomEE/JEE/JSF core and want to avoid the heavy Grails-style stack, here's a practical, low-overhead approach tailored to your enterprise custom extension use case:
Core Problem to Solve
JSF relies on JavaBean conventions (getters/setters) and EL expression resolution to access object properties. Dynamic GroovyObjects (with runtime-added properties) don't fit this pattern out of the box, so we need to bridge EL to Groovy's dynamic property system.
Step 1: Add Groovy Dependency
First, include Groovy in your project (no Grails required). For Maven:
<dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-all</artifactId> <version>3.0.17</version> <!-- Use latest stable version --> <scope>compile</scope> </dependency>
Step 2: Custom EL PropertyResolver for GroovyObjects
Create a custom EL resolver to let JSF's EL engine handle Groovy's dynamic properties. This resolver will intercept property access for GroovyObject instances and use Groovy's native getProperty()/setProperty() methods:
package com.yourcompany.jsf.el; import groovy.lang.GroovyObject; import javax.el.PropertyResolver; import javax.el.ELContext; public class GroovyELPropertyResolver extends PropertyResolver { private final PropertyResolver delegate; public GroovyELPropertyResolver(PropertyResolver delegate) { this.delegate = delegate; } @Override public Object getValue(ELContext context, Object base, Object property) { if (base instanceof GroovyObject && property instanceof String) { context.setPropertyResolved(true); return ((GroovyObject) base).getProperty((String) property); } return delegate.getValue(context, base, property); } @Override public void setValue(ELContext context, Object base, Object property, Object value) { if (base instanceof GroovyObject && property instanceof String) { context.setPropertyResolved(true); ((GroovyObject) base).setProperty((String) property, value); return; } delegate.setValue(context, base, property, value); } // Delegate all other PropertyResolver methods to the default resolver @Override public Class<?> getType(ELContext context, Object base, Object property) { return delegate.getType(context, base, property); } @Override public boolean isReadOnly(ELContext context, Object base, Object property) { return delegate.isReadOnly(context, base, property); } @Override public Class<?> getCommonPropertyType(ELContext context, Object base) { return delegate.getCommonPropertyType(context, base); } @Override public Iterator<String> getPropertyNames(ELContext context, Object base) { return delegate.getPropertyNames(context, base); } }
Register this resolver in your faces-config.xml to activate it:
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd" version="2.2"> <application> <el-resolver>com.yourcompany.jsf.el.GroovyELPropertyResolver</el-resolver> </application> </faces-config>
Step 3: Use GroovyObjects as JSF Beans
You can use Groovy's built-in Expando (a ready-to-use GroovyObject) or create custom Groovy classes for your dynamic entities. Here's how to expose them via CDI (TomEE supports CDI out of the box):
Example 1: Expando as a Managed Bean
package com.yourcompany.beans; import groovy.util.Expando; import javax.enterprise.context.SessionScoped; import javax.inject.Named; import java.io.Serializable; @Named("dynamicEntityHolder") @SessionScoped class DynamicEntityHolder implements Serializable { // A dynamic Groovy object with runtime-addable properties Expando customerEntity = new Expando() // Method to add custom properties (for customer-specific extensions) void addCustomProperty(String name, Object value) { customerEntity[name] = value } // Helper to get all property names (for dynamic table columns) List<String> getPropertyNames() { customerEntity.properties.keySet().findAll { it != 'class' && it != 'metaClass' } as List } }
Example 2: Custom GroovyObject Implementation
For more control, create a custom Groovy class that implements GroovyObject:
package com.yourcompany.entities; import groovy.lang.GroovyObject; import groovy.lang.MetaClass; import javax.enterprise.context.RequestScoped; import javax.inject.Named; @Named("customGroovyEntity") @RequestScoped class CustomGroovyEntity implements GroovyObject { private final Map<String, Object> dynamicProps = [:] private final GroovyObject delegate = new Expando() @Override Object getProperty(String propertyName) { dynamicProps.getOrDefault(propertyName, delegate.getProperty(propertyName)) } @Override void setProperty(String propertyName, Object newValue) { dynamicProps.put(propertyName, newValue) } @Override MetaClass getMetaClass() { delegate.getMetaClass() } @Override void setMetaClass(MetaClass metaClass) { delegate.setMetaClass(metaClass) } @Override Object invokeMethod(String name, Object args) { delegate.invokeMethod(name, args) } }
Step 4: Render Dynamic Properties in JSF Components
Now you can directly access Groovy's dynamic properties in JSF EL. For a data table with dynamic columns (perfect for custom customer extensions):
<h:dataTable value="#{customerList}" var="customer" border="1"> <!-- Dynamic columns based on Groovy object properties --> <c:forEach items="#{dynamicEntityHolder.propertyNames}" var="propName"> <h:column> <f:facet name="header">#{propName}</f:facet> #{customer[propName]} </h:column> </c:forEach> </h:dataTable>
Key Advantages for Your Use Case
- Lightweight: No Grails dependencies—just core Groovy and JSF/EL extensions.
- Flexible: Perfect for customer-specific extensions where you need runtime-addable properties.
- Native Integration: Works seamlessly with TomEE's JSF/CDI stack.
内容的提问来源于stack exchange,提问作者Jacek Cz

