#How do annotations provide metadata to compilers and runtime, exactly?

1 messages · Page 1 of 1 (latest)

hollow bolt
#

I have finished learning annotations in my textbook, but one thing does not make any sense to me. I know how to create custom annotations, how to specify targets, and how to extract information out of them from classes at runtime using reflection. But how do these annotations actually perform the action of metadata to compilers and runtime? The annotations as a type of interface-like cannot have function bodies, so this is very confusing

crimson oreBOT
#

<@&987246399047479336> please have a look, thanks.

#

Here is an AI assisted attempt to answer your question 🤖. Maybe it helps! In any case, a human is on the way 👍. To continue talking to the AI, you can use </chatgpt:1108714622413963314>.

#
TJ-Bot
I have finished learning annotations in my textbook, but one thing does not make any sense to me. I know how to create custom annotations, how to specify targets, and how to extract information out of them from classes at runtime using reflection. But how

Annotations in Java provide metadata to compilers and runtime by allowing developers to attach additional information to classes, methods, fields, and other program elements. This metadata can be used by the compiler to generate code, perform validation checks, or modify behavior at runtime.

When you create a custom annotation in Java, you define its structure using the @interface keyword. This allows you to specify elements that can hold values such as strings or numbers. These elements can then be accessed using reflection at runtime.

Annotations are processed by the compiler and can be used to trigger specific actions or behaviors. For example, the @Override annotation tells the compiler that a method is intended to override a superclass method, while the @Deprecated annotation marks a method as obsolete and informs users that it should no longer be used.

At runtime, annotations can be retrieved using reflection and used to make decisions about how a program should behave. For example, an annotation could be used to specify transactional boundaries in a database operation or configure aspects of dependency injection.

Overall, annotations provide a powerful mechanism for adding metadata to Java code that can be used by both the compiler and runtime environment for various purposes.

hollow bolt
#

wow, this AI is useless

naive nexus
# hollow bolt wow, this AI is useless

But how do these annotations actually perform the action of metadata to compilers and runtime?
what do you mean by "perform the action of metadata to compilers and runtime"?

#

It's just ChatGPT afaik.

hollow bolt
#

How does it achieve this functionality

#

and how can I create an annotation like this, say for example, I wanna make an annotation @Digit which can only applied to integer data types, but will give compiler error if the number is not within 0 to 9

naive nexus
slender crag
#

The annotation itself doesn't do anything, it's the compiler that checks for its presence

#

If you make a custom annotation it's up to you to write code elsewhere that checks for it

hollow bolt
naive nexus
# hollow bolt You mean, I would need to write a custom compiler? But that doesnt make any sens...

Spring is open source so you don't need to figure how it work, look inside:

`RequestMapping` annotation is handled by `RequestMappingHandlerMapping`, see `getMappingForMethod` method.

`Autowired` annotation is handled by `AutowiredAnnotationBeanPostProcessor`, see `processInjection` method.

Both use reflection to get annotation data and build the handler mapping info in the first case or populate the bean in the second one.

Source

hollow bolt
slender crag
#

You (nor Spring) can create compiler warnings with your annotations

#

(except by annotation processors)

naive nexus
#

*I've only skimmed it though

hollow bolt
#

Hmm, I know reflection can read class data, can it actually modify the class at compile time too?

naive nexus
hollow bolt
#

Gets in the way of a true understanding of language

#

If I have to pretend like this annotations are some black box

naive nexus
#

Yeah, I know.
Python is also sorta magic in that regards, but it's still much easier to look up what is happening

hollow bolt
turbid iron
#

@hollow bolt I just wrote about this

turbid iron
#

lombok breaks java rules when it does so

hollow bolt
naive nexus
# hollow bolt <:peepo_sad_2:874567365906550825>

Python example, in case you don't know what it looks like there:

import sys


def memoize(func):
    memo: dict = {}

    def wrapper(*args, **kwargs):
        key = str(*args) + str(**kwargs)
        if key not in memo:
            memo[key] = func(*args, **kwargs)

        return memo[key]

    return wrapper


@memoize
def fibonacci(n: int) -> int:
    if n < 2:
        return n

    return fibonacci(n - 1) + fibonacci(n - 2)


if __name__ == '__main__':
    sys.setrecursionlimit(1000000)
    print(fibonacci(10))
    print(fibonacci(100))
    print(fibonacci(1000))
turbid iron
#

normal sanctioned uses of annotations cannot do that

#

they are just structured comments

#

they can be read at compile time - and you can do stuff like that

#

and you can read them at runtime

#

and then use reflection to do stuff

hollow bolt
turbid iron
#

but they are just structured comments

turbid iron
hollow bolt
turbid iron
#

python decorators aren't just comments

hollow bolt
#

Which is why it made no sense how Lombok could modify the class like that

turbid iron
#

in python this:

@memoize
def fibonacci(n: int) -> int:
    if n < 2:
        return n

    return fibonacci(n - 1) + fibonacci(n - 2)

is the same as this

def fibonacci(n: int) -> int:
    if n < 2:
        return n

    return fibonacci(n - 1) + fibonacci(n - 2)

fibonacci = memoize(fibonacci)
#

its a shorthand for a "wrapped reassignment"

#

so any "function that takes a function and returns a function" can be used as a python decorator

hollow bolt
turbid iron
#

its not builtin in this example

