Java 用作 JSF 值的记录
Java Records to be used as JSF values
我正在使用 Java 15 和 JSF 2.3 加上 PrimeFaces 8 开发一个简单的 JSF Web 应用程序(主要用于学习),我正在使用一个简单的 java 记录来为实体建模。该应用程序不使用任何数据库。
我的问题是是否可以像这样在 xhtml 页面中使用 java 记录作为值
<p:column headerText="Id">
<h:outputText value="#{car.randomId}" />
</p:column>
因为我收到以下错误
javax.el.PropertyNotFoundException: The class 'com.company.Car' does not have the property 'id'.
。
我尝试输入 car.year()
,但没有用。
记录的定义是这样的
public record Car (String randomId, String randomBrand, int randomYear, String randomColor, int randomPrice, boolean randomSoldState) {
}
在pom.xml中,我使用的是下面的api
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
<version>8.0.0</version>
<scope>provided</scope>
</dependency>
感谢您的帮助!
在深入研究堆栈跟踪并检查读取属性的代码后,目前似乎尚未实现从 java 记录中读取属性。
也许在未来的版本中。
请注意,我会在找到有关此主题的相关信息后尽快更新答案。
更新 2020 年 11 月 8 日
不幸的是,我误解了记录的范围。
在现实世界的应用程序中,我的实体将是 JPA 实体。
从技术上讲,问题不在 JSF 中,而在 EL 中。异常的包名已经暗示了这一点:javax.el.PropertyNotFoundException
。正在使用的 EL 版本还不能识别 Java 条记录。 Jakarta EE 8 与 Java 8 相关,但 Java 记录功能是在 Java 14 中引入的。理论上,它最早只能在与 Jakarta EE 关联的 EL 版本中得到原生支持与 Java 14 关联的版本。但即便如此也不太可能,因为 Java 记录仅作为“预览功能”提供(因此默认情况下根本不启用)。
回到您的具体问题,使用 #{car.randomId()}
中的方法表达式语法在 WildFly 21 上确实对我有用。无论如何,EL 解析始终可以使用自定义 ELResolver
.这是一个启动示例,它根据 Class#isRecord()
检查记录并收集通过 Class#getRecordComponents()
可用的字段作为属性:
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();
}
}
为了让它工作,请按如下方式在 faces-config.xml
中注册它:
<application>
<el-resolver>com.example.RecordELResolver</el-resolver>
</application>
真正的工作是在getValue()
方法中完成的。它基本上定位了代表 Java 记录访问器的 java.lang.reflect.Method
并调用它。
也就是说,Java 记录不适合替代完全有价值的 JavaBean,主要是因为 Java 记录是 不可变的 。所以它们不能用作真实的 (JPA) 实体,因为它们应该是可变的。 Java 记录最多用作 DTO。
我正在使用 Java 15 和 JSF 2.3 加上 PrimeFaces 8 开发一个简单的 JSF Web 应用程序(主要用于学习),我正在使用一个简单的 java 记录来为实体建模。该应用程序不使用任何数据库。
我的问题是是否可以像这样在 xhtml 页面中使用 java 记录作为值
<p:column headerText="Id">
<h:outputText value="#{car.randomId}" />
</p:column>
因为我收到以下错误
javax.el.PropertyNotFoundException: The class 'com.company.Car' does not have the property 'id'.
。
我尝试输入 car.year()
,但没有用。
记录的定义是这样的
public record Car (String randomId, String randomBrand, int randomYear, String randomColor, int randomPrice, boolean randomSoldState) {
}
在pom.xml中,我使用的是下面的api
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
<version>8.0.0</version>
<scope>provided</scope>
</dependency>
感谢您的帮助!
在深入研究堆栈跟踪并检查读取属性的代码后,目前似乎尚未实现从 java 记录中读取属性。
也许在未来的版本中。
请注意,我会在找到有关此主题的相关信息后尽快更新答案。
更新 2020 年 11 月 8 日 不幸的是,我误解了记录的范围。
在现实世界的应用程序中,我的实体将是 JPA 实体。
从技术上讲,问题不在 JSF 中,而在 EL 中。异常的包名已经暗示了这一点:javax.el.PropertyNotFoundException
。正在使用的 EL 版本还不能识别 Java 条记录。 Jakarta EE 8 与 Java 8 相关,但 Java 记录功能是在 Java 14 中引入的。理论上,它最早只能在与 Jakarta EE 关联的 EL 版本中得到原生支持与 Java 14 关联的版本。但即便如此也不太可能,因为 Java 记录仅作为“预览功能”提供(因此默认情况下根本不启用)。
回到您的具体问题,使用 #{car.randomId()}
中的方法表达式语法在 WildFly 21 上确实对我有用。无论如何,EL 解析始终可以使用自定义 ELResolver
.这是一个启动示例,它根据 Class#isRecord()
检查记录并收集通过 Class#getRecordComponents()
可用的字段作为属性:
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();
}
}
为了让它工作,请按如下方式在 faces-config.xml
中注册它:
<application>
<el-resolver>com.example.RecordELResolver</el-resolver>
</application>
真正的工作是在getValue()
方法中完成的。它基本上定位了代表 Java 记录访问器的 java.lang.reflect.Method
并调用它。
也就是说,Java 记录不适合替代完全有价值的 JavaBean,主要是因为 Java 记录是 不可变的 。所以它们不能用作真实的 (JPA) 实体,因为它们应该是可变的。 Java 记录最多用作 DTO。