How to extend Java annotation?
Asked Answered
G

7

71

In my project I use pre-defined annotation @With:

@With(Secure.class)
public class Test { //....

The source code of @With:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface With { 

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

I want to write custom annotation @Secure, which will have the same effect as @With(Secure.class). How to do that?


What if I do like this? Will it work?

@With(Secure.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Secure {

}
Gerontology answered 2/3, 2014 at 11:16 Comment(0)
A
39

As piotrek pointed out, you cannot extend Annotations in the sense of inheritance. Still, you can create Annotations that aggregate others:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SuperAnnotation {
    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SubAnnotation {
    SuperAnnotation superAnnotation();
    String subValue();
}

Usage:

@SubAnnotation(subValue = "...", superAnnotation = @SuperAnnotation(value = "superValue"))
class someClass { ... }
An answered 10/12, 2014 at 16:46 Comment(1)
So instead of writing @With(Secure.class), OP now has to write @Secure(superAnnotation = @With(Secure.class))? How does that help?Tumult
T
29

From Java language specification, Chapter 9.6 Annotation Types:

No extends clause is permitted. (Annotation types implicitly extend annotation.Annotation.)

So, you can not extend an Annotation. you need to use some other mechanism or create a code that recognize and process your own annotation. Spring allows you to group other Spring's annotation in your own custom annotations. but still, no extending.

Tonl answered 2/3, 2014 at 11:20 Comment(3)
Probably 'extend' is not best expression for this. I mean implementation of auto-placement for argument.Gerontology
@bvitaliyg: you can create your own annotation with any default fields but there is no out-of-the-box mechanism in jvm that will recognize it automatically. you need to write that code by yourself or check if existing libraries (like spring) are sufficient for your caseTonl
For anyone looking to do this with Spring, it's worth knowing that the term Spring has for this is "meta-annotation". A "custom annotation" is just any annotation that you defined yourself, it doesn't necessarily mean that you meta-annotated it with a 3rd party annotation.Atalie
R
13

To expand on Muhammad Abdurrahman's answer--

@With(Secure.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Secure {

}

This does not work by default but you can use it in conjunction with Spring's AnnotationUtils.

See this SO answer for an example.

Redintegration answered 29/12, 2017 at 20:57 Comment(0)
S
10
@With(Secure.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Secure {

}

This will work.

Shuntwound answered 22/9, 2017 at 16:0 Comment(5)
This is literally copied directly from the question.Laaspere
@Laaspere Well spotted. Since the questioner asked whether this would work in their question, my answer gets straight to the point and actually answers the question.Shuntwound
Yes, this is the correct answer to the question as asked... but only in the context of Spring, I believe (it only works if your annotation processors "walk" the annotation graph to the meta-annotation).Atalie
See the answer by Eric JiangAtalie
This does not work. I'm trying to create my own annotation for fields on classes annotated with JPA's @Entity. I wanted my annotation to do the same as @Transient plus other stuff.Bulbous
A
1

You can use annotation for annotation like this:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@WithSecurityContext(factory = WithCustomUserSecurityContextFactory.class)
public @interface WithCustomUser {
  String username() default "[email protected]";
  String password() default "demo";
  String[] authorities() default {Authority.USER};
}

And define exact state in its "child"

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@WithCustomUser(username = "[email protected]",
                password = "admin",
                authorities = {Authority.USER, Authority.ADMINISTRATOR})
public @interface WithAdminUser {
}

In this case you have a some kind of "state" and access to the parent annotation fields via reflection/aspect.

Abigailabigale answered 14/3, 2018 at 10:46 Comment(1)
This is exactly what I was looking for. I was wondering if it is necessary to change the ElementType if I want a "child"-annotation. Apparently I don't have to change it if it applies to the same type as the parent one.Perri
M
1

So the provided answer from Eric Jiang is 100% working in my situation and she is: I need JMSListener ,but i want to hide the destination name:

@GetPlayerDataByUUIDListener
    public void getPlayerDataByUUID(Object message) {
        System.out.println("Im Here");
    }

`

@JmsListener(destination = PlayerStatisticsJMSConstants.GET_PLAYER_DATA_BY_UUID)
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface GetPlayerDataByUUIDListener {
}

So this is working perfectly ,and it is the same as:

@JmsListener(destination = "example")
    @GetPlayerDataByUUIDListener
    public void getPlayerDataByUUID(Object message) {
        System.out.println("Im Here");
    }
Marsupial answered 30/3, 2019 at 12:57 Comment(0)
M
0

If you need to set some field you can use @AliasFor. Example.

Instead of using this verbose annotations:

@Test
@Sql(
    config = SqlConfig(
        dataSource = "myDataSource",
        transactionManager = "myTransactionManager",
    ),
    scripts = ["/myScript.sql"],
)
test()

I can create my own annotation that will container those that I want to customize. Note that I had to use the @aliasFor annotation to keep my ability to customize some fields:

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

import org.springframework.core.annotation.AliasFor;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Sql(config = @SqlConfig(
    dataSource = "mnDataSource",
    transactionManager = "myTransactionManager"
))
public @interface SqlMyDB {

  @AliasFor(annotation = Sql.class, attribute = "scripts")
  String[] scripts() default {};

}

Now I can simply do:

@Test
@SqlMyDB(scripts = ["/myScript.sql"])
test()

Note that @AliasFor is a Spring annotation and will only work if:

  • You use it for Spring Framework Annotations
  • Use Spring Annotation utils like with Eric's answer
  • Code the equivalent logic yourself.
Maltz answered 20/6 at 20:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.