1

I want to create a custom annotation to skip method execution

This is my annotation code, with the validator class

@Target({ METHOD , FIELD , PARAMETER } )
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy={MyValidator .class})
public @interface MyAnnotation {

    String message() default "DEFAULT_FALSE";

    Class<?>[] groups() default{};

    Class<? extends Payload>[] payload() default{};

}

I tried it with validator. This is how my validator looks like

public class MyValidator implements ConstraintValidator<MyAnnotation, String >{

    @Override
    public void initialize(MyAnnotation arg0) {

    }

    @Override
    public boolean isValid(String arg0, ConstraintValidatorContext arg1) {

        if(str=="msg"){
            return true;
        }
        return false;
    }

}

And this is how I want to use -- I want to use the annotation on method level and to skip the method execution.

I don't know if it is possible.. Please help.

public class Test {



    public static void main(String[] args) {
    Test t = new Test();
        boolean valid=false;

         valid=t.validate();
        System.out.println(valid);

    }

@MyAnnotation(message="msg")
    public boolean validate(){

     // some code to return true or false
    return true;


    }
}
Ashish Shetkar
  • 1,414
  • 2
  • 18
  • 35
  • 3
    you need to change `if(str=="msg"){` to use `equals()` – Sharon Ben Asher Feb 28 '17 at 12:49
  • how is the "skipping" thing going to happen? – Sharon Ben Asher Feb 28 '17 at 12:51
  • hi , yes thats my question if it is possible using such validator , i have no idea if it can be done – Ashish Shetkar Feb 28 '17 at 12:52
  • @SharonBenAsher , pls suggest if there is another way of doing it , any help is appreciated :) – Ashish Shetkar Feb 28 '17 at 12:53
  • calling a method is done in the JVM. no way to influence that except if you use reflection – Sharon Ben Asher Feb 28 '17 at 12:55
  • and suppose you skip `validate()`. what do you want variable `valid` to have? – Sharon Ben Asher Feb 28 '17 at 12:57
  • Why you need it? It will not work such way. – Tarwirdur Turon Feb 28 '17 at 13:00
  • Hello , i want to use the annotation to skip method execution if some condition is not satisfied – Ashish Shetkar Mar 01 '17 at 04:54
  • pls help if it is possible this way – Ashish Shetkar Mar 01 '17 at 06:39
  • 2
    If you already know that you want to skip method execution - and you have to know if you want to annotate the target methods beforehand - why do you call or implement them at all? I know how you can easily implement that via AspectJ, but the approach as such is flawed. And the question remains, what should non-void methods return if skipped? Give a more reasonable example, then I might feel inclined to help. I need to understand the actual problem you want to solve, not how you think it should be solved. Those two are not necessarily the same. – kriegaex Mar 01 '17 at 11:49
  • hello @kriegaex , Yes , I understand your concern. This is a bit odd situation. My method just returns boolean. Before calling the method my boolean will be set to false. The validate method will do some business logic to return true or false as per business requirement. But if the annotation does not have the value - @MyAnnotation(message="msg") - ( because for values other than "msg" the business method returns true for some values , and that fails my main requirement ) then the entire method execution should skip and my boolean remains false. – Ashish Shetkar Mar 02 '17 at 04:59
  • In addition to this i also have future requirement that , when it is not required - I will comment or remove the annotaion - so that the method will always get called after removing the annotation – Ashish Shetkar Mar 02 '17 at 05:02

2 Answers2

4

You should use AOP for that. Create a AspectJ project, and try something like this:

MyAnnotation.java:

package moo.aspecttest;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(value = { ElementType.METHOD })
public @interface MyAnnotation
{
    public String value();
}

MyAspectClass.java:

package moo.aspecttest;

import java.lang.reflect.Method;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;

@Aspect
public class MyAspectClass
{

