I have a program written in Java 11 with two custom annotation. One on Method level and one on Parameter level.
I`m using Spring AOP to act on methods that use my Custom annotations. But I have not found a way to get the value ouf of an optional annotation that is on Parameter level.
#CustomAnnotation
public void myMethod(#CustomValue String param1, int param2) {
...
}
#AfterReturning(value = "#annotation(customAnnotation)", argNames = "customAnnotation, jp")
public void afterReturning(JoinPoint jp, CustomAnnotation customAnnotation) {
...
}
The program works well with my #CustomAnnotation, but I have problems to get the value out of parameters that is annotated with #CustomValue. #CustomValue is useless if the method is not annotated with #CustomAnnotation. And the number of parameters that can be annotated with #CustomValue is 0 or more.
How can I get the values from the parameters that is annotated with #CustomValue on a method that is annotated with #CustomAnnotation?
#AfterReturning(value = "#annotation(CustomAnnotation)", argNames = "customAnnotation, jp")
public void afterReturning(JoinPoint jp, CustomAnnotation customAnnotation) {
CustomAnnotation annotation = customAnnotation;
List<Parameter> parameterList = new ArrayList<>();
if (jp.getSignature() instanceof MethodSignature) {
MethodSignature signature =
(MethodSignature) jp.getSignature();
Method method = signature.getMethod();
anno = method.getAnnotation(CustomAnnotation.class);
Parameter[] params = method.getParameters();
for (Parameter param : params) {
try {
if(param.getAnnotation(CustomValue.class) !=null){
parameterList.add(param);
}
} catch (Exception e) {
//do nothing
}
}
}
}
You should now get the list of parameters annotated with #CustomValue. This exact code may not work while copy-paste, but you should get the idea.
Maybe there is a better way, but I solved this by using following:
#AfterReturning(value = "#annotation(customAnnotation)", argNames = "customAnnotation, jp")
public void afterReturning(JoinPoint jp, CustomAnnotation customAnnotation) {
MethodSignature signature = (MethodSignature) jp.getSignature();
Parameter[] parameters = signature.getMethod().getParameters();
Object[] args = jp.getArgs();
for (int i = 0; i < args.length; ++i) {
if (parameters[i].isAnnotationPresent(CustomValue.class)) {
Object paramValue = args[i];
}
}
}
Related
Implementing a custom constraint annotation, like #MySize requires me testing it with unit tests to see if it functions correctly:
public class MySizeTest {
#Test
public void noMinMax() {
Dummy dummy = new Dummy();
// some asserts or so
dummy.setMyField("");
dummy.setMyField(null);
dummy.setMyField("My text");
}
#Test
public void onlyMin() {
// change #MySize to have min: #MySize(min = 1)
... how?
... then test with some setMyField:
Dummy dummy = new Dummy();
// some asserts or so
dummy.setMyField("");
dummy.setMyField(null);
dummy.setMyField("My text");
}
#Test
public void onlyMax() {
// change #MySize to have max: #MySize(max = 50)
...
}
#Test
public void bothMinMax() {
// change #MySize to have min and max: #MySize(min = 1, max = 50)
...
}
private class Dummy {
#MySize()
String myField;
public String getMyField() {
return myField;
}
public void setMyField(String myField) {
this.myField = myField;
}
}
}
I assume this has to be done with reflection, but I have no idea how.
Basicly don't have to use reflection just create a Validator instance and use that for validating.
For examaple:
When the annotation is:
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = MyValidator.class)
public #interface MyAnnotation {
String message() default "Invalid value (it must be foo)";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and the related validator is:
public class MyValidator implements ConstraintValidator<MyAnnotation, String> {
#Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if (null == s) return true;
return "foo".equalsIgnoreCase(s);
}
}
Then the tests sould be like these:
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class MyValidatorTest {
private Validator validator;
#BeforeAll
void init() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
private static class TestObject {
#MyAnnotation
private String testField;
TestObject() {
this(null);
}
TestObject(String value) {
testField = value;
}
public String getTestField() {
return testField;
}
public void setTestField(String testField) {
this.testField = testField;
}
}
#Test
void shouldValidForNullValue() {
var obj = new TestObject();
var violations = validator.validate(obj); // Set<ConstraintViolation<TestObject>>
Assertions.assertTrue(violations.isEmpty(), String.format("Object should valid, but has %d violations", violations.size()));
}
#Test
void shouldValidForFooValue() {
var obj = new TestObject("foo");
var violations = validator.validate(obj); // Set<ConstraintViolation<TestObject>>
Assertions.assertTrue(violations.isEmpty(), String.format("Object should valid, but has %d violations", violations.size()));
}
#Test
void shouldInvalidForBarValue() {
var obj = new TestObject("bar");
var violations = validator.validate(obj); // Set<ConstraintViolation<TestObject>>
Assertions.assertEquals(1, violations.size());
}
}
Update (2020.05.21.) - Using attributes and AnnotationFactory
Based on comments I've updated my answer.
If you want to test only the validation logic then just create an Annotation instance and call the isValid method which is returns true or false
Hibernate Validator provides AnnotationFactory.create(...) method to make annotaion instance.
After that you can create an instance of your custom validator and call initialize and isValid methods in your test case.
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = MyHasAttributesValidator.class)
public #interface MyAnnotationHasAttributes {
String message() default "Invalid value (it must be foo)";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int attributeOne() default 10;
int attributeTwo() default 20;
}
related validator:
public class MyHasAttributesValidator implements ConstraintValidator<MyAnnotationHasAttributes, String> {
private MyAnnotationHasAttributes ann;
#Override
public void initialize(MyAnnotationHasAttributes constraintAnnotation) {
ann = constraintAnnotation;
}
#Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if (null == s) return true;
return s.length() >= ann.attributeOne() && s.length() < ann.attributeTwo();
}
}
and the modified test (which has failing assertion):
public class HasAttributeValidatorTest {
private MyAnnotationHasAttributes createAnnotation(Integer one, Integer two) {
final Map<String, Object> attrs = new HashMap<>();
if (null != one) {
attrs.put("attributeOne", one);
}
if (null != two) {
attrs.put("attributeOne", two);
}
var desc = new AnnotationDescriptor.Builder<>(MyAnnotationHasAttributes.class, attrs).build();
return AnnotationFactory.create(desc);
}
#ParameterizedTest
#MethodSource("provideValues")
void testValidator(Integer one, Integer two, String input, boolean expected) {
MyAnnotationHasAttributes ann = createAnnotation(one, two);
MyHasAttributesValidator validator = new MyHasAttributesValidator();
validator.initialize(ann);
var result = validator.isValid(input, null);
Assertions.assertEquals(expected, result, String.format("Validation must be %s but found: %s with params: %d, %d, %s", expected, result, one, two, input));
}
private static Stream<Arguments> provideValues() {
return Stream.of(
Arguments.of(null, null, null, true),
Arguments.of(null, 20, "foo", true),
Arguments.of(null, null, RandomStringUtils.randomAlphabetic(30), false)
);
}
}
Limitations of this solution
Vendor lock
In this case your test using Hibernate Validator which is a specific implementation if the Bean Validation standards. Honestly I don't think it's a huge problem, because Hibernate Validator is the refecerence implementation and the most popular bean validation library. But technically it's a vendor lock.
Cross field validation is unavailable
This soulution works only in one-field situations. If you have e.g a cross-field validator (e.g. password and confirmPassword matching) this example won't fit.
Type independent validation needs more work
Like previously mentioned #Size annotation belongs to several different validator implementations based on type (primitives, collections, string, etc.).
Using this solution you always have to chose the certain validator manually and test it.
Only the isValid method can be tested
In this case you won't be able to test another things just the isValid method. I mean e.g. error message has expected format and parameters or something like this.
In sort, I know creating many different fields with different annotation attributes is boring but I strongly prefer that way because you can test everything you need about your validator.
I have a struts 2 action class:
public class MyAction{
private ArrayList<User> users;
public void setUsers(ArrayList<User> users){
this.users = users;
}
public String doMyAction(){
//...
}
}
the doMyAction method has a AOP pointcut, so MyAction is actually a cglib proxied class at runtime, and the users field will be populated by json data from client, when aop is enabled, struts JSONInterceptor will fail to populate json data into users field. I debuged with the source code of struts json plugin and found this in org.apache.struts2.json.JSONPopulator:
public void populateObject(Object object, final Map elements)
throws IllegalAccessException,
InvocationTargetException, NoSuchMethodException, IntrospectionException,
IllegalArgumentException, JSONException, InstantiationException {
Class clazz = object.getClass();
BeanInfo info = Introspector.getBeanInfo(clazz);
PropertyDescriptor[] props = info.getPropertyDescriptors();
// iterate over class fields
for (int i = 0; i < props.length; ++i) {
PropertyDescriptor prop = props[i];
String name = prop.getName();
if (elements.containsKey(name)) {
Object value = elements.get(name);
Method method = prop.getWriteMethod();
if (method != null) {
JSON json = method.getAnnotation(JSON.class);
if ((json != null) && !json.deserialize()) {
continue;
}
// use only public setters
if (Modifier.isPublic(method.getModifiers())) {
Class[] paramTypes = method.getParameterTypes();
Type[] genericTypes = method.getGenericParameterTypes();
if (paramTypes.length == 1) {
Object convertedValue = this.convert(paramTypes[0],
genericTypes[0], value, method);
method.invoke(object, new Object[] { convertedValue });
}
}
}
}
}
}
and on this line:
Type[] genericTypes = method.getGenericParameterTypes();
when AOP is enabled, it returns java.util.ArrayList against the setter method of users field. but java.util.ArrayList<User> expected.
It seems that my action class lose it's generic info when proxied by cglib. I also found a old bug about this.
I can exclude my method from aop configurations to fix this. but I still want to know if there is a better solution?
My idea is try to find the actual type behind the proxy. According to spring documentation, any proxy obtained from spring aop implements the org.springframework.aop.framework.Advised interface, and this interface expose method to query the target class.
Any AOP proxy obtained from Spring can be cast to this interface to allow manipulation of its AOP advice.
so here we have a considerable option, we can download the struts json plugin source code and build our own one, with modification on populateObject method of JSONPopulator
public void populateObject(Object object, final Map elements) throws IllegalAccessException,
InvocationTargetException, NoSuchMethodException, IntrospectionException,
IllegalArgumentException, JSONException, InstantiationException {
Class clazz = object.getClass();
// if it is a proxy, find the actual type behind it
if(Advised.class.isAssignableFrom(clazz)){
clazz = ((Advised)object).getTargetSource().getTargetClass();
}
BeanInfo info = Introspector.getBeanInfo(clazz);
PropertyDescriptor[] props = info.getPropertyDescriptors();
// iterate over class fields
for (int i = 0; i < props.length; ++i) {
PropertyDescriptor prop = props[i];
String name = prop.getName();
if (elements.containsKey(name)) {
Object value = elements.get(name);
Method method = prop.getWriteMethod();
if (method != null) {
JSON json = method.getAnnotation(JSON.class);
if ((json != null) && !json.deserialize()) {
continue;
}
// use only public setters
if (Modifier.isPublic(method.getModifiers())) {
Class[] paramTypes = method.getParameterTypes();
Type[] genericTypes = method.getGenericParameterTypes();
if (paramTypes.length == 1) {
Object convertedValue = this.convert(paramTypes[0], genericTypes[0], value,
method);
method.invoke(object, new Object[] { convertedValue });
}
}
}
}
}
}
please note these lines I added:
// if it is a proxy, find the actual type behind it
if(Advised.class.isAssignableFrom(clazz)){
clazz = ((Advised)object).getTargetSource().getTargetClass();
}
Cglib was created before generic types existed. A proxy is generated as a subclass of the proxied class in cglib which does not retain the generic type information. Therefore, you cannot query it from the proxy class.
I have a bean class
public class Group{string name;Type type; }
and another bean
public class Type{String name;}
Now, i want to bind group by using jdbi #BindBean
#SqlBatch("INSERT INTO (type_id,name) VALUES((SELECT id FROM type WHERE name=:m.type.name),:m.name)")
#BatchChunkSize(100)
int[] insertRewardGroup(#BindBean ("m") Set<Group> groups);
How can i bind the user defined object's property as member of the bean??
You could implement your own Bind-annotation here. I implemented one that I am adopting for this answer. It will unwrap all Type ones.
I think it could be made fully generic with a little more work.
Your code would look like this (please note that m.type.name changed to m.type):
#SqlBatch("INSERT ... WHERE name=:m.type),:m.name)")
#BatchChunkSize(100)
int[] insertRewardGroup(#BindTypeBean ("m") Set<Group> groups);
This would be the annotation:
#BindingAnnotation(BindTypeBean.SomethingBinderFactory.class)
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.PARAMETER})
public #interface BindTypeBean {
String value() default "___jdbi_bare___";
public static class SomethingBinderFactory implements BinderFactory {
public Binder build(Annotation annotation) {
return new Binder<BindTypeBean, Object>() {
public void bind(SQLStatement q, BindTypeBean bind, Object arg) {
final String prefix;
if ("___jdbi_bare___".equals(bind.value())) {
prefix = "";
} else {
prefix = bind.value() + ".";
}
try {
BeanInfo infos = Introspector.getBeanInfo(arg.getClass());
PropertyDescriptor[] props = infos.getPropertyDescriptors();
for (PropertyDescriptor prop : props) {
Method readMethod = prop.getReadMethod();
if (readMethod != null) {
Object r = readMethod.invoke(arg);
Class<?> c = readMethod.getReturnType();
if (prop.getName().equals("type") && r instanceof Type) {
r = ((Type) r).getType();
c = r.getClass();
}
q.dynamicBind(c, prefix + prop.getName(), r);
}
}
} catch (Exception e) {
throw new IllegalStateException("unable to bind bean properties", e);
}
}
};
}
}
}
Doing this in JDBI is not possible , you have to bring out the property and give is a argument.
I am using Spring AOP and have below aspect:
#Aspect
public class LoggingAspect {
#Before("execution(* com.mkyong.customer.bo.CustomerBo.addCustomer(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("logBefore() is running!");
System.out.println("hijacked : " + joinPoint.getSignature().getName());
System.out.println("******");
}
}
Above aspect intercepts addCustomer method execution. addCustomer method takes string as an input.
But I need to log input passed to addCustomer method inside logBefore method.
Is it possible to do so ?
You have a few options:
First, you can use the JoinPoint#getArgs() method which returns an Object[] containing all the arguments of the advised method. You might have to do some casting depending on what you want to do with them.
Second, you can use the args pointcut expression like so:
// use '..' in the args expression if you have zero or more parameters at that point
#Before("execution(* com.mkyong.customer.bo.CustomerBo.addCustomer(..)) && args(yourString,..)")
then your method can instead be defined as
public void logBefore(JoinPoint joinPoint, String yourString)
Yes, the value of any argument can be found using getArgs
#Before("execution(* com.mkyong.customer.bo.CustomerBo.addCustomer(..))")
public void logBefore(JoinPoint joinPoint) {
Object[] signatureArgs = thisJoinPoint.getArgs();
for (Object signatureArg: signatureArgs) {
System.out.println("Arg: " + signatureArg);
...
}
}
If you have to log all args or your method have one argument, you can simply use getArgs like described in previous answers.
If you have to log a specific arg, you can annoted it and then recover its value like this :
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface Data {
String methodName() default "";
}
#Aspect
public class YourAspect {
#Around("...")
public Object around(ProceedingJoinPoint point) throws Throwable {
Method method = MethodSignature.class.cast(point.getSignature()).getMethod();
Object[] args = point.getArgs();
StringBuilder data = new StringBuilder();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int argIndex = 0; argIndex < args.length; argIndex++) {
for (Annotation paramAnnotation : parameterAnnotations[argIndex]) {
if (!(paramAnnotation instanceof Data)) {
continue;
}
Data dataAnnotation = (Data) paramAnnotation;
if (dataAnnotation.methodName().length() > 0) {
Object obj = args[argIndex];
Method dataMethod = obj.getClass().getMethod(dataAnnotation.methodName());
data.append(dataMethod.invoke(obj));
continue;
}
data.append(args[argIndex]);
}
}
}
}
Examples of use :
public void doSomething(String someValue, #Data String someData, String otherValue) {
// Apsect will log value of someData param
}
public void doSomething(String someValue, #Data(methodName = "id") SomeObject someData, String otherValue) {
// Apsect will log returned value of someData.id() method
}
There is also another way if you define one pointcut for many advices it can be helpful:
#Pointcut("execution(#com.stackoverflow.MyAnnotation * *(..))")
protected void myPointcut() {
}
#AfterThrowing(pointcut = "myPointcut() && args(someId,..)", throwing = "e")
public void afterThrowingException(JoinPoint joinPoint, Exception e, Integer someId) {
System.out.println(someId.toString());
}
#AfterReturning(pointcut = "myPointcut() && args(someId,..)")
public void afterSuccessfulReturn(JoinPoint joinPoint, Integer someId) {
System.out.println(someId.toString());
}
Your can use either of the following methods.
#Before("execution(* ong.customer.bo.CustomerBo.addCustomer(String))")
public void logBefore1(JoinPoint joinPoint) {
System.out.println(joinPoint.getArgs()[0]);
}
or
#Before("execution(* ong.customer.bo.CustomerBo.addCustomer(String)), && args(inputString)")
public void logBefore2(JoinPoint joinPoint, String inputString) {
System.out.println(inputString);
}
joinpoint.getArgs() returns object array. Since, input is single string, only one object is returned.
In the second approach, the name should be same in expression and input parameter in the advice method i.e. args(inputString) and public void logBefore2(JoinPoint joinPoint, String inputString)
Here, addCustomer(String) indicates the method with one String input parameter.
you can get method parameter and its value and if annotated with a annotation with following code:
Map<String, Object> annotatedParameterValue = getAnnotatedParameterValue(MethodSignature.class.cast(jp.getSignature()).getMethod(), jp.getArgs());
....
private Map<String, Object> getAnnotatedParameterValue(Method method, Object[] args) {
Map<String, Object> annotatedParameters = new HashMap<>();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Parameter[] parameters = method.getParameters();
int i = 0;
for (Annotation[] annotations : parameterAnnotations) {
Object arg = args[i];
String name = parameters[i++].getDeclaringExecutable().getName();
for (Annotation annotation : annotations) {
if (annotation instanceof AuditExpose) {
annotatedParameters.put(name, arg);
}
}
}
return annotatedParameters;
}
If it's a single String argument, do:
joinPoint.getArgs()[0];
if your using #Aspect an option is add this method inside your Aspect and send the JoinPoint and the name of parameter you need.
private Object getParameter(ProceedingJoinPoint joinPoint, String parameterName) {
Object valueParameter = null;
if (Objects.nonNull(joinPoint) && joinPoint.getSignature() instanceof MethodSignature
&& Objects.nonNull(parameterName) ) {
MethodSignature method = (MethodSignature)joinPoint.getSignature();
String[] parameters = method.getParameterNames();
for (int t = 0; t< parameters.length; t++) {
if( Objects.nonNull(parameters[t]) && parameters[t].equals(parameterName)) {
Object[] obj = joinPoint.getArgs();
valueParameter = obj[t];
}
}
}
return valueParameter;
}
and the call example:
Object parameterObject = getParameter(joinPoint, "nameClient");
if ( Objects.nonNull(parameterObject) ) {
String parametro = String.valueOf(parameterObject);
}
Only need know the type of object for convert
I have a class that uses XML and reflection to return Objects to another class.
Normally these objects are sub fields of an external object, but occasionally it's something I want to generate on the fly. I've tried something like this but to no avail. I believe that's because Java won't allow you to access private methods for reflection.
Element node = outerNode.item(0);
String methodName = node.getAttribute("method");
String objectName = node.getAttribute("object");
if ("SomeObject".equals(objectName))
object = someObject;
else
object = this;
method = object.getClass().getMethod(methodName, (Class[]) null);
If the method provided is private, it fails with a NoSuchMethodException. I could solve it by making the method public, or making another class to derive it from.
Long story short, I was just wondering if there was a way to access a private method via reflection.
You can invoke private method with reflection. Modifying the last bit of the posted code:
Method method = object.getClass().getDeclaredMethod(methodName);
method.setAccessible(true);
Object r = method.invoke(object);
There are a couple of caveats. First, getDeclaredMethod will only find method declared in the current Class, not inherited from supertypes. So, traverse up the concrete class hierarchy if necessary. Second, a SecurityManager can prevent use of the setAccessible method. So, it may need to run as a PrivilegedAction (using AccessController or Subject).
Use getDeclaredMethod() to get a private Method object and then use method.setAccessible() to allow to actually call it.
If the method accepts non-primitive data type then the following method can be used to invoke a private method of any class:
public static Object genericInvokeMethod(Object obj, String methodName,
Object... params) {
int paramCount = params.length;
Method method;
Object requiredObj = null;
Class<?>[] classArray = new Class<?>[paramCount];
for (int i = 0; i < paramCount; i++) {
classArray[i] = params[i].getClass();
}
try {
method = obj.getClass().getDeclaredMethod(methodName, classArray);
method.setAccessible(true);
requiredObj = method.invoke(obj, params);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return requiredObj;
}
The Parameter accepted are obj, methodName and the parameters. For example
public class Test {
private String concatString(String a, String b) {
return (a+b);
}
}
Method concatString can be invoked as
Test t = new Test();
String str = (String) genericInvokeMethod(t, "concatString", "Hello", "Mr.x");
you can do this using ReflectionTestUtils of Spring (org.springframework.test.util.ReflectionTestUtils)
ReflectionTestUtils.invokeMethod(instantiatedObject,"methodName",argument);
Example : if you have a class with a private method square(int x)
Calculator calculator = new Calculator();
ReflectionTestUtils.invokeMethod(calculator,"square",10);
Let me provide complete code for execution protected methods via reflection. It supports any types of params including generics, autoboxed params and null values
#SuppressWarnings("unchecked")
public static <T> T executeSuperMethod(Object instance, String methodName, Object... params) throws Exception {
return executeMethod(instance.getClass().getSuperclass(), instance, methodName, params);
}
public static <T> T executeMethod(Object instance, String methodName, Object... params) throws Exception {
return executeMethod(instance.getClass(), instance, methodName, params);
}
#SuppressWarnings("unchecked")
public static <T> T executeMethod(Class clazz, Object instance, String methodName, Object... params) throws Exception {
Method[] allMethods = clazz.getDeclaredMethods();
if (allMethods != null && allMethods.length > 0) {
Class[] paramClasses = Arrays.stream(params).map(p -> p != null ? p.getClass() : null).toArray(Class[]::new);
for (Method method : allMethods) {
String currentMethodName = method.getName();
if (!currentMethodName.equals(methodName)) {
continue;
}
Type[] pTypes = method.getParameterTypes();
if (pTypes.length == paramClasses.length) {
boolean goodMethod = true;
int i = 0;
for (Type pType : pTypes) {
if (!ClassUtils.isAssignable(paramClasses[i++], (Class<?>) pType)) {
goodMethod = false;
break;
}
}
if (goodMethod) {
method.setAccessible(true);
return (T) method.invoke(instance, params);
}
}
}
throw new MethodNotFoundException("There are no methods found with name " + methodName + " and params " +
Arrays.toString(paramClasses));
}
throw new MethodNotFoundException("There are no methods found with name " + methodName);
}
Method uses apache ClassUtils for checking compatibility of autoboxed params
One more variant is using very powerfull JOOR library https://github.com/jOOQ/jOOR
MyObject myObject = new MyObject()
on(myObject).get("privateField");
It allows to modify any fields like final static constants and call yne protected methods without specifying concrete class in the inheritance hierarhy
<!-- https://mvnrepository.com/artifact/org.jooq/joor-java-8 -->
<dependency>
<groupId>org.jooq</groupId>
<artifactId>joor-java-8</artifactId>
<version>0.9.7</version>
</dependency>