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:
- Inheritance WebMvcConfigurationSupport or its subclasses implement custom extensions
- 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.