用作JSF值的Java记录
我正在开发一个简单的JSF web应用程序,主要是为了学习使用Java 15和JSF 2.3以及PrimeFaces 8,我正在使用一个简单的Java记录来建模实体。应用程序不使用任何数据库 我的问题是,是否可以像这样在xhtml页面中使用java记录作为值用作JSF值的Java记录,jsf,el,java-record,Jsf,El,Java Record,我正在开发一个简单的JSF web应用程序,主要是为了学习使用Java 15和JSF 2.3以及PrimeFaces 8,我正在使用一个简单的Java记录来建模实体。应用程序不使用任何数据库 我的问题是,是否可以像这样在xhtml页面中使用java记录作为值 <p:column headerText="Id"> <h:outputText value="#{car.randomId}" /> <
<p:column headerText="Id">
<h:outputText value="#{car.randomId}" />
</p:column>
在pom.xml中,我使用以下api
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
<version>8.0.0</version>
<scope>provided</scope>
</dependency>
谢谢你的帮助 在深入挖掘堆栈跟踪并检查用于读取属性的代码之后,似乎目前还没有实现从java记录中读取属性 也许在未来的版本中 请注意,我将在找到有关此主题的相关信息后尽快更新答案 更新8.11.2020 不幸的是,我误解了记录的范围
在现实世界的应用程序中,我的实体将是一个JPA实体。在深入挖掘堆栈跟踪并检查用于读取属性的代码后,似乎目前还没有实现从java记录中读取属性 也许在未来的版本中 请注意,我将在找到有关此主题的相关信息后尽快更新答案 更新8.11.2020 不幸的是,我误解了记录的范围
在真实的应用程序中,我的实体将是一个JPA实体。从技术上讲,问题不在JSF中,而在EL中。异常的包名已经提示:javax.el.PropertyNotFoundException。正在使用的EL版本还不能识别Java记录。Jakarta EE 8与Java8绑定,但Java14中引入了Java记录功能。理论上,它最早只能在与Java14相关联的Jakarta EE版本相关联的EL版本中本机支持。但即使这样也不太可能,因为Java记录仅作为预览功能可用,因此默认情况下根本不启用 回到您的具体问题,使用{car.randomId}中的方法表达式语法在WildFly 21上确实对我有效。在任何情况下,都可以使用自定义ELResolver自定义EL解析。下面是一个启动示例,它基于ClassSrecord检查记录,并通过ClassgetRecordComponents收集可用字段作为属性:
public class RecordELResolver extends ELResolver {
private static final Map<Class<?>, Map<String, PropertyDescriptor>> RECORD_PROPERTY_DESCRIPTOR_CACHE = new ConcurrentHashMap<>();
private static boolean isRecord(Object base) {
return base != null && base.getClass().isRecord();
}
private static Map<String, PropertyDescriptor> getRecordPropertyDescriptors(Object base) {
return RECORD_PROPERTY_DESCRIPTOR_CACHE
.computeIfAbsent(base.getClass(), clazz -> Arrays
.stream(clazz.getRecordComponents())
.collect(Collectors
.toMap(RecordComponent::getName, recordComponent -> {
try {
return new PropertyDescriptor(recordComponent.getName(), recordComponent.getAccessor(), null);
}
catch (IntrospectionException e) {
throw new IllegalStateException(e);
}
})));
}
private static PropertyDescriptor getRecordPropertyDescriptor(Object base, Object property) {
PropertyDescriptor descriptor = getRecordPropertyDescriptors(base).get(property);
if (descriptor == null) {
throw new PropertyNotFoundException("The record '" + base.getClass().getName() + "' does not have the field '" + property + "'.");
}
return descriptor;
}
@Override
public Object getValue(ELContext context, Object base, Object property) {
if (!isRecord(base) || property == null) {
return null;
}
PropertyDescriptor descriptor = getRecordPropertyDescriptor(base, property);
try {
Object value = descriptor.getReadMethod().invoke(base);
context.setPropertyResolved(base, property);
return value;
}
catch (Exception e) {
throw new ELException(e);
}
}
@Override
public Class<?> getType(ELContext context, Object base, Object property) {
if (!isRecord(base) || property == null) {
return null;
}
PropertyDescriptor descriptor = getRecordPropertyDescriptor(base, property);
context.setPropertyResolved(true);
return descriptor.getPropertyType();
}
@Override
public Class<?> getCommonPropertyType(ELContext context, Object base) {
if (!isRecord(base)) {
return null;
}
return String.class;
}
@Override
public boolean isReadOnly(ELContext context, Object base, Object property) {
if (!isRecord(base)) {
return false;
}
getRecordPropertyDescriptor(base, property); // Forces PropertyNotFoundException if necessary.
context.setPropertyResolved(true);
return true;
}
@Override
public void setValue(ELContext context, Object base, Object property, Object value) {
if (!isRecord(base)) {
return;
}
throw new PropertyNotWritableException("Java Records are immutable");
}
@Override
public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
if (!isRecord(base)) {
return null;
}
Map rawDescriptors = getRecordPropertyDescriptors(base);
return rawDescriptors.values().iterator();
}
}
实际工作是用getValue方法完成的。它基本上定位表示java记录访问器的java.lang.reflect.Method并调用它
这就是说,Java记录不适合替代完整的JavaBean,主要是因为Java记录是不可变的。因此,它们不能用作真正的JPA实体,因为它们应该是可变的。Java记录作为DTO最有用。从技术上讲,问题不在于JSF,而在于EL。异常的包名已经提示:javax.el.PropertyNotFoundException。正在使用的EL版本还不能识别Java记录。Jakarta EE 8与Java8绑定,但Java14中引入了Java记录功能。理论上,它最早只能在与Java14相关联的Jakarta EE版本相关联的EL版本中本机支持。但即使这样也不太可能,因为Java记录仅作为预览功能可用,因此默认情况下根本不启用 回到您的具体问题,使用{car.randomId}中的方法表达式语法在WildFly 21上确实对我有效。在任何情况下,都可以使用自定义ELResolver自定义EL解析。下面是一个启动示例,它基于ClassSrecord检查记录,并通过ClassgetRecordComponents收集可用字段作为属性:
public class RecordELResolver extends ELResolver {
private static final Map<Class<?>, Map<String, PropertyDescriptor>> RECORD_PROPERTY_DESCRIPTOR_CACHE = new ConcurrentHashMap<>();
private static boolean isRecord(Object base) {
return base != null && base.getClass().isRecord();
}
private static Map<String, PropertyDescriptor> getRecordPropertyDescriptors(Object base) {
return RECORD_PROPERTY_DESCRIPTOR_CACHE
.computeIfAbsent(base.getClass(), clazz -> Arrays
.stream(clazz.getRecordComponents())
.collect(Collectors
.toMap(RecordComponent::getName, recordComponent -> {
try {
return new PropertyDescriptor(recordComponent.getName(), recordComponent.getAccessor(), null);
}
catch (IntrospectionException e) {
throw new IllegalStateException(e);
}
})));
}
private static PropertyDescriptor getRecordPropertyDescriptor(Object base, Object property) {
PropertyDescriptor descriptor = getRecordPropertyDescriptors(base).get(property);
if (descriptor == null) {
throw new PropertyNotFoundException("The record '" + base.getClass().getName() + "' does not have the field '" + property + "'.");
}
return descriptor;
}
@Override
public Object getValue(ELContext context, Object base, Object property) {
if (!isRecord(base) || property == null) {
return null;
}
PropertyDescriptor descriptor = getRecordPropertyDescriptor(base, property);
try {
Object value = descriptor.getReadMethod().invoke(base);
context.setPropertyResolved(base, property);
return value;
}
catch (Exception e) {
throw new ELException(e);
}
}
@Override
public Class<?> getType(ELContext context, Object base, Object property) {
if (!isRecord(base) || property == null) {
return null;
}
PropertyDescriptor descriptor = getRecordPropertyDescriptor(base, property);
context.setPropertyResolved(true);
return descriptor.getPropertyType();
}
@Override
public Class<?> getCommonPropertyType(ELContext context, Object base) {
if (!isRecord(base)) {
return null;
}
return String.class;
}
@Override
public boolean isReadOnly(ELContext context, Object base, Object property) {
if (!isRecord(base)) {
return false;
}
getRecordPropertyDescriptor(base, property); // Forces PropertyNotFoundException if necessary.
context.setPropertyResolved(true);
return true;
}
@Override
public void setValue(ELContext context, Object base, Object property, Object value) {
if (!isRecord(base)) {
return;
}
throw new PropertyNotWritableException("Java Records are immutable");
}
@Override
public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
if (!isRecord(base)) {
return null;
}
Map rawDescriptors = getRecordPropertyDescriptors(base);
return rawDescriptors.values().iterator();
}
}
实际工作是用getValue方法完成的。它基本上定位表示java记录访问器的java.lang.reflect.Method并调用它
这就是说,Java记录不适合替代完整的JavaBean,主要是因为Java记录是不可变的。因此,它们不能用作真正的JPA实体,因为它们应该是可变的。Java记录作为DTO最有用。请您分享car类,以便更好地帮助您吗?事实上,car类,该记录是否构成问题,但这里是公共记录car字符串randomId,字符串randomBrand,int randomYear,字符串randomColor,int randomPrice,布尔randomSoldState{}请分享car类,为了更好地帮助您?实际上是汽车类,这是记录形式的问题,但这里是公共记录汽车字符串randomId,字符串randomBrand,int randomYear,字符串randomColor,int randomPrice,boolean randomSoldState{}非常感谢您的详细解释和代码示例!小细节:getCommonPropertyType的return String.class-当您使用字符串寻址记录组件时。GetFeatureDescriptor的return String.class:return record_COMPONENT_CACHE.computeIfAbsent….values.mapp->new java.beans.propertyDescriptor.getNam
e、 p.getAccessor,null.iterator;“可能有用。”@Johannes:完全正确。这只是一个基于现有javax.el.BeanELResolver的启动示例。提到的方法实际上从未在正常的JSF生命周期中调用过,但我会更新代码。非常感谢您的附加注释。非常感谢您的详细解释和代码示例!小细节:getCommonPropertyType的return String.class-当您使用字符串寻址记录组件时。getFeatureDescriptor的return String.class:return record_COMPONENT_CACHE.ComputeFabSent….values.mapp->new java.beans.propertyDescriptor.getName,p.getAccessor,null.iterator;“可能有用。”@Johannes:完全正确。这只是一个基于现有javax.el.BeanELResolver的启动示例。在正常的JSF生命周期中,上述方法实际上从未被调用过,但我将更新代码。非常感谢附加的注释。
<application>
<el-resolver>com.example.RecordELResolver</el-resolver>
</application>