Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide access to all configured interceptors in AbstractHandlerMapping #28985

Closed
lizongbo opened this issue Aug 21, 2022 · 6 comments
Closed
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Milestone

Comments

@lizongbo
Copy link

lizongbo commented Aug 21, 2022

Versions
Spring Version 5.3.22
Overview

I wanted to get all the HandlerInterceptor that had been registered in Spring MVC after the application was successfully launched, but I found that this method was protected, so it could not be obtained directly, and I had to use the reflection method to call, so it was recommended to set it to a public method.
My code was:

            
RequestMappingHandlerMapping requestMappingHandlerMapping = configurableApplicationContext.getBean(RequestMappingHandlerMapping.class);

Field adaptedInterceptorsField = ReflectionUtils.findField(RequestMappingHandlerMapping.class, "adaptedInterceptors");

adaptedInterceptorsField.setAccessible(true);

List<HandlerInterceptor> hiList = (List<HandlerInterceptor>) adaptedInterceptorsField.get(requestMappingHandlerMapping);

I also suggest that Springboot's actuator provide endpoints to get that information。

@rstoyanchev
Copy link
Contributor

Please, elaborate on your reason for this request. Generally, the interceptors are configured and invoked within the HandlerMapping, and in that sense they are for internal use.

@rstoyanchev rstoyanchev added status: waiting-for-feedback We need additional information before we can continue in: web Issues in web modules (web, webmvc, webflux, websocket) labels Sep 16, 2022
@lizongbo
Copy link
Author

lizongbo commented Sep 16, 2022

I'm implementing our development framework based on Springboot, and since we have hundreds of business function developers, and we maintain hundreds of systems, and their Java development capabilities are high and low, I have to collect information while the program is running and automatically analyze the differences in changes in production materials. So I used a unified main class in the framework, called the start method of applicationContext in the main method, and then triggered the information collection operation in the start event, and the collected information will be written to a json file.
Then, through the devops tool, the json file in the docker container is collected and the information is recorded into the database. Before each deployment of materials to the production environment, through the devops tool to compare the current branch to the material and the materials that have been published production branches, you can quickly find the differences in changes such as filters and interceptors, and timely find possible error changes. This enables a quick review of changes to hundreds of systems.

Here's some of my sample code:

  1. main class:

public static void main(String[] args) {
ConfigurableApplicationContext configurableApplicationContext =  SpringApplication.run(XXXXServerApplication.class, args);
 configurableApplicationContext.start();
}

  1. start event call back

  @Order(Ordered.HIGHEST_PRECEDENCE + 6)
    @EventListener
    public void onContextStartedEvent(ContextStartedEvent event) {
 dumpAppConfigInfo(event.getApplicationContext());
}

  1. dump logic:

public static void dumpAppConfigInfo(ApplicationContext configurableApplicationContext) {
   
            Map<String, Object> map = new TreeMap<>();
//get System Properties info
 Map<String, String> mapTmp = new TreeMap<>();
            for (String key : System.getProperties().stringPropertyNames()) {
                mapTmp.put(key, System.getProperty(key));
            }
            map.put("systemProperties", mapTmp);
// get env info
            map.put("systemEnv", new TreeMap<>(System.getenv()));
//get font info
            Locale[] localeArr = { Locale.CHINESE, Locale.SIMPLIFIED_CHINESE, Locale.ENGLISH };
            for (Locale locale : localeArr) {
                String[] fontNames = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(locale);
                List<String> fontNameList = new ArrayList<>(Arrays.asList(fontNames));
                Collections.sort(fontNameList);
                map.put("fontNames_" + locale, fontNameList);
            }
            Font[] allFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
            List<String> allFontNameList = new ArrayList<>();
            for (Font font : allFonts) {
                allFontNameList.add(font.getName());
            }
            Collections.sort(allFontNameList);
            map.put("allFontNameList", allFontNameList);
  //get filter info 
   List<Map<String, Object>> webFilterList = getWebFilterList(configurableApplicationContext);
            map.put("webFilterList", webFilterList);
// get Interceptor info 
            List<Map<String, Object>> mappedInterceptorList = getMappedInterceptorList(configurableApplicationContext);
            map.put("mappedInterceptorList", mappedInterceptorList);
try {
                FileUtils.writeStringToFile(new File(logDirPath, "appConfigInfo.json"), JSONUtil.toFullJsonStr(map, true), StandardCharsets.UTF_8);
            } catch (IOException e) {
                LOG.info("SpringUtil.dumpAppConfigInfo|buterr", e);
            }
}

    private static List<Map<String, Object>> getWebFilterList(ApplicationContext configurableApplicationContext) {
        List<Map<String, Object>> webFilterList = new ArrayList<>();
        if (configurableApplicationContext instanceof WebApplicationContext) {
            WebApplicationContext wac = (WebApplicationContext) configurableApplicationContext;
            ServletContext sc = wac.getServletContext();
            if (sc != null) {
                Map<String, ? extends FilterRegistration> allFiltersMap = sc.getFilterRegistrations();
                if (allFiltersMap != null) {
                    for (Map.Entry<String, ? extends FilterRegistration> e : allFiltersMap.entrySet()) {
                        Map<String, Object> m = new TreeMap<>();
                        FilterRegistration fr = e.getValue();
                        m.put("name", fr.getName());
                        m.put("className", fr.getClassName());
                        m.put("urlPatternMappings", fr.getUrlPatternMappings());
                        m.put("servletNameMappings", fr.getServletNameMappings());
                        webFilterList.add(m);
                    }
                }
            }
        }
        return webFilterList;
    }

    private static List<Map<String, Object>> getMappedInterceptorList(ApplicationContext configurableApplicationContext) {
        List<Map<String, Object>> mappedInterceptorList = new ArrayList<>();
        try {
            Map<String, RequestMappingHandlerMapping> beanMap = configurableApplicationContext.getBeansOfType(RequestMappingHandlerMapping.class);
            for (Map.Entry<String, RequestMappingHandlerMapping> e : beanMap.entrySet()) {
                String beanName = e.getKey();
                RequestMappingHandlerMapping requestMappingHandlerMapping = e.getValue();
                Field adaptedInterceptorsField = ReflectionUtils.findField(RequestMappingHandlerMapping.class, "adaptedInterceptors");
                if (adaptedInterceptorsField != null) {
                    adaptedInterceptorsField.setAccessible(true);
                    List<HandlerInterceptor> hiList = null;
                    try {
                        hiList = (List<HandlerInterceptor>) adaptedInterceptorsField.get(requestMappingHandlerMapping);
                    } catch (IllegalArgumentException | IllegalAccessException ex1) {
                        LOG.error(“get adaptedInterceptors fail”, ex1);
                    }
                    if (hiList != null) {
                        for (HandlerInterceptor hi : hiList) {
                            if (hi instanceof MappedInterceptor) {
                                MappedInterceptor mi = (MappedInterceptor) hi;
                                Map<String, Object> m = new TreeMap<>();
                                m.put("requestMappingHandlerMappingBeanName", beanName);
                                m.put("pathPatterns", mi.getPathPatterns());
                                m.put("className", getRealClassForSpringBean(mi.getInterceptor()).getName());
                                mappedInterceptorList.add(m);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            LOG.warn("getMappedInterceptorList|buterr|", e);
        }
        return mappedInterceptorList;
    }

    public static Class<?> getRealClassForSpringBean(Object bean) {
        Objects.requireNonNull(bean);
        Class<?> userClass = ClassUtils.getUserClass(bean.getClass());
        Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
        if (!AopUtils.isJdkDynamicProxy(bean)) {
            return userClass;
        }
        return targetClass;
    }

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Sep 16, 2022
@lizongbo
Copy link
Author

Of course, we not only collected the information involved in the above sample code, but also collected other information.
For example, the reason why the above collection collects font information is because our operators removed a font file when updating the docker container basic image, but our application used the font to generate pictures, and the result was deployed to production after user complaints that the O&M made font changes, so we collected information and then used to find some important configuration changes, such as more filters, or less interceptors.


My english translated by bing

@rstoyanchev
Copy link
Contributor

rstoyanchev commented Jan 27, 2023

Thanks for the feedback. We do expose all interceptors on HandlerExecutionChain during an actual request. We can consider doing the same on AbstractHandlerMapping. Please confirm if this is still something you need.

@rstoyanchev rstoyanchev added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Jan 27, 2023
@rstoyanchev rstoyanchev self-assigned this Jan 27, 2023
@rstoyanchev rstoyanchev changed the title please make public method for AbstractHandlerMapping.getMappedInterceptors Provide access to all configured interceptors in AbstractHandlerMapping Jan 27, 2023
@lizongbo
Copy link
Author

Thank you! yes I need the public method for collected the interceptor list。

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jan 28, 2023
@rstoyanchev rstoyanchev added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged or decided on status: feedback-provided Feedback has been provided labels Jan 30, 2023
@rstoyanchev rstoyanchev added this to the 6.0.5 milestone Jan 30, 2023
@rstoyanchev
Copy link
Contributor

We can't have a public HandlerInterceptor[] getInterceptors() because the corresponding setter is more general and accepts Object... which can be one of several types. The mismatch creates issues for reflective calls that initialize the class.

Instead I promoted the existing getAdapterInterceptors from protected to a public method, with the name reflecting correctly that it returns the adapted set of interceptors passed into the setter.

mdeinum pushed a commit to mdeinum/spring-framework that referenced this issue Jun 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

3 participants