my custom annotation is:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface CacheClear {
long versionId() default 0;
}
I want to achieve something like this, in which I can pass the method param "versionTo" to my custom annotation.
#CacheClear(versionId = {versionTo})
public int importByVersionId(Long versionTo){
......
}
What should I do?
That's not possible.
Annotations require constant values and a method parameter is dynamic.
You cannot pass the value, but you can pass the path of that variable in Spring Expression and use AOP's JoinPoint and Reflection to get and use it. Refer below:
Your Annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface CacheClear {
String pathToVersionId() default 0;
}
Annotation Usage:
#CacheClear(pathToVersionId = "[0]")
public int importByVersionId(Long versionTo){
......
}
Aspect Class:
#Component
#Aspect
public class YourAspect {
#Before ("#annotation(cacheClear)")
public void preAuthorize(JoinPoint joinPoint, CacheClear cacheClear) {
Object[] args = joinPoint.getArgs();
ExpressionParser elParser = new SpelExpressionParser();
Expression expression = elParser.parseExpression(cacheClear.pathToVersionId());
Long versionId = (Long) expression.getValue(args);
// Do whatever you want to do with versionId
}
}
Hope this helps someone who wants to do something similar.
Related
I have a custom annotation which is declared as below and have some implementation for this.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface SampleTestCase {
public int caseID() default -1;
public int suiteId() default -1;
}
Now, I am trying to use this annotation and trying to send runtime parameters to it.
ConfigHelper config = new ConfigHelper();
int caseId = config.getTestCaseID();
#SampleTestCase(caseID=caseId,suiteId="Test")
public void testCaseOne(){
Assert.assertTrue(true);
}
Getting an error as "The value for annotation attribute TivoTestCase.caseID must be a constant expression".
Is there any way to pass dynamic parameters to an annotation other than this way??
Consider a UrlValidator method annotation that tests if a given url is valid before calling a method.
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface UrlValdator{
String value();
}
This is working fine when routes are static and known ahead of time. For example:
#UrlValidator("http://some.known.url")
public void doSomething();
But this is not very flexible. For example, what if the route was implicit in the doSomething() method signature? Could I somehow access it form the Spring Expression Language, or some other means? For example, this doesn't work but is what I'm shooting for
#UrlValidator("#p1")
public void doSomething(String url)
or
#UrlValidator("#p1.url")
public void doSomething(Request request)
Is it possible to make annotations dynamic this way?
Related
This is the closest I've found, but the thread is old and the accepted answer is quire cumbersome/hard to follow. Is there a minimal working example/updated way to do this?
I'm not entirely sure if that's what you had in mind, but i can suggest using Spring AOP as it can give you a lot of flexibility.
Since you've mentioned in one of the comments that you're already using Spring AOP, I'm going to assume that you've added spring-boot-starter-aop as a dependency and that you've enabled support for handling components marked with #Aspect by annotating one of your config classes with #EnableAspectJAutoProxy
For example, having defined annotations as such:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface EnsureUrlValid {
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface UrlToVerify {
}
I can use them in a sample spring component as follows:
#Component
public class SampleComponent {
private static final Logger logger = LogManager.getLogger(SampleComponent.class);
#EnsureUrlValid
public void fetchData(String url) {
logger.info("Fetching data from " + url);
}
#EnsureUrlValid
public long fetchData(Long id, #UrlToVerify String url) {
logger.info("Fetching data for user#" + id + " from " + url);
// just to show that a method annotated like this can return values too
return 10L;
}
#EnsureUrlValid
public void fetchDataFailedAttempt() {
logger.info("This should not be logged");
}
}
And here's a sample "processor" of the EnsureUrlValid annotation. It looks for the annotated methods, tries to extract the passed-in url and depending on whether the url is valid or not, it proceeds with invoking the method or throws an exception. It's simple but it shows that you have complete control over the methods that you've annotated.
#Aspect
#Component
public class UrlValidator {
#Around(value = "#annotation(EnsureUrlValid)")
public Object checkUrl(ProceedingJoinPoint joinPoint) throws Throwable {
final Optional<String> urlOpt = extractUrl(joinPoint);
if (urlOpt.isPresent()) {
final String url = urlOpt.get();
if (isUrlValid(url)) {
return joinPoint.proceed();
}
}
throw new RuntimeException("The passed-in url either could not be resolved or is not valid");
}
private Optional<String> extractUrl(JoinPoint joinPoint) {
Object[] methodArgs = joinPoint.getArgs();
Object rawUrl = null;
if (methodArgs.length == 1) {
rawUrl = methodArgs[0];
}
else if (methodArgs.length > 1) {
// check which parameter has been marked for validation
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
Parameter[] parameters = method.getParameters();
boolean foundMarked = false;
int i = 0;
while (i < parameters.length && !foundMarked) {
final Parameter param = parameters[i];
if (param.getAnnotation(UrlToVerify.class) != null) {
rawUrl = methodArgs[i];
foundMarked = true;
}
i++;
}
}
if (rawUrl instanceof String) { // if rawUrl is null, instanceof returns false
return Optional.of((String) rawUrl);
}
// there could be some kind of logic for handling other types
return Optional.empty();
}
private boolean isUrlValid(String url) {
// the actual validation logic
return true;
}
}
I hope it's somewhat helpful.
Short answer: Yes.
Long answer:
ElementType specifies the target of the annotation, which can be the following: ANNOTATION_TYPE, CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE, and TYPE_PARAMETER. Were are interested in PARAMETER here. Since we want from the compiler the run our code, RetentionPolicy.RUNTIME is fine for the retention type.
Next we have to add #Constraint annotation, which according to the documentation:
Marks an annotation as being a Bean Validation constraint.
This means, Spring will pick up your parameter and validate it in runtime. The last thing we have to do is to implement the validation itself which implies creating a class which implements ConstraintValidator interface.
Putting it all together:
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = UrlValidatorImplementation.class)
public #interface UrlValidator{
String message() default "Invalid url";
}
Implementation of the UrlValidatorImplementation class:
public class UrlValidatorImplementation implements ConstraintValidator<UrlValidator, String> {
#Override
public void initialize(UrlValidator annotation) {
// initialization, probably not needed
}
#Override
public boolean isValid(String url, ConstraintValidatorContext context) {
// implementation of the url validation
}
}
Usage of the annotation:
public void doSomething(#UrlValidator url) { ... }
I am trying to declare custom annotation in following way:
Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
public #interface InnerAnnotation {
}
Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
public #interface OuterAnnotation {
public String default "";
public InnerAnnotation innerAnnotation(); //here I wanted to do "public InnerAnnotation innerAnnotation() default {some default value}"
}
I wanted to use it in a way:
class first{
#OuterAnnotation(value = "new") //wanted to declare something like this without need to define innerAnnotation
public void func(){
}
}
I wanted to assign some default value to inner annotation usage(so that I don't have to provide any mandatory value while using it), but some how I am not able to do that as compiler asks for compile time constant for this.Can any please suggest how to use inner annotation with any default value ?
The syntax for what you what is as follows:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
public #interface OuterAnnotation {
public String default "";
public InnerAnnotation innerAnnotation() default #InnerAnnotation(); //this does the trick;
}
Is there a way by which I can set the id inside annotated method...
Annotation class:
import java.lang.annotation.*;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public
#interface MyAnnotation {
int id();
}
//Set id at runtime
public class A {
#MyAnnotation(id = ? )
public void method1() {
// I want to set the id here for my annotation...
}
}
Yes, but it's a bit unintuitive. You'll have to edit its bytecode using a tool like JavaAssist.
Here is an article describing what you're after.
My query is this. Say, i have a custom annotation as follows:
//rest of the code left out for the sake of brevity
interface #Name{
String myName();
}
Now, in the class where am using this annotation on say, a field or a method, i want to pass a value to "myName" from a property file, something like this:
#Name(myName="${read.my.name}")
public void something(){}
Could anyone please suggest how can i read the value passed to 'myName' in my annotation-processor from the property file? I have read a bit about the use of placeholders and then using #Value but am not sure i can/should use this approach in say, a service class where i just want to have an annotated field or method marked with this annotation? Any guidance would be much appreciated.
Thanks and regards!
Here's my method-level annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Name {
public String myName();
}
Here's a dummy class that declares the annotation:
public class Z {
#Name(myName = "George")
public void something() {
}
}
Here's how to get the value:
final Method method = Z.class.getMethod("something");
if (method.isAnnotationPresent(Name.class)) {
final Annotation annotation = method.getAnnotation(Name.class);
final Name name = (Name) annotation;
System.out.println(name.myName()); // Prints George
}