    @Around("execution(* *(..))")
    public Object aroundAdvice(ProceedingJoinPoint point) throws Throwable
    {
        Method method = MethodSignature.class.cast(point.getSignature()).getMethod();
        String name = method.getName();
        MyAnnotation puff = method.getAnnotation(MyAnnotation.class);
        if (puff != null) {
            System.out.println("Method " + name + " annotated with " + puff.value() + ": skipped");
            return null;
        } else {
            System.out.println("Method " + name + " non annotated: executing...");
            Object toret = point.proceed();
            System.out.println("Method " + name + " non annotated: executed");
            return toret;
        }
    }
}

MyTestClass.java:

package moo.aspecttest;

public class MyTestClass
{

    @MyAnnotation("doh")
    public boolean validate(String s) {
        System.out.println("Validating "+s);
        return true;
    }

    public boolean validate2(String s) {
        System.out.println("Validating2 "+s);
        return true;
    }

    public static void main(String[] args)
    {
        MyTestClass mc = new MyTestClass();

        mc.validate("hello");
        mc.validate2("cheers");

        }
    }
}

output generated when you run it:

Method main non annotated: executing...
Method validate annotated with doh: skipped
Method validate2 non annotated: executing...
Validating2 cheers
Method validate2 non annotated: executed
Method main non annotated: executed

I used a very generic aroundAdvice, but you can use a beforeAdvice, if you want. Indeed, I think that point is clear.

Sampisa
  • 1,487
  • 2
  • 20
  • 28
  • hello , this is some what i am trying , but i tired this , the execution flow does not even go to aspect class , are you missing something here..? – Ashish Shetkar Mar 01 '17 at 09:40
  • what i exactly want is to skip the method execution of method - validate , which you annotated in the example - with @MyAnnotation("doh") – Ashish Shetkar Mar 01 '17 at 11:15
  • 1
    If it does not work for you, **you** are missing something, not @Sampisa. Can you maybe learn how to ask questions in a meaningful way which also conveys your actual intent? – kriegaex Mar 01 '17 at 11:50
  • 1
    Please, remember to add AspectJ dependencies/runtime libraries to your project. Easily, if you use Eclipse, right-click to your project , Configure -> Convert to AspectJ project. Here you can find a easy tutorial: http://o7planning.org/en/10257/java-aspect-oriented-programming-tutorial-with-aspectj – Sampisa Mar 01 '17 at 14:38
  • hello @Sampisa ,Thankyou for this . I tried it , but still it always skips the annotated method , what i want is , when my annotation is @AnnotatioClass(message="msg") - then it shall execute my method , and if value is other than "msg" - then it shall not execute the method – Ashish Shetkar Mar 02 '17 at 08:58
  • 1
    @Sampisa, your remark that a `@Before` advice could be used instead is false. You cannot stop a method from executing (other than by means of throwing an exception) or change the return value in a before advice. – kriegaex Mar 02 '17 at 10:32
  • @Ashish you can then change code testing puff.value() (or any other custom attribute defined in @interface) and return "null" to avoid method execution. The fact that your app doesn't work, is just matter of runtime libraries missing. Please follow explanation on tutorial in link I sent you, and everything will run smoothly. – Sampisa Mar 02 '17 at 21:22
  • @Sampisa, how do you like my approach? Is it not way simpler than yours, just a simple pointcut and a one-line method returning `false`? No cast, no if-else. – kriegaex Mar 02 '17 at 21:24
  • @kriegaex IMHO your solution is technically complete, well structured, but personally I prefer to let sugar be added by the... student. Keep in mind that my code was written in 10 minutes, in a couple of pomodoro pauses, and it is a quick&readable draft useful to explore whole process. Indeed... chapeau :) – Sampisa Mar 02 '17 at 21:35
  • 1
    So you are telling to an agile coach that you use your pomodoro pauses for repetitive multi-tasking context switches. Hmm... ;-) I won't tell your Scrum Master. – kriegaex Mar 02 '17 at 22:10
  • 1
    @Sampisa , thanks man , your answer make me understood the aspect context , but the below answer is working as required – Ashish Shetkar Mar 03 '17 at 06:34
2

It is actually very simple, sort of the simplest aspect one can write. ;-)

The ugly thing about your sample code is that it uses several classes for which you do not show the source code, so I had to create dummy classes/interfaces in order to make your code compile. You also do not show how the validator is applied, so I have to speculate. Anyway, here is a fully self-consistent set of sample classes:

