Arguments Against Annotations
Actually I think that the bad feeling in your gut against has more to do with Annotations like this mixing configuration with code.
Personally I feel the same way as you do, I would prefer to leave configuration (such as transaction definitions, path elements, URLs that a controller should be mapped to, etc.) outside of the code base itself and in external Spring XML context files.
I think though that the correct approach here comes down to opinion and which method you prefer - I would predict that half the community would agree with the annotations approach and the other half would agree with the external configuration approach.
Maybe you have a problem with redundant annotations that are all over the code. With meta-annotations redundant annotations can be replaced and your annotations are at least DRY.
From the Spring Blog:
@Service
@Scope("request")
@Transactional(rollbackFor=Exception.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyService {
}
@MyService
public class RewardsService {
…
}
Because Java evolves so slowly people are putting more features that are missing in the language into annotations. This is a good thing Java can be extended in some form and this is a bad thing as most of the annotations are some workaround and add complexity.
I was also initially skeptical about annotations, but seeing them in use, they can be a great thing. They can also be over used.
The main thing to remember about annotations is that they are static. They cannot change at runtime. Any other configuration method (xml, self-description in code, whatever) does not suffer from this. I have seen people here on SO have issues with Spring in terms of having a test environment on injecting test configurations, and having to drop down to XML to get it done.
XML isn't polymorphic, inherited or anything else either, so it is not a step backwards in that sense.
The advantage of annotations is that it can give you more static checking on your configuration and can avoid a lot of verbosity and coordination difficulties in the XML configurations (basically keeping things DRY).
Just like XML was, Annotations can be over used. The main point is to balance the needs and advantages of each. Annotations, to the degree that they give you less verbose and DRYer code, are a tool to be leveraged.
EDIT: Regarding the comment about an annotation replacing an interface or abstract class, I think that can be reasonable at the framework boundary. In a framework intended to be used by hundreds, if not thousands of projects, having an interface or base class can really crimp things (especially a base class, although if you can do it with annotations, there is no reason you couldn't do it with a regular interface.
Consider JUnit4. Before, you had to extends a base class that had a setup and tear down method. For my point, it doesn't really matter if those had been on an interface or in a base class. Now I have a completely separate project with its own inheritance hierarchy, and they all have to honor this method. First of all, they can't have their own conflicting method names (not a big deal in a testing framework, but you get my point). Second of all you have have the chain of calling super all the way down, because all methods must be coupled.
Now with JUnit4, you can have different @Before methods in different classes in the hierarchy and they can be independent of each other. There is no equally DRY way to accomplish this without annotations.
From the point of view of the developers of JUnit, it is a disaster. Much better to have a defined type that you can call setUp and teardown on. But a framework doesn't exist for the convenience of the framework developer, it exists for the convenience of the framework user.
All of this applies if your code doesn't need to care about the type (that is, in your example, nothing would every really use a Controller type anyway). Then you could even say that implementing the framework's interface is more leaky than putting on an annotation.
If, however, you are going to be writing code to read that annotation in your own project, run far away.
I personally feel that annotations have taken over too much and have blown up from their original and super useful purpose (e.g., minor things like indicating overridden method) into this crazy metaprogramming tool. I don't feel the JAva mechanism is robust enough to handle these clusters of annotations preceding each method. For instance, I'm fighting with JUnit annotations these days because they restrict me in ways that I don't like
That being said, in my experience the XML based configuration isn't pretty either. So to quote South Park, you're choosing between a giant douche and a t*rd sandwich.
I think that the main decision you have to make is whether you are more comfortable with having a delocalization of the spring configuration (i.e., maintain two files instead of one), and whether you use tools or IDE plugins that benefit from the annotations. Another important question is whether the developers who will use or maintain your code truly understand annotations.
Like many things, there are pros and cons. In my opinion, some annotations are fine, though sometimes it feels like there is a tendency to overuse annotations when a plain old function calling approach might be superior, and taken as a whole, this can unintentionally increase cognitive load because they increase the number of ways to "do stuff."
Let me explain. For example, I'm glad you mentioned the @Transactional annotation. Most Spring developers probably are going to know about and use @Transactional. But how many of those developers know how @Transactional actually works? And would they know off the top of their head how to create and manage a transaction without using the @Transactional annotation? Using @Transactional makes it easier for me to use transactions in a majority of cases, but in particular cases when I need more fine-grained control over a transaction, it hides those details from me. So in a way it is a double edged sword.
Another example is @Profile in Spring config classes. In the general case, it makes it easier to specify which profiles you want a Spring component loaded in. However, it if you need more powerful logic than just specifying a list of profiles for which you want the component loaded, you would have to get the Environment object yourself and write a function to do this. Again, most Spring developers would probably be familiar with @Profile, but the side effect of that is they become less familiar with the details of how it works, like the Environment.acceptsProfiles(String... profiles) function, for instance.
Finally, when annotations don't work, it can be harder to understand why and you can't just put a breakpoint on the annotation. (For instance, if you forgot the @EnableTransactionManagement on your config, what would happen?) You have to find the annotation processor and debug that. With a function calling approach, you can of course just put a breakpoint in the function.