Мы получаем 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 файл в этой банке. Я не уверен, откуда взялась эта версия или как я мог ее изменить, если мне нужно.
Может ли кто-нибудь помочь мне понять, где я могу получить поддержку в этих конкретных классах, могу ли я контролировать это?
Любая другая помощь или подсказки, которые кто-то может предоставить по этому вопросу, были бы полезны.