Helper classes:

This is just scaffolding in order to make everything compile.

package de.scrum_master.app;

public interface Payload {}
package de.scrum_master.app;

public class ConstraintValidatorContext {}
package de.scrum_master.app;

public @interface Constraint {
  Class<MyValidator>[] validatedBy();
}
package de.scrum_master.app;

import java.lang.annotation.Annotation;

public interface ConstraintValidator<T1 extends Annotation, T2> {
  void initialize(T1 annotation);
  boolean isValid(T2 value, ConstraintValidatorContext validatorContext);
}
package de.scrum_master.app;

public class MyValidator implements ConstraintValidator<MyAnnotation, String> {
  @Override
  public void initialize(MyAnnotation annotation) {}

  @Override
  public boolean isValid(String value, ConstraintValidatorContext validatorContext) {
    if ("msg".equals(value))
      return true;
    return false;
  }
}
package de.scrum_master.app;

import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;

import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.*;

@Target({ METHOD, FIELD, PARAMETER })
@Retention(RUNTIME)
@Constraint(validatedBy = { MyValidator.class })
public @interface MyAnnotation {
  String message() default "DEFAULT_FALSE";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
}

Driver application:

If you want to test something, you do not just need a positive test case, but also a negative one. Because you did not provide that, user Sampisa's answer was not what you were looking for. BTW, I think you should have been able to deduce from it the solution by yourself. You did not even try. Do you not have any programming experience?

package de.scrum_master.app;

public class Application {
  public static void main(String[] args) {
    Application application = new Application();
    System.out.println(application.validate1());
    System.out.println(application.validate2());
  }

  @MyAnnotation(message = "execute me")
  public boolean validate1() {
    return true;
  }

  @MyAnnotation(message = "msg")
  public boolean validate2() {
    return true;
  }
}

Aspect:

The only reason why I add another sample aspect in addition to Sampisa's is that his solution is suboptimal with regard to his reflection usage. It is ugly and it is slow. I think my solution is a bit more elegant. See for yourself:

package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class SkipValidationAspect {
  @Around("execution(@de.scrum_master.app.MyAnnotation(message=\"msg\") boolean *(..))")
  public boolean skipValidation(ProceedingJoinPoint thisJoinPoint) throws Throwable {
    return false;
  }
}

Very simple, is it not?

Console log:

true
false

Et voilà - I think this is what you were looking for.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • aye aye captain ... one of the finest answer i have seen across stackoverflow.com – Ashish Shetkar Mar 03 '17 at 06:31
  • i just edited the aspect little bit , combination of both answers – Ashish Shetkar Mar 03 '17 at 07:03
  • Hi @kriegaex , I added this in project1 , and am using project1.jar in project2 . it is not working in project2. Does project1 needs to be AspectJ project ..? and Does project2 also needs to be AspectJ project – Ashish Shetkar Mar 03 '17 at 11:49
  • Please create a new question for it, it is off-topic. And therein, describe the problem better. But before you do, please be so kind as to first read a few AspectJ tutorials. Other people cannot do everything for you. And thanks in advance for accepting and upvoting my answer. P.S.: I do not understand why you think you should combine my simple, elegant solution with another equivalent, but complicated one. Do you think much medicine helps more when you are ill? – kriegaex Mar 03 '17 at 12:13
  • hello @kriegaex - i tried adding code in the validate1 method , it is not getting executed , when i see the exoprted jar - using decompiler - then i see the validate1() - contains some method called - validate1_aroundBody1$advice - , and my method code is in validate1_aroundBody0 which is not getting called why so ... ? – Ashish Shetkar Mar 16 '17 at 13:52
  • i need the code inside - validate1_aroundBody0 - to be executed - which will is compiled from - validate1() – Ashish Shetkar Mar 16 '17 at 13:53
  • I said, create a new question. I mean it. – kriegaex Mar 17 '17 at 01:08
  • http://stackoverflow.com/questions/42850782/aspect-around-annotations-used-custom-annotated-method-not-getting-execute – Ashish Shetkar Mar 17 '17 at 06:37