Spring Version: 6.2.11 Spring Boot Version: 3.4.10 Minimal Reproduction: reproduction.zip

Use case

We generate OpenAPI interfaces and model classes from an OpenAPI spec. Previously our projects used swagger-codegen, but we are moving to openapi-generator, which happens to add an @Validated annotation on the generated interfaces, when adding any validation annotations to the generated code. These generated interfaces are implemented by our hand-made code - classes implementing @RestController or @Controller, depending on who implemented it. Our projects all use spring.aop.proxy-target-class=false.

The problem

The effect we see with the addition of this @Validated is that the new controllers stopped being registered as endpoints or having requests routed to them by Spring, even though they are annotated with these controller annotations.

Things tried

If we comment out the @Validated in the generated interfaces, the controllers work normally again.

We set a flag to the generator to add @Controller annotations to the interfaces, which brought the traffic to our controller class again, but it is being handled using @Controller mappings even when our implementation class is annotated with @RestController (for instance, if I have return type of String and the implementation returns "Hello world", we now get an error that no static resource exists for "Hello World").

If I switch spring.aop.proxy-target-class=true, the controllers work normally again.

If I annotate individual controller classes with @Scope(proxyMode = org.springframework.context.annotation.ScopedProxyMode.TARGET_CLASS) (which I think just switches that one bean's advice to use CGLIB proxies), it works normally.

By "normally", I mean that, where the fixes don't cause author issues, the implementation methods seem to be getting the same inputs from requests and producing the same responses as they did before.

None of these options are particularly attractive to us, because it seems like @RestController on the implementation class should be honored, regardless of whether it is being advised or not.

Why is this happening?

I traced through what was happening with a debugger, and here is what I think is happening:

  1. Component scan picks up the implementation class and adds it to the context - as part of this, the @Validation is found and it's determined the bean needs to be wrapped in a proxy. Since I've indicated in properties to use JDK proxies where possible, it creates a JDK proxy using my interface, then adds the proxy to the Spring context.
  2. The Web mapping code looks through beans for ones it should map to endpoints. On each one, it finds the type of bean it is. It then calls a method to determine if the type is a handler - if it is, it will proceed to process the types methods. In this case, the type is a proxy type, which it seems happy enough to accept.

Image 4. The check for handler seems to be just looking for controller annotations using a "Hierarchy" search, which recursively looks for at direct annotations, interface annotations, and parent annotations. None of these directions account for proxy target annotations. The search stops as soon as matching annotations are found. In this case, that means as soon as it finds @Controller on the interface (if it has one), or not at all (if it doesn't have one).

Image

Some ideas

Having spent some time on this, I have a few ideas of possible ways this could be addressed - of course I don't know if any of these are feasible:

  1. In step 2, AbstractBeanFactory returns the type of the bean in context as being a proxy class. The code for this seems pretty simple:

Image

I'm not sure how something would find it useful to see a random Proxy class as the type, besides just knowing it is a proxy, so I think maybe it would make sense for the proxy target type to be returned here in the case of a proxy. If not, at least processCandidateBean could find the target type if it is a proxy, and use that.

  1. If the bean type in processCandidateBean is left as proxy, maybe the code which searches for controller annotations could use a different search method which is able to find annotations on target types and give them priority over interfaces they extend.

Objective/Summary

It seems like documentation I have found mostly suggests that @RestController on an implementation class should always override the one in the interface, and that the implementation class in this scenario would be the ideal location to have this annotation.

I think it is very confusing that this isn't the case with spring.aop.proxy-target-class=false.

Right now it seems like it is something that could be fixed without too much trouble, but of course do not know for sure and wanted to at least raise the issue.

Thanks in advance for any insights here! If it is a valid concern/issue and someone has some ideas on direction for a fix but no priority for it, I would also be interested in trying my hand at a pull request.

Comment From: Kronen

I think it is very confusing that this isn't the case with spring.aop.proxy-target-class=true.

Maybe you meant false?

Comment From: snydergd

@Kronen yes, you are right. I'll edit it. Thanks