springmvc之HandlerMapping
一 概述
HandlerMapping的作用是根据request找到对应的处理器Handler
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
二. 源码分析
HandlerMapping家族成员如下所示:
通常我们使用的是RequestMappingHandlerMapping
接下来分析下整个家族的实现方式,另外一条继承线路AbstractUrlHandlerMapping
不常使用,不做分析
2.1 AbstractHandlerMapping
2.1.1 创建
该类继承了WebApplicationObjectSupport
,因此在初始化时会自动调用initApplicationContext()
@Override
protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.adaptedInterceptors);
initInterceptors();
}
-
extendInterceptors()
是模板方法,用于给子类提供一个添加interceptors的入口 -
detectMappedInterceptors()
用于将springmvc容器和父容器中所有MappedInterceptor类型的Bean添加到adaptedInterceptors属性protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) { mappedInterceptors.addAll( BeanFactoryUtils.beansOfTypeIncludingAncestors( obtainApplicationContext(), MappedInterceptor.class, true, false).values()); }
很简单的一段代码,利用spring找到所有MappedInterceptor类型的类,加入到
adaptedInterceptors
中 -
initInterceptors()
将第一步中找到的Interceptor添加到adaptedInterceptors中
protected void initInterceptors() { if (!this.interceptors.isEmpty()) { for (int i = 0; i < this.interceptors.size(); i++) { Object interceptor = this.interceptors.get(i); if (interceptor == null) { throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null"); } this.adaptedInterceptors.add(adaptInterceptor(interceptor)); } } } protected HandlerInterceptor adaptInterceptor(Object interceptor) { if (interceptor instanceof HandlerInterceptor) { return (HandlerInterceptor) interceptor; } else if (interceptor instanceof WebRequestInterceptor) { return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor); } else { throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName()); } }
小技巧:
从这里可以看到,拦截器除了可以实现
HandlerInterceptor
之外,还可以实现WebRequestInterceptor
接口
2.1.2 使用
由前面springmvc执行请求的流程可以看到,DispatchServlet是调用getHandler()方法来根据request找到对应的Handler的
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (hasCorsConfigurationSource(handler)) {
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
-
getHandlerInternal()
是一个模板方法,具体的寻找过程由子类去完成 -
如果没有找到,使用默认的Handler,这里为null
-
如果handler是一个String类型的,从容器中根据名称找到对应的类
-
然后将Handler转为
HandlerExecutionChain
,并且将拦截器也都加进去
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
HandlerExecutionChain
中封装了Handler和interceptors,都用来对请求进行处理,这里在对拦截器处理的时候,如果是MappedInterceptor
类型的,需要对路径进行匹配
由此可见,
MappedInterceptor
也就是对拦截器又进行了一层封装,可以对拦截器作用的路径进行定制,而不是所有的请求
-
最后,是对跨域资源的处理
首先检测handler是否有跨域资源,如果handler是
CorsConfigurationSource
类型的或者注册信息有CorsConfiguration
,即有@CrossOrigin
@Override protected boolean hasCorsConfigurationSource(Object handler) { return super.hasCorsConfigurationSource(handler) || (handler instanceof HandlerMethod && this.mappingRegistry.getCorsConfiguration((HandlerMethod) handler) != null) || handler.equals(PREFLIGHT_AMBIGUOUS_MATCH); } protected boolean hasCorsConfigurationSource(Object handler) { if (handler instanceof HandlerExecutionChain) { handler = ((HandlerExecutionChain) handler).getHandler(); } return (handler instanceof CorsConfigurationSource || this.corsConfigurationSource != null); }
如果是跨域资源,将
CorsInterceptor
加到拦截器的第一个位置chain.addInterceptor(0, new CorsInterceptor(config));
小技巧:
从这里的分析可以看出,由于我们处理静态资源所使用的
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**").addResourceLocations("classpath:/static/"); }
实际上是注入了
ResourceHttpRequestHandler
对象,这个类是CorsConfigurationSource
的子类,因此,放在/static/文件夹下的资源可以跨域
2.2 AbstractHandlerMethodMapping
2.2.1 创建
该类是将Method作为Handler来使用的,这也是我们现在用得最多的一种Handler,比如经常使用的@RequestMapping所注释的方法就是这种Handler,它专门有一个类型——HandlerMethod,也就是Method类型的Handler。
实现了InitializingBean
接口,所以spring容器会自动调用其afterPropertiesSet方法
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
protected String[] getCandidateBeanNames() {
return (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
obtainApplicationContext().getBeanNamesForType(Object.class));
}
首先找到容器中所有的类(根据detectHandlerMethodsInAncestorContexts
决定是否查找父容器,默认为false)
然后根据一定的规则对找到的类进行筛选,将筛选出来的类保存到相应的Map中
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
beanType = obtainApplicationContext().getType(beanName);
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
这里isHandler()
是一个模板方法,由子类去实现如何判断类是一个符合条件的Handler
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
//返回用户自定义的类,对于代理类也返回原始对象
Class<?> userType = ClassUtils.getUserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
detectHandlerMethods()
这个方法主要完成了以下几件事:
-
如果handler是String类型的,那么找到类名对应的Class对象,否则直接返回Class对象
-
返回用户自定义的类,对于代理类也返回原始对象
-
对该类中所有的方法进行遍历,根据
getMappingForMethod()
方法进行筛选,返回{方法:匹配条件类}的map这里的泛型T表示 匹配Handler的条件专门使用的一种类,在
RequestMappingHandlerMapping
中使用的是RequestMappingInfo
-
然后对符合条件的Method进行遍历,进行注册,
registerHandlerMethod
也是一个模板方法,由子类实现
2.2.2 使用
在AbstractHandlerMapping
中的模板方法getHandlerInternal
由子类实现
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
这个方法主要做了以下几件事情:
-
根据request获取lookupPath,可以简单地理解为url
-
将路径属性设置到request
-
根据lookupPath和request找到对应处理的HandlerMethod
-
如果找到了,使用
createWithResolvedBean()
创建新的HandlerMethod并返回public HandlerMethod createWithResolvedBean() { Object handler = this.bean; if (this.bean instanceof String) { Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory"); String beanName = (String) this.bean; handler = this.beanFactory.getBean(beanName); } return new HandlerMethod(this, handler); }
判断handlerMethod里的handler是不是String类型,如果是则改为将其作为beanName从容器中所取到的bean,不过HandlerMethod里的属性都是final类型的,不可以修改,所以在createWithResolvedBean方法中又用原来的属性和修改后的handler新建了一个HandlerMethod。
2.3 RequestMappingHandlerMapping
这个类就是我们最常用的HandlerMapping,根据上面的分析,该类主要实现了父类的几个模板方法,来完成基于request查找Handler的功能
2.3.1 isHandler()
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
判断是否是一个Handler,即有@Controller
或者@RequestMapping
注解
2.3.2 getMappingForMethod()
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;
}
根据method和所在类返回对应的RequestMappingInfo信息
RequestMappingInfo
封装多种映射信息,主要有以下几个属性:
分别对应@RequestMapping不同的属性
-
name()
: 映射的名字,可以不指定 -
value()
,path()
都是代表请求路径url,路径信息会被封装到PatternsRequestCondition
中 -
method()
代表支持的请求方式,可以多种,例如RequestMethod.GET
,该信息被封装到RequestMethodsRequestCondition
-
params()
代表支持的参数值,例如params = "test!=mhn"
,表示参数名称为test的属性,不接受值为mhn请求如果test传了mhn,就会抛出
UnsatisfiedServletRequestParameterException
,大小写敏感 -
headers()
代表支撑的请求头,大小写不敏感params 和 headers 相同点均有以下三种表达式格式:
- !param1: 表示允许不含有 param1 请求参数/请求头参数(以下简称参数)
- param2!=value2:表示允许不包含 param2 或者 虽然包含 param2 参数但是值不等于 value2;不允许包含param2参数且值等于value2
- param3=value3:表示需要包含 param3 参数且值等于 value3
表达式的逻辑解析见
AbstractNameValueExpression
-
consumes()
代表设置允许映射的ContentType,例如MediaType.TEXT_PLAIN_VALUE
,也可以使用表达式 -
produces()
其功能有两个功能1:当请求头中Accept的value与produces()配置的属性匹配上,则进行映射,否则返回客户端HTTP 406(Not Acceptable)响应,或415 unsupported mediaType
功能2:默认会把produces中配置的内容写到响应头的Content-Type中去
-
consumers和produces都有正反 2 种表达式
下面是一个完整的带参数的@RequestMapping
@RequestMapping(name = "Mahaonan", value = "/test3", method = RequestMethod.GET, params = "test!=mhn",
headers = "User-Agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36",
consumes = "text/plain",
produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseResult<String> test3(String test,) {
return ResponseResult.ok("success");
}
这个名为Mahaonan
的方法,映射路径为/test3
,请求类型的为GET
,test参数的值不能为mhn,请求头中只接受User-Agent为指定浏览器,并且请求Content-Type为text/plain
,accept中必须包含application/json
,并且会产生该类型的数据
2.3.3 registerHandlerMethod()
@Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
super.registerHandlerMethod(handler, method, mapping);
updateConsumesCondition(mapping, method);
}
首先调用了父类AbstractHandlerMethodMapping
的注册方法
public void register(T mapping, Object handler, Method method) {
// Assert that the handler method is not a suspending one.
if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
}
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
validateMethodMapping(handlerMethod, mapping);
this.mappingLookup.put(mapping, handlerMethod);
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
this.urlLookup.add(url, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
整个方法其实就是给不同的map放入对应的属性,下面看看注册信息中封装的几种map及其作用
class MappingRegistry {
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
}
-
mappingLookup
: 根据映射信息查找对应Handler的map,key->RequestMappingInfo
,即映射信息;value->HandlerMethod
; -
urlLookup
:根据请求路径找到对应的映射信息的map,key->请求路径;value-> 映射信息由于@RequestMapping的value可以为多个值,即多个路径映射同一个方法,因此这里是一个
MultiValueMap
-
nameLookup
:根据name找到对应的handler,一个name可能对应多个Handler -
corsLookup
:根据HandlerMethod找到对应的跨域配置 -
registry
:总的map,根据映射信息找到对应映射注册类
接下来,调用了updateConsumesCondition(mapping, method);
private void updateConsumesCondition(RequestMappingInfo info, Method method) {
ConsumesRequestCondition condition = info.getConsumesCondition();
if (!condition.isEmpty()) {
for (Parameter parameter : method.getParameters()) {
MergedAnnotation<RequestBody> annot = MergedAnnotations.from(parameter).get(RequestBody.class);
if (annot.isPresent()) {
condition.setBodyRequired(annot.getBoolean("required"));
break;
}
}
}
}
对于consumers属性,专门判断方法参数上是否有@RequestBody
注解,如果有,给consumers条件设置 bodyRequired
2.4 SimpleUrlHandlerMapping
这种HandlerMapping是基于另一体系继承链的,父类我们不做分析,这里简单看下其所起的作用
SimpleUrlHandlerMapping主要是为静态资源提供Handler
首先来看看默认情况下,springboot会给容器中注入的HandlerMapping
可以看到SimpleUrlHandlerMapping处于最后一个位置,这个可以从注入的时候看出来
DispatchServlet在调用getHandler方法时,会依次遍历容器中的HandlerMapping,由于静态资源在前面的几个中都找不到对应的Handler,因此就会来到最后的Simple
这个HandlerMapping中存放的映射关系是ResourceHttpRequestHandler
类型的,这个Handler即处理静态资源的handler
@Bean
@Nullable
public HandlerMapping resourceHandlerMapping(
@Qualifier("mvcUrlPathHelper") UrlPathHelper urlPathHelper,
@Qualifier("mvcPathMatcher") PathMatcher pathMatcher,
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
Assert.state(this.applicationContext != null, "No ApplicationContext set");
Assert.state(this.servletContext != null, "No ServletContext set");
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
this.servletContext, contentNegotiationManager, urlPathHelper);
addResourceHandlers(registry);
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
if (handlerMapping == null) {
return null;
}
springboot在处理mvc配置的时候都大同小异,都是利用Registry进行注册,把我们在mvcConfig中配置的内容转为对应的类,对于静态资源而言,起作用的是ResourceHandlerRegistry
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
}
registry.getHandlerMapping();
这个返回的就是SimpleUrlHandlerMapping
@Nullable
protected AbstractHandlerMapping getHandlerMapping() {
if (this.registrations.isEmpty()) {
return null;
}
Map<String, HttpRequestHandler> urlMap = new LinkedHashMap<>();
for (ResourceHandlerRegistration registration : this.registrations) {
for (String pathPattern : registration.getPathPatterns()) {
ResourceHttpRequestHandler handler = registration.getRequestHandler();
if (this.pathHelper != null) {
handler.setUrlPathHelper(this.pathHelper);
}
if (this.contentNegotiationManager != null) {
handler.setContentNegotiationManager(this.contentNegotiationManager);
}
handler.setServletContext(this.servletContext);
handler.setApplicationContext(this.applicationContext);
try {
handler.afterPropertiesSet();
}
catch (Throwable ex) {
throw new BeanInitializationException("Failed to init ResourceHttpRequestHandler", ex);
}
urlMap.put(pathPattern, handler);
}
}
return new SimpleUrlHandlerMapping(urlMap, this.order);
}
将我们填写的静态资源路径,例如class:/static/
作为key,ResourceHttpRequestHandler作为value放入SimpleUrlHandlerMpping中保存。后续在有符合条件的静态资源进来时,就会找到该Handler进行处理
三. 使用技巧
3.1 MappedInterceptor
MappedInterceptor
类型的拦截器是一种增加对请求路径判断的拦截器,可以对拦截器进行定制路径,表示该拦截器只作用的此路径的请求。
但是从MappedInterceptor的源码可以看到,该类是一个final类型的,意味着我们并不能直接继承该类来往容器中注入该类型的类型,使detectMappedInterceptors
能够侦测到该类型的拦截器。
因此,在springboot中,通过另一种方式,实现了这种拦截器的注入。我们通常在mvcConfig中配置的拦截器都是属于这种
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CreateSessionHandlerInterceptor());
registry.addInterceptor(new CheckConfigHandlerInterceptor(sysConfigService)).addPathPatterns("/**");
}
通过这个方法添加的拦截器,如果有addPathPatterns
都会转成MappedInterceptor类型
springboot通过InterceptorRegistry
这个拦截器注册器实现了MappedInterceptor的注入,下面看看springboot是如何注入的
WebMvcConfigurationSupport
自动注入RequestMappingHandlerMapping
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
//....
return mapping;
}
其他的配置不做分析,这里重点看看getInterceptors
protected final Object[] getInterceptors(
FormattingConversionService mvcConversionService,
ResourceUrlProvider mvcResourceUrlProvider) {
if (this.interceptors == null) {
InterceptorRegistry registry = new InterceptorRegistry();
addInterceptors(registry);
registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService));
registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider));
this.interceptors = registry.getInterceptors();
}
return this.interceptors.toArray();
}
这个方法的addInterceptors(registry)
是注入拦截器的关键
//DelegatingWebMvcConfiguration
@Override
protected void addInterceptors(InterceptorRegistry registry) {
this.configurers.addInterceptors(registry);
}
//WebMvcConfigurerComposite
private final List<WebMvcConfigurer> delegates = new ArrayList<>();
@Override
public void addInterceptors(InterceptorRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addInterceptors(registry);
}
}
这里的delegates即包括我们自定义的
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer{}
到这里就会执行我们上述添加拦截器的方法
然后this.interceptors = registry.getInterceptors();
这个方法,会把我们注入的拦截器转成MappedInterceptor
类型
protected Object getInterceptor() {
if (this.includePatterns.isEmpty() && this.excludePatterns.isEmpty()) {
return this.interceptor;
}
String[] include = StringUtils.toStringArray(this.includePatterns);
String[] exclude = StringUtils.toStringArray(this.excludePatterns);
MappedInterceptor mappedInterceptor = new MappedInterceptor(include, exclude, this.interceptor);
if (this.pathMatcher != null) {
mappedInterceptor.setPathMatcher(this.pathMatcher);
}
return mappedInterceptor;
}
可以看到如果有路径参数,new一个MappedInterceptor,重新包装加入了路径参数
由此分析可以知道:
我们实现WebMvcConfigurer
接口重写addInterceptors()
添加的拦截器,springboot在注入RequestMappingHandlerMapping
的时候添加进去,然后如果有路径参数,会重新包装为MappedInterceptor
3.2 WebRequestInterceptor
3.3 自定义HandlerMapping
3.4 CorsConfiguration
跨域请求
: 当前发起请求的域与该请求指向的资源所在的域不一样。
这里的域指的是这样的一个概念:我们认为若协议 + 域名 + 端口号均相同,那么就是同域
这里讨论的仅是资源的访问,例如ajax中请求另一个ip的资源,不包括cookie,session的跨域共享
对CorsConfiguration
起作用的是@CrossOrigin
注解,用在类或者方法上,表示此Handler允许访问的策略
origins
:允许访问的域名,可以配置多个,*
代表允许全部,配置后会给response设置一个header,Access-Control-Allow-Origin: *
allowedHeaders
:允许访问的请求头,*
代表允许全部,配置后会给response设置一个header,Access-Control-Allow-Headers
exposedHeaders
:服务端暴露的请求头,一般用来自定义配置header,Access-Control-Expose-Headers
methods
:允许访问的请求方式,例如Get,PostallowCredentials
:是否允许cookie随请求发送,使用时必须指定具体的域,Access-Control-Allow-Credentials
maxAge
:预请求的结果的有效期,默认30分钟,Access-Control-Max-Age
前端代码:
<script>
$(function () {
console.log(123)
$.ajax({
type: 'post',
url: "http://10.2.47.101:8081/source/test1",
success: function (res) {
console.log(res);
}
})
})
</script>
本域为localhost,如果在ajax中发起跨域请求,如果不指定CrossOrigin,那么响应会被浏览器拦截
这时候就需要在服务器做跨域配置:
- 指定某个方法的跨域配置:
@CrossOrigin(origins = "*", allowedHeaders = "*", methods = RequestMethod.GET, maxAge = 1800)
public String test1() {}
- 全局的跨域配置:
@Configuration
public class MyConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
};
}
}
其他知识:
需要注意,cookie,session 的跨域,这种方式并不行。
对于tomcat而言,session对应的JSESSIONID是由tomcat维护并管理的,在springboot2.0之后貌似不支持修改domain。而且对于cookies和session而言,只能作用于本域,最多是二级域名扩展到一级域名,并不能跨域。
要解决cookie,session的跨域共享,可以使用spring session技术。
四. 总结
4.1 追踪请求
4.2 HandlerMapping杂谈
HandlerMapping在mvc中的作用就是通过request找到对应的Handler,为了完成不同的功能,以及提供多样的匹配方式,整个HandlerMapping又分为两大家族,我们通常使用的基于@RequestMapping的Handler使用到了ReqeustMappingHandlerMapping
,而静态资源使用的Handler是ResourceHttpRequestHandler内置的,对应的HandlerMapping为SimpleUrHandlerMapping
ReqeustMappingHandlerMapping的创建初始化则从父类一步步开始
-
往容器中注入了拦截器,这是在
AbstractHandlerMapping
中完成的 -
找到容器中所有的bean,根据isHandler判断是否是我们需要的Handler,把符合条件的Handler,以及对应的映射信息保存在HandlerMapping中供后续使用。
-
ReqeustMappingHandlerMapping
中则提供了具体的逻辑,-
包括isHandler的判断;
-
如何根据method找到对应的映射信息;(即对@RequestMapping注解的解析)
-
如何注册映射信息;(即把对应的映射信息保存到HandlerMapping中)
-
这样,所有的相关信息就保存好了,那么,在使用时,只需要根据DispatchServlet中的getHandler依次查找即可,只要找到一个匹配的Handler就不会继续找了,返回给下一步使用。