18

How to reflectively get list of all controllers (best if not only annotated, but also specified in xml), matching some specfic url in your Spring MVC application?

In case of annotated-only,

@Autowired
private ListableBeanFactory listableBeanFactory;
...
whatever() {
    Map<String,Object> beans = listableBeanFactory.getBeansWithAnnotation(RequestMapping.class);

    // iterate beans and compare RequestMapping.value() annotation parameters
    // to produce list of matching controllers
}

could be used, but what to do in more general case, when controllers may be specified in spring.xml config? And what to do with request-path parameters?

tereško
  • 58,060
  • 25
  • 98
  • 150
Alexander Tumin
  • 1,561
  • 4
  • 22
  • 33
  • How do you plan to use this information? Spring Tool Suite will give you this information inside the IDE. Otherwise, here's a utility that I wrote: https://github.com/kdgregory/pathfinder (I'll be merging some updates later this week) – kdgregory Dec 24 '12 at 21:45
  • I am trying to implement dynamic-menu system for my web application. I need not to statically analyze the source codes (my Idea does that well already), i need to analyze it run-time, reflectively. – Alexander Tumin Dec 24 '12 at 21:50
  • How do you plan on finding the relevant controllers in the XML config? Maybe you could access `org.springframework.web.bind.annotation.support.HandlerMethodResolver.getHandlerMethods()` at runtime and process the returned methods. – Marcel Stör Dec 25 '12 at 13:16
  • Does *Ralph*'s answer not give you what you're looking for? My thought is to scan your beans for `DefaultAnnotationHandlerMapping` and then pull its mapping map, but as I haven't tried this it's not an answer. The alternative is to examine the registered beans for annotations, but there are some twists to this. – kdgregory Dec 29 '12 at 23:28

4 Answers4

33

Since Spring 3.1, there is the class RequestMappingHandlerMapping, it delivers information about the mapping (RequestMappingInfo) of @Controller classes.

@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;

@PostConstruct
public void init() {
    Map<RequestMappingInfo, HandlerMethod> handlerMethods =
                              this.requestMappingHandlerMapping.getHandlerMethods();

    for(Entry<RequestMappingInfo, HandlerMethod> item : handlerMethods.entrySet()) {
        RequestMappingInfo mapping = item.getKey();
        HandlerMethod method = item.getValue();

        for (String urlPattern : mapping.getPatternsCondition().getPatterns()) {
            System.out.println(
                 method.getBeanType().getName() + "#" + method.getMethod().getName() +
                 " <-- " + urlPattern);

            if (urlPattern.equals("some specific url")) {
               //add to list of matching METHODS
            }
        }
    }       
}

It is important that this bean is defined in the spring context where the controllers are defined.

Ralph
  • 118,862
  • 56
  • 287
  • 383
  • 1
    I am facing no bean of type RequestMappingHandlerMapping. Who will ceate this bean before autowiring..can you help me here ? – Nagappa L M Oct 20 '14 at 02:28
  • Since Spring Framework 5.3 / Spring Boot 2.6, `getPatternsCondition()` can now be _null_. In that case, simply call `getPathPatternsCondition().getPatternValues()`. Just support both cases, that's simple and independent of wich "path matching" apply, if you have a clue what it is. A bit more info is in [Spring Boot 2.6 Release Notes](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.6-Release-Notes) under _PathPattern Based Path Matching Strategy for Spring MVC_ – Florian H. Jul 20 '22 at 16:50
5

You get a mapped Controller by calling HandlerMapping.getHandler(HTTPServletRequest).getHandler(). An HandlerMapping instance can be aquired by IoC. If you don't have a HTTPServletRequest you can build your Request with a MockHttpServletRequest.

@Autowired
private HandlerMapping mapping;

public Object getController(String uri) {
    MockHttpServletRequest request = new MockHttpServletRequest("GET", uri);
    // configure your request to some mapping
    HandlerExecutionChain chain = mapping.getHandler(request);
    return chain.getHandler();
}

Sorry, I read now that you want all controllers for a URL. This will geht you only one exactly matched controller. It's obviously not want you need.

Markus Malkusch
  • 7,738
  • 2
  • 38
  • 67
3

You can try using ListableBeanFactory interface to retrieve all bean names.

private @Autowired ListableBeanFactory beanFactory;

public void doStuff() {
    for (String beanName : beanFactory.getBeanDefinitionNames()) {
        if (beanName.startsWith("env")) { // or whatever check you want to do
            Object bean = beanFactory.getBean(beanName)
            // .. do something with it
        }
    }
}

See documentation of ListableBeanFactory here. This interface provides several methods like getBeansOfType(), getBeanDefinitionCount() etc.

If this approach does not list @Controller annotated beans visit this page to see how that can be done.

Community
  • 1
  • 1
Jeevan Patil
  • 6,029
  • 3
  • 33
  • 50
0

you need to do a entry for RequestMappingHandlerMapping in your dispatcher servlet

<bean name="requestHandlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="useTrailingSlashMatch" value="true"></property>
    </bean>

the dispatcher servlet will look into this mapping and instantiate a bean RequestMappingHandlerMapping in your application

now in any of your controller/class you can use

@Autowired
private HandlerMapping mapping;

and it should work fine.

Note: you need to take care of adding the bean, if any of your dispatcherservlet (in case of big application) contains this bean it will result into noUniqueBean exception and application wont start.

Amit Kumar Lal
  • 5,537
  • 3
  • 19
  • 37