The background,

At present, spring series is the most popular foundation framework dependency in Java programming field besides JDK, and almost all applications use Spring as the basic framework for architecture. Big to a line, tencent (ali), a consortium to individual applications, project launched operations in the rear will leave something online running status detection and data reduction, etc., and the back door is not open to users directly or transparent to users, then we will need to use some methods to protect or will this backdoor interface points environment shielding. There are two common ways:

  • Interface registration online permission control: IP dimension, visitor dimension restriction
  • Sub-environment registration: online environment does not register, in the pre-issued or grayscale environment below the registration

Of course, based on previous experience and risk control and safety considerations, the first method is basically not used, so we will mainly talk about the second method today.

Two, principle analysis

A previous articleMVC Principles of Springboot (Part 2)- Capability SupportWe know, the springmvc for web ability to support the principle of simple review springboot at boot time support for MVC: RequestMappingHandlerMapping create:RequestMappingHandlerMapping initialization:Essentially encapsulating and injecting the Controller interface layer URL and Method into HandlerMapping for the DispatcherServlet to handle requests.

Back to us the main idea of this article, if you want to implement the interface points registration environment, regard RequestMappingHandlerMapping created, and initial into as the breakthrough point. First let’s take a look at the WebMvcAutoConfiguration class springBoot supports MVC capabilities:

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    / /... omit
    /**
	 * Configuration equivalent to {@code @EnableWebMvc}. * /
	@Configuration
	public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {

		private final WebMvcProperties mvcProperties;

		private final ListableBeanFactory beanFactory;

		private final WebMvcRegistrations mvcRegistrations;

		public EnableWebMvcConfiguration( ObjectProvider
       
         mvcPropertiesProvider, ObjectProvider
        
          mvcRegistrationsProvider, ListableBeanFactory beanFactory)
        
        {
			this.mvcProperties = mvcPropertiesProvider.getIfAvailable();
			this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
			this.beanFactory = beanFactory;
		}
        @Bean
		@Primary
		@Override
		public RequestMappingHandlerMapping requestMappingHandlerMapping(a) {
			// Must be @Primary for MvcUriComponentsBuilder to work
			return super.requestMappingHandlerMapping();
		}
        @Override
		protected RequestMappingHandlerMapping createRequestMappingHandlerMapping(a) {
			if (this.mvcRegistrations ! =null
					&& this.mvcRegistrations.getRequestMappingHandlerMapping() ! =null) {
				return this.mvcRegistrations.getRequestMappingHandlerMapping();
			}
			return super.createRequestMappingHandlerMapping(); }}Copy the code

The WebMvcAutoConfiguration layer configuration has a note:

@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
Copy the code

Said if no injection in the spring container WebMvcConfigurationSupport class or subclass implementation when injection configuration. And WebMvcConfigurationSupport class is the main configuration of MVC ability, general to apply @ EnableWebMvc added to the class to initiate a MVC ability, or use a different kind of high-level implementation, directly from such extensions and according to the method needs to be rewritten.

WebMvcAutoConfiguration EnableWebMvcConfiguration class defines a configuration class, as can be seen from the class notes the configuration class is equivalent to @ EnableWebMvc, and is also a new version of the default springboot open web capabilities of an implementation. Then EnableWebMvcConfiguration is MVC able to support the core of the implementation, have a look at its inheritance relationships:As you can see, EnableWebMvcConfiguration inheritance in WebMvcConfigurationSupport, so the front mentioned @ ConditionalOnMissingBean (WebMvcConfigurationSupport. C Lass) purpose is if the user-defined WebMvcConfigurationSupport or its subclasses implementation, then give up springboot default implementation to this one. From the front the first sequence diagram can be seen, in the definition of HandlerMapping will eventually use EnableWebMvcConfiguration# createRequestMappingHandlerMapping

@Override
protected RequestMappingHandlerMapping createRequestMappingHandlerMapping(a) {
	if (this.mvcRegistrations ! =null
			&& this.mvcRegistrations.getRequestMappingHandlerMapping() ! =null) {
		return this.mvcRegistrations.getRequestMappingHandlerMapping();
	}
	return super.createRequestMappingHandlerMapping();
}
Copy the code

The definition of the method is that if the user-defined WebMvcRegistrations and implement getRequestMappingHandlerMapping method, then use the user defined HandlerMapping yourself, or use the default RequestMappin gHandlerMapping,WebMvcRegistrations:

public interface WebMvcRegistrations {
	/**
	 * Return the custom {@link RequestMappingHandlerMapping} that should be used and
	 * processed by the MVC configuration.
	 * @return the custom {@link RequestMappingHandlerMapping} instance
	 */
	default RequestMappingHandlerMapping getRequestMappingHandlerMapping(a) {
		return null;
	}
	/**
	 * Return the custom {@link RequestMappingHandlerAdapter} that should be used and
	 * processed by the MVC configuration.
	 * @return the custom {@link RequestMappingHandlerAdapter} instance
	 */
	default RequestMappingHandlerAdapter getRequestMappingHandlerAdapter(a) {
		return null;
	}
	/**
	 * Return the custom {@link ExceptionHandlerExceptionResolver} that should be used and
	 * processed by the MVC configuration.
	 * @return the custom {@link ExceptionHandlerExceptionResolver} instance
	 */
	default ExceptionHandlerExceptionResolver getExceptionHandlerExceptionResolver(a) {
		return null; }}Copy the code

