通过 JSF/EL 和 JDK 17 访问 ZoneInfo 时出现 IllegalAccessException

IllegalAccessException when accessing ZoneInfo via JSF/EL with JDK 17

在将一个大型 JEE8 应用程序移植到 Java17 时,我在渲染一个简单的 EL 表达式时偶然发现了一个 IllegalAccessException#{myWarBean.defaultTZ.rawOffset}。我设法在 SSCCE on github 中重现了该问题。当您 运行 Wildfly 应用程序服务器上的应用程序(我使用的是 26.1.1.Final)时,您会得到以下堆栈跟踪:

SEVERE [javax.enterprise.resource.webcontainer.jsf.application] (default task-1) Error Rendering View[/index.xhtml]: javax.el.ELException: /index.xhtml @23,74 value="raw offset=#{myWarBean.defaultTZ.rawOffset}": java.lang.IllegalAccessException: class javax.el.BeanELResolver cannot access class sun.util.calendar.ZoneInfo (in module java.base) because module java.base does not export sun.util.calendar to unnamed module @6a1cb0de
    at com.sun.jsf-impl@2.3.17.SP01//com.sun.faces.facelets.el.TagValueExpression.getValue(TagValueExpression.java:77)
    at javax.faces.api@3.1.0.SP01//javax.faces.component.ComponentStateHelper.eval(ComponentStateHelper.java:194)
    at javax.faces.api@3.1.0.SP01//javax.faces.component.ComponentStateHelper.eval(ComponentStateHelper.java:181)
    at javax.faces.api@3.1.0.SP01//javax.faces.component.UIOutput.getValue(UIOutput.java:140)
    at com.sun.jsf-impl@2.3.17.SP01//com.sun.faces.renderkit.html_basic.HtmlBasicInputRenderer.getValue(HtmlBasicInputRenderer.java:198)
    at com.sun.jsf-impl@2.3.17.SP01//com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.getCurrentValue(HtmlBasicRenderer.java:328)
    at com.sun.jsf-impl@2.3.17.SP01//com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.encodeEnd(HtmlBasicRenderer.java:143)
    at javax.faces.api@3.1.0.SP01//javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:600)
    at com.sun.jsf-impl@2.3.17.SP01//com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.encodeRecursive(HtmlBasicRenderer.java:286)
    at com.sun.jsf-impl@2.3.17.SP01//com.sun.faces.renderkit.html_basic.GroupRenderer.encodeChildren(GroupRenderer.java:90)
    at javax.faces.api@3.1.0.SP01//javax.faces.component.UIComponentBase.encodeChildren(UIComponentBase.java:571)
    at javax.faces.api@3.1.0.SP01//javax.faces.component.UIComponent.encodeAll(UIComponent.java:1648)
    at javax.faces.api@3.1.0.SP01//javax.faces.component.UIComponent.encodeAll(UIComponent.java:1651)
    at javax.faces.api@3.1.0.SP01//javax.faces.component.UIComponent.encodeAll(UIComponent.java:1651)
    at com.sun.jsf-impl@2.3.17.SP01//com.sun.faces.application.view.FaceletViewHandlingStrategy.renderView(FaceletViewHandlingStrategy.java:461)
    [...]
Caused by: javax.el.ELException: java.lang.IllegalAccessException: class javax.el.BeanELResolver cannot access class sun.util.calendar.ZoneInfo (in module java.base) because module java.base does not export sun.util.calendar to unnamed module @6a1cb0de
    at javax.el.api@2.0.0.Final//javax.el.BeanELResolver.getValue(BeanELResolver.java:193)
    at com.sun.jsf-impl@2.3.17.SP01//com.sun.faces.el.DemuxCompositeELResolver._getValue(DemuxCompositeELResolver.java:156)
    at com.sun.jsf-impl@2.3.17.SP01//com.sun.faces.el.DemuxCompositeELResolver.getValue(DemuxCompositeELResolver.java:184)
    at org.glassfish.jakarta.el@3.0.3.jbossorg-4//com.sun.el.parser.AstValue.getValue(AstValue.java:114)
    at org.glassfish.jakarta.el@3.0.3.jbossorg-4//com.sun.el.parser.AstValue.getValue(AstValue.java:177)
    at org.glassfish.jakarta.el@3.0.3.jbossorg-4//com.sun.el.parser.AstDeferredExpression.getValue(AstDeferredExpression.java:39)
    at org.glassfish.jakarta.el@3.0.3.jbossorg-4//com.sun.el.parser.AstCompositeExpression.getValue(AstCompositeExpression.java:44)
    at org.glassfish.jakarta.el@3.0.3.jbossorg-4//com.sun.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:183)
    at org.jboss.weld.core@3.1.9.Final//org.jboss.weld.module.web.el.WeldValueExpression.getValue(WeldValueExpression.java:50)
    at org.jboss.weld.core@3.1.9.Final//org.jboss.weld.module.web.el.WeldValueExpression.getValue(WeldValueExpression.java:50)
    at com.sun.jsf-impl@2.3.17.SP01//com.sun.faces.facelets.el.TagValueExpression.getValue(TagValueExpression.java:73)
    ... 73 more
Caused by: java.lang.IllegalAccessException: class javax.el.BeanELResolver cannot access class sun.util.calendar.ZoneInfo (in module java.base) because module java.base does not export sun.util.calendar to unnamed module @6a1cb0de
    at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:392)
    at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:674)
    at java.base/java.lang.reflect.Method.invoke(Method.java:560)
    at javax.el.api@2.0.0.Final//javax.el.BeanELResolver.getValue(BeanELResolver.java:186)
    ... 83 more

看来,问题出在EL表达式访问java.util.TimeZoneTimeZone class 在内部使用 sun.util.calendar.ZoneInfo。而且这似乎不再合法了。

这只发生在 Java 17。当 运行ning 在 Java 11 时,一切正常。

我可以通过在启动 wildfly 时添加以下参数来解决异常问题:

--add-exports=java.base/sun.util.calendar=ALL-UNNAMED

但是,我认为应该可以 运行 没有这个解决方法的示例。

知道我遗漏了什么吗?这甚至可能是 Java/JDK 17 中的错误?

它可以通过普通 Java 应用程序 class 重现,如下所示:

package com.Whosebug.q72361100;

import java.lang.reflect.Method;
import java.util.TimeZone;

public class Test {
    public static void main(String... args) throws Exception {
        TimeZone instance = TimeZone.getDefault();
        Class<?> cls = instance.getClass();
        Method method = cls.getMethod("getRawOffset");
        Object result = method.invoke(instance); // java.lang.IllegalAccessException
        System.out.println(result);
    }
}

这里的问题是 instance.getClass() returns sun.util.calendar.ZoneInfo 因为那是 TimeZone#getDefault() 返回的实现。解决方法是使用 TimeZone.class 而不是 instance.getClass():

package com.Whosebug.q72361100;

import java.lang.reflect.Method;
import java.util.TimeZone;

public class Test {
    public static void main(String... args) throws Exception {
        TimeZone instance = TimeZone.getDefault();
        Class<?> cls = TimeZone.class; // Work around
        Method method = cls.getMethod("getRawOffset");
        Object result = method.invoke(instance);
        System.out.println(result);
    }
}

我认为这需要更改 EL 规范。理想情况下,它应该在声明的 super classes 中进一步搜索,如果该方法可以按照 Method#canAccess() 访问,然后改用它。

package com.Whosebug.q72361100;

import java.lang.reflect.Method;
import java.util.TimeZone;

public class Test {
    public static void main(String... args) throws Exception {
        TimeZone instance = TimeZone.getDefault();
        Class<?> cls = instance.getClass();
        Method method = getAccessibleMethod(instance, cls, "getRawOffset"); // Look in superclasses as well.
        Object result = method.invoke(instance);
        System.out.println(result);
    }

    private static Method getAccessibleMethod(Object instance, Class<?> cls, String methodName) throws NoSuchMethodException {
        Method method = cls.getMethod(methodName);

        if (method.canAccess(instance)) {
            return method;
        }

        return getAccessibleMethod(instance, cls.getSuperclass(), methodName);
    }

}

我在 EL 规范中创建了一个问题:https://github.com/jakartaee/expression-language/issues/188

在他们修复它之前,您可以通过为其添加专用 getter 来解决它:

public int getDefaultTZrawOffset() {
    return getDefaultTZ().getRawOffset();
}
#{myWarBean.defaultTZrawOffset}