I have an interface with the following default method:
default Integer getCurrentYear() {return DateUtil.getYear();}
I also have a controller that implements this interface, but it does not overwrite the method.
public class NotifyController implements INotifyController
I'm trying to access this method from my xhtml like this:
#{notifyController.currentYear}
However when I open the screen the following error occurs:
The class 'br.com.viasoft.controller.notify.notifyController' does not have the property 'anoAtual'
If I access this method from an instance of my controller, it returns the right value, however when I try to access it from my xhtml as a "property" it occurs this error.
Is there a way to access this interface property from a reference from my controller without having to implement the method?
Interfaces can have default methods with implementation in Java 8 on later. Interfaces can have static methods as well, similar to static methods in classes. Default methods were introduced to provide backward compatibility for old interfaces so that they can have new methods without affecting existing code.
Technically the class that implements the interface is free to use a property or a getter. In the same way there is no way for us to specify that a property is a setter in an interface, but we can still use a setter in our class.
Default methods enable you to add new functionality to existing interfaces and ensure binary compatibility with code written for older versions of those interfaces. In particular, default methods enable you to add methods that accept lambda expressions as parameters to existing interfaces.
A class can override a default interface method and call the original method by using super , keeping it nicely in line with calling a super method from an extended class. But there is one catch, you need to put the name of the interface before calling super this is necessary even if only one interface is added.
This may be considered as a bug, or one might argue it is a decision to not support default methods as properties.
See in JDK8 java.beans.Introspector.getPublicDeclaredMethods(Class<?>)
or in JDK13 com.sun.beans.introspect.MethodInfo.get(Class<?>)
at line if (!method.getDeclaringClass().equals(clz))
And only the super class (recursively upto Object
, but not the interfaces) are added, see java.beans.Introspector.Introspector(Class<?>, Class<?>, int)
when setting superBeanInfo
.
Solutions:
Use EL method call syntax (i.e. not property access): #{notifyController.getCurrentYear()}
in your case.
Downside: You have to change the JSF code and must consider for each use if it may be a default method. Also refactoring forces changes that are not recognized by the compiler, only during runtime.
Create an EL-Resolver to generically support default methods. But this should use good internal caching like the standard java.beans.Introspector
to not slow down the EL parsing.
See "Property not found on type" when using interface default methods in JSP EL for a basic example (without caching).
If only a few classes/interfaces are affected simply create small BeanInfo
classes.
The code example below shows this (basing on your example).
Downside: A separate class must be created for each class (that is used in JSF/EL) implementing such an interface.
See also: Default method in interface in Java 8 and Bean Info Introspector
=> static getBeanInfo()
in the interface with default methods
=> simple+short BeanInfo
class for each class extending the interface
interface INotifyController {
default Integer getCurrentYear() { ... }
default boolean isAHappyYear() { ... }
default void setSomething(String param) { ... }
/** Support for JSF-EL/Beans to get default methods. */
static java.beans.BeanInfo[] getBeanInfo() {
try {
java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(INotifyController.class);
if (info != null) return new java.beans.BeanInfo[] { info };
} catch (java.beans.IntrospectionException e) {
//nothing to do
}
return null;
}
}
public class NotifyController implements INotifyController {
// your class implementation
...
}
// must be a public class and thus in its own file
public class NotifyControllerBeanInfo extends java.beans.SimpleBeanInfo {
@Override
public java.beans.BeanInfo[] getAdditionalBeanInfo() {
return INotifyController.getBeanInfo();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With