It provides the RequestMappingHandlerMapping RequestMappingHandlerAdapter/ExceptionHandlerExceptionResolver three basic components of custom extensions, also can see from here the sprin G open and close principle design of good intentions.

From the above description, if we want to customize HandlerMapping or other components, there are two ideas:

  1. Inheritance WebMvcConfigurationSupport or its subclasses implement custom extensions
  2. Custom WebMvcRegistrations for extension

3. Interface environment registration

EnvironmentAware is an interface that encapsulates Environment information into an implementation class after the application is started: EnvironmentAware

public interface EnvironmentAware extends Aware {
	/**
	 * Set the {@code Environment} that this component runs in.
	 */
	void setEnvironment(Environment environment);
}
Copy the code

Next, let’s start implementing the interface environment registry at the code level.

1. Customize HandlerMapping

Can be seen from the inheritance of RequestMappingHandlerMapping it has on the HandlerMapping provides the realization of the more mature, then we will according to lend chicken unripe egg thinking, on the basis of the RequestMappingHandlerMapping extensions , we found from the RequestMappingHandlerMapping initialization sequence diagram for the discovery of interface will eventually be transferred to the RequestMappingHandlerMapping getMappingForMethod method:

protected RequestMappingInfo getMappingForMethod(Method method, Class
        handlerType) {
	RequestMappingInfo info = createRequestMappingInfo(method);
	if(info ! =null) {
		RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
		if(typeInfo ! =null) {
			info = typeInfo.combine(info);
		}
		String prefix = getPathPrefix(handlerType);
		if(prefix ! =null) { info = RequestMappingInfo.paths(prefix).build().combine(info); }}return info;
}
Copy the code

Equestmappinginfo () ¶ getMappingForMethod () ¶

private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) { RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); RequestCondition<? > condition = (elementinstanceofClass ? getCustomTypeCondition((Class<? >) element) : getCustomMethodCondition((Method) element));return(requestMapping ! =null ? createRequestMappingInfo(requestMapping, condition) : null);
}
Copy the code

Theoretically we inherit the RequestMappingHandlerMapping rewrite createRequestMappingInfo can meet the demands, but it is a private method, can’t rewrite, so want to have to rewrite getMappingForMethod and expand CreateRequestMappingInfo. Customized HandlerMapping implementation FilterRequestMappingHandlerMapping:

@Slf4j
public class FilterRequestMappingHandlerMapping extends RequestMappingHandlerMapping implements EnvironmentAware {
    private CurrentEnv currentEnv;
    @Override
    public void afterPropertiesSet(a) {
        log.info("FilterRequestMappingHandlerMapping start initializing...");
        super.afterPropertiesSet();
    }
    @Override
    protected RequestMappingInfo getMappingForMethod(Method method, Class
        handlerType) {
        //RequestMappingInfo requestMappingInfo = super.getMappingForMethod(method, handlerType);
        RequestMappingInfo info = createRequestMappingInfo(method);
        if(info ! =null) {
            RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
            if(typeInfo ! =null) { info = typeInfo.combine(info); }}return info;
    }
    private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
        if(null == requestMapping) {
            return null;
        }
        EnvLimit envLimit = AnnotatedElementUtils.findMergedAnnotation(element, EnvLimit.class);
        if(isEnvLimited(envLimit)) {
            log.info("FilterRequestMappingHandlerMapping.createRequestMappingInfo current env should not registry mapping; env={},url={}"
                    ,currentEnv,requestMapping.value());
            return null; } RequestCondition<? > condition = (elementinstanceofClass ? getCustomTypeCondition((Class<? >) element) : getCustomMethodCondition((Method) element));return createRequestMappingInfo(requestMapping, condition);
    }
    @Override
    public void setEnvironment(Environment environment) {
        String env =  environment.getActiveProfiles()[0];
        log.info("FilterRequestMappingHandlerMapping.setEnvironment current env is {}",env);
        this.currentEnv = CurrentEnv.of(env.toLowerCase());
    }

    /** * Check whether the current environment needs to register mapping **@param envLimit
     * @return* /
    protected boolean isEnvLimited(EnvLimit envLimit) {
        if(null == envLimit) {
            return false;
        }
        for(CurrentEnv env : envLimit.exclude()) {
            if(env.equals(currentEnv)) {
                return true; }}return false; }}Copy the code

First, the class initializes to inject environment information:

    /**
     * 开发
     */
    DEV("dev".1."Development"),
    /** * test */
    TEST("test".2."Test"),
    /** * grayscale */
    GRAY("gray".3."Gray"),
    /** ** ** /
    PROD("prod".4."Production")
Copy the code

The afterPropertiesSet method call chain is applied to createRequestMappingInfo to create the interface mapping information. Here we use the custom annotation EnvLimit to determine whether the current startup environment should register the interface:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnvLimit {
    /** * Default environment limits **@return* /
    CurrentEnv[] value() default {CurrentEnv.DEV,CurrentEnv.TEST,CurrentEnv.GRAY,CurrentEnv.PROD};

    CurrentEnv[] exclude() default {CurrentEnv.PROD};
}
Copy the code

IsEnvLimited checks EnvLimit and currentEnv to determine whether the current environment is registered for mapping:

protected boolean isEnvLimited(EnvLimit envLimit) {
    if(null == envLimit) {
        return false;
    }
    for(CurrentEnv env : envLimit.exclude()) {
        if(env.equals(currentEnv)) {
            return true; }}return false;
}
Copy the code

2. Apply custom HandlerMapping

As we know from section 2, there are two ways to use custom Web components, which we will implement separately without further ado:

  • Inheritance WebMvcConfigurationSupport or its subclasses

We inherited WebMvcConfigurationSupport class directly

@Configuration
public class CustomWebMvcConfigurationSupport extends WebMvcConfigurationSupport {

    @Override
    protected RequestMappingHandlerMapping createRequestMappingHandlerMapping(a) {
        return newFilterRequestMappingHandlerMapping(); }}Copy the code

We set isLimit to always True and start the application:It is found from the printed logs that we have implemented the ability of sub-environment registration interface.

  • Custom WebMvcRegistrations for extension

Custom WebMvcRegistrations implementation class and inject it into Spring:

@Configuration
public class WebMvcConfig  {
    @Bean
    public WebMvcRegistrations webMvcRegistrationsHandlerMapping(a) {
        return new WebMvcRegistrations() {
            @Override
            public RequestMappingHandlerMapping getRequestMappingHandlerMapping(a) {
                return newFilterRequestMappingHandlerMapping(); }}; }}Copy the code

Restarting the application:The ability to register interfaces by environment is also implemented.

Four,

This paper we extend the spring component implementation interface points environment’s ability to register, for the two way personally, I tend to be the second custom WebMvcRegistrations implementation, because spring throughout the WebMvcConfigurationSupport inheritance relationship to help us We added a lot of handy components. If we used the first one, we would have to either re-iterate them ourselves or throw them away for the most part. If we used the second one, we extended the customization capabilities with the default extension features.