NullPointerException в FacesCompositeELResolver, как исправить или получить поддержку? - PullRequest
1 голос
/ 17 апреля 2019

Мы получаем NPE в Производстве, которое использует apache-tomcat-8.5.28 или, возможно, 8.5.34 (на самом деле не уверен, какой, но я могу попытаться выяснить это), со следующим стеком:

java.lang.NullPointerException: while trying to invoke the method javax.el.ELResolver.getValue(javax.el.ELContext, java.lang.Object, java.lang.Object) of a null object loaded from an array (which itself was loaded from field javax.el.CompositeELResolver.resolvers of an object) with an index loaded from local variable 'i'
    at javax.el.CompositeELResolver.getValue(CompositeELResolver.java:62) ~[el-api.jar:3.0.FR]
    at com.sun.faces.el.FacesCompositeELResolver.getValue(FacesCompositeELResolver.java:72) ~[jsf-impl.jar:1.2_13-b02-FCS]
    at org.apache.el.parser.AstValue.getValue(AstValue.java:169) ~[jasper-el.jar:8.5.34]
    at org.apache.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:190) ~[jasper-el.jar:8.5.34]
    at com.sun.faces.application.ValueBindingValueExpressionAdapter.getValue(ValueBindingValueExpressionAdapter.java:113) ~[jsf-impl.jar:1.2_13-b02-FCS]
    ...

Ошибка не всегда возникает, хотя, как только это происходит, кажется, что это происходит много для этого конкретного узла сервера. Последние несколько дней я копался в декомпилированных JAR-файлах и устанавливал точки останова, чтобы попытаться понять ошибку.

Это класс FacesCompositeELResolver, который расширяет CompositeELResolver, и существует только 2 экземпляра этого класса: один с chainType = Faces, а другой с chainType = JSP. Первый (где chainType = Faces) создается как часть первого вызова FacesServlet.service (), который происходит только при первом обращении к серверу.

Родительский класс CompositeElResolver пытается вызвать getValue с кодом, подобным этому:

// copied from CompositeELResolver.java

public Object getValue(ELContext context, Object base, Object property) {
    context.setPropertyResolved(false);
    int sz = this.size;

    for(int i = 0; i < sz; ++i) {
        Object result = this.resolvers[i].getValue(context, base, property); // <----  line 62 NPE here
        if(context.isPropertyResolved()) {
            return result;
        }
    }

    return null;
}

В этом случае важно признать, что в ошибке упоминается, что массив "this.resolvers [i]" содержит нулевой элемент в i-й позиции. Следующий код отвечает за установку значения внутри массива this.resolvers:

// copied from CompositeELResolver.java

public void add(ELResolver elResolver) {
    Objects.requireNonNull(elResolver);       // <---- line 47
    if(this.size >= this.resolvers.length) {
        ELResolver[] nr = new ELResolver[this.size * 2];
        System.arraycopy(this.resolvers, 0, nr, 0, this.size);
        this.resolvers = nr;
    }

    this.resolvers[this.size++] = elResolver; // <---- line 54
}

По номинальному значению для массива this.resolvers было бы невозможно получить нулевое значение в позиции массива с индексом меньше this.size, поскольку строка 47 проверяет, что значение не может быть нулевым; однако это возможно в многопоточной среде с двумя потоками, совместно использующими одну и ту же ссылку на объект. Первая часть, которую я упомянул о том, что FacesCompositeElResolver является общим объектом, который создается только один раз и используется всеми потоками.

Предположим, что 2 потока вызвали FacesServlet.service () в одно и то же время и выполнили вызов add(elResolver), тогда строка 54 this.resolvers[this.size++] = elResolver; может быть вызвана одновременно в одном и том же экземпляре FacesCompositeELResolver, что приведет к this.size++ произойдет 2 раза перед выполнением присваивания позиции массива, вызывая состояние гонки.

Итак, я поставил точку останова в функции CompositeELResolver.add и посмотрю, кто ее вызывает, и посмотрим, защищен ли этот код какой-то функцией synchronized, которая защитит этот код в многопоточной среде.

javax.el.CompositeELResolver.add():54
com.sun.faces.el.FacesCompositeElResolver.add():60
com.sun.faces.el.ElUtils.buildFacesResolver():160

Это может быть проблемой, хотя я еще не воспроизвел проблему, я подозреваю, что виноват вышеупомянутый стек.

Подводя итог, можно сказать, что статическая функция ElUtils.buildFacesResolver модифицирует общий объект типа FacesCompositeELResolver, и если серверу «посчастливится» вызвать этот же стек в 2 потоках при запуске, это приведет к this.resolvers массив в экземпляре синглтона, содержащий нулевой объект, который будет постоянно через NullPointerException, пока сервер не отскочит - что, похоже, является именно тем, что мы видим.

Но вот что на самом деле ничего из этого не относится к выполняемому коду приложения, оно похоже на часть jsf-impl.jar, который мы используем и который в версии 1.2_13-b02-FCS отображается в соответствии с manifest.mf файл в этой банке. Я не уверен, откуда взялась эта версия или как я мог ее изменить, если мне нужно.

Может ли кто-нибудь помочь мне понять, где я могу получить поддержку в этих конкретных классах, могу ли я контролировать это?

Любая другая помощь или подсказки, которые кто-то может предоставить по этому вопросу, были бы полезны.

...