#
def memoize(func):
    memo: dict = {}

    def wrapper(*args, **kwargs):
        key = str(*args) + str(**kwargs)
        if key not in memo:
            memo[key] = func(*args, **kwargs)

        return memo[key]

    return wrapper
hollow bolt
#

Ooooh

turbid iron
#

since here its just a function @naive nexus wrote

naive nexus
#

no?

hollow bolt
#

But from what I can tell, python relies on functions being first class citizen to do this

#

Coz the function is being passed as an argument to this memoize function

hollow bolt
#

And a new function is returned which is basically the original function with memoization assistance

turbid iron
#

at least I don't think

naive nexus
turbid iron
#

but the global rebind affects it

#

but anyways - thats not how java does @Thing

#

in java its just a comment thats retrievable by code

#

annotation processors can read them at compile time to generate new files or make static checks

#

other tools can read them in the classfile to do whatever with them

hollow bolt
turbid iron
#

and you can read them at runtime with reflection

turbid iron
hollow bolt
hollow bolt
#

Ah

turbid iron
#

a full program is "valid java" if it doesn't violate the rules of the java language

#

one of those rules is "a class defines all its members"

#

so

#
class A {

}

class B {
   void main() {
      new A().f();
   }
}
#

is definitely invalid java

#

because we already saw a definition of the A class

#

and it does not include a definition for f

#
class A extends A2 {

}

class B {
   void main() {
      new A().f();
   }
}
#

is maybe correct, maybe invalid

#

we don't know for sure until we have a definition for A2

hollow bolt
turbid iron
#

hence being a different language

#

the ways it hijacks the compiler is not through actually supported apis

hollow bolt
turbid iron
#

even patch releases

hollow bolt
#

Wow...

turbid iron
#

since its really "the lombok language"

#

coincidentally implemented by hooking into the java compiler

hollow bolt
#

People who use lombok a lot are aware of this crazy risk

turbid iron
#

not really

#

they just assume it will continue to be maintained

hollow bolt
#

An awful lot of trust into something which is essentially the equivalent of arcane magic

#

Coz it does something impossible in Java

turbid iron
#

its perfectly fine to take the risk - like kotlin is also a different language

#

but the fact that the risk/reality is obscured is annoying for everyone

#

lombokc ... would be a lot nicer

hollow bolt
#

lombokc? Is that different from lombok?

turbid iron
#

im saying that it would be nicer if there was a lombokc compiler for lombok

hollow bolt
#

Ohh like javac and kotlinc

naive nexus
# turbid iron nah it doesn't go into the function body

@hollow bolt You can ignore this message.

*Just because I wanted to absolutely make sure (this will be the last Python message I'll send here 🐒 ):

def print_result(func):
    def wrapper(*args, **kwargs):
        print(res := func(*args, **kwargs))
        return res

    return wrapper


@print_result
def f(n) -> int:
    if n < 0:
        return 0

    return n + f(n - 1)


def g(n):
    if n < 0:
        print(0)
        return 0

    print(res := n + g(n - 1))
    return res


if __name__ == '__main__':
    print("-----")
    f(4)
    print("-----")
    g(4)
```prints:

0
0
1
3
6

0
0
1
3
6

So it does indeed need to go into the function body, but it's not necessarily possible to just use the annotation function how I tried to do, but you have to actually re-write the annotation implementation (in this case the `print`s).

If we want to use the `print_result` function in the body, then it starts to look like a real mess...
```py
def g(n):
    if n < 0:
        return print_result(lambda: 0)()

    return print_result(lambda: n + g(n - 1))()
hollow bolt
turbid iron
#

you can in a file like this

#
@Getter
class B extends A {
   String s;
}

generate a new file that looks like this

class A {
    String getString() {
        return ((B) this).s;
    }
}
#

and that is kosher because you changed nothing about how B is written

hollow bolt
turbid iron
#

thats just a cast

hollow bolt
#

A needs to masquerade as B now

turbid iron
#

yeah

hollow bolt
#

Wait I completely missed the part where you put extends A

naive nexus
hollow bolt
#

But wait, that was not specified in the article. So even then, the annotation is able to automatically extend from a base class

#

Weird weird

turbid iron
#

no

#

you need to write the extends yourself

#

so you are extending a class that doesn't exist

#

you compile and an annotation processor generates a new file

hollow bolt
#

but now we have this annotation processor to worry about. Where does it come from

#

And how do we know everyone who uses this annotation will get one

turbid iron
#

you have to seperately put the processor on the --processor-path or --processor-module-path when you run javac

#

here is how you make one

hollow bolt
#

Ahh, I assume IntelliJ sorta does that under the hood

turbid iron
#

depends on the build tool

#

you do need to add it as a dep though

hollow bolt
#

And when you import lombok in maven, the dependency itself has the annotation processor built in, with instructions?

turbid iron
#

yes

hollow bolt
#

That is very interesting

turbid iron
#

and it goes beyond the allowed api of an annotation processor

#

but its also the most popular "annotation processor"

#

and thus defines peoples expectations for what an annotation can do

#

which, unideal

hollow bolt
hollow bolt
ionic cedar
#

part of the reason why I switched to kotlin for personal projects is because it has all the functionality provided by lombok in the language itself

#

at long last my classes don't look like christmas trees

hollow bolt
#

wow another dub for Kotlin