一. 测试代码

@RestController
@RequestMapping("/book")
public class BookController { @PostMapping("add")
public JsonResponse<Integer> add(@Valid @RequestBody Book book, BindingResult errors){
//1. 对 item 数据进行验证
StringBuffer sb = new StringBuffer();
if (errors.hasErrors()) {
for (ObjectError objectError : errors.getAllErrors()) {
sb.append(objectError.getDefaultMessage());
}
}
if (sb.length() > 0) {
return JsonResponse.error(sb.toString());
}
int id = BookDB.add(book);
return JsonResponse.success(id);
} @GetMapping("getById")
public JsonResponse<Book> getById(@RequestParam("id") Integer id){
Book book = BookDB.getById(id);
return JsonResponse.success(book);
} @GetMapping("getAll")
public JsonResponse<List<Book>> getAll(){
List<Book> list = BookDB.getAll();
return JsonResponse.success(list);
}
}

在 BookController 中, 有三个方法可以访问.

/book/add -> add()

/book/getById -> getById()

/book/getAll -> getAll()

url 和 对应的名字, 是可以不一样的, 比如 我新写个方法:

@GetMapping("byWhat")
public JsonResponse<Book> getBy(){return JsonResponse.success(null);
}

此时的对应关系就是: /book/byWhat -> getBy() 方法.

这种映射, 就是一种路由关系. 通过地址路由到方法上.

二. 建立路由

1. 配置文件配置

DispatcherServlet.properties 文件中配置了两个路由处理类:

org.springframework.web.servlet.HandlerMapping=org.`.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

2. 配置类配置

spring-boot-autoconfigure 中, 有一块专门对 web 进行配置的类, WebMvcAutoConfiguration 是其中之一

@Bean
public SimpleUrlHandlerMapping faviconHandlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",
faviconRequestHandler()));
return mapping;
} @Bean
public ResourceHttpRequestHandler faviconRequestHandler() {
ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
requestHandler.setLocations(resolveFaviconLocations());
return requestHandler;
}

3. 类图

4. SimpleUrlHandlerMapping

由于 其 祖先类  ApplicationObjectSupport 实现了 ApplicationContextAware 接口, 所以在实例化后, 会调用  setApplicationContext() 方法:

@Override
public final void setApplicationContext(@Nullable ApplicationContext context) throws BeansException {
if (context == null && !isContextRequired()) {
// Reset internal context state.
this.applicationContext = null;
this.messageSourceAccessor = null;
}
else if (this.applicationContext == null) {
// Initialize with passed-in context.
if (!requiredContextClass().isInstance(context)) {
throw new ApplicationContextException(
"Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]");
}
this.applicationContext = context;
this.messageSourceAccessor = new MessageSourceAccessor(context);
initApplicationContext(context);
}
else {
// Ignore reinitialization if same context passed in.
if (this.applicationContext != context) {
throw new ApplicationContextException(
"Cannot reinitialize with different application context: current one is [" +
this.applicationContext + "], passed-in one is [" + context + "]");
}
}
} protected void initApplicationContext(ApplicationContext context) throws BeansException {
initApplicationContext();
} //空方法, 被子类重写
protected void initApplicationContext() throws BeansException {
}

SimpleUrlHandlerMapping 重写了  initApplicationContext 方法:

@Override
public void initApplicationContext() throws BeansException {
  //此处调用了 AbstractHandlerMapping 中的方法
super.initApplicationContext();
registerHandlers(this.urlMap);
} protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
if (urlMap.isEmpty()) {
logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
}
else {
urlMap.forEach((url, handler) -> {
// Prepend with slash if not already present.
if (!url.startsWith("/")) {
url = "/" + url;
}
// Remove whitespace from handler bean name.
if (handler instanceof String) {
handler = ((String) handler).trim();
}
registerHandler(url, handler);
});
}
}

在 faviconHandlerMapping() 中, 设置了 urlMap, 经过上面的方法后, 其关系为

/**/favicon.ico -> ResourceHttpRequestHandler

然后对其进行注册:

protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler; // Eagerly resolve handler if referencing singleton via name.
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
ApplicationContext applicationContext = obtainApplicationContext();
if (applicationContext.isSingleton(handlerName)) {
resolvedHandler = applicationContext.getBean(handlerName);
}
} Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
}
}
else {
if (urlPath.equals("/")) {
if (logger.isInfoEnabled()) {
logger.info("Root mapping to " + getHandlerDescription(handler));
}
setRootHandler(resolvedHandler);
}
else if (urlPath.equals("/*")) {
if (logger.isInfoEnabled()) {
logger.info("Default mapping to " + getHandlerDescription(handler));
}
setDefaultHandler(resolvedHandler);
}
else {
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isInfoEnabled()) {
logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}

5. BeanNameUrlHandlerMapping

这个类并没有重写   initApplicationContext() 方法. 但是他的父类 AbstractDetectingUrlHandlerMapping 重写了此方法:

@Override
public void initApplicationContext() throws ApplicationContextException {
super.initApplicationContext();
detectHandlers();
} protected void detectHandlers() throws BeansException {
ApplicationContext applicationContext = obtainApplicationContext();
if (logger.isDebugEnabled()) {
logger.debug("Looking for URL mappings in application context: " + applicationContext);
}
   //拿取容器中所有的 bean
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
applicationContext.getBeanNamesForType(Object.class));

//遍历容器中所有的 bean, 按照规则, 进行 urls 的生成工作
// Take any bean name that we can determine URLs for.
for (String beanName : beanNames) {
     //这个是一个抽象方法, 留给子类BeanNameUrlHandlerMapping实现的
String[] urls = determineUrlsForHandler(beanName);
if (!ObjectUtils.isEmpty(urls)) {
// URL paths found: Let's consider it a handler.
registerHandler(urls, beanName);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
}
}
}
}

接着看一下  BeanNameUrlHandlerMapping 里的方法:

public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {

    /**
* Checks name and aliases of the given bean for URLs, starting with "/".
*/
@Override
protected String[] determineUrlsForHandler(String beanName) {
List<String> urls = new ArrayList<>();
if (beanName.startsWith("/")) {
urls.add(beanName);
}
String[] aliases = obtainApplicationContext().getAliases(beanName);
for (String alias : aliases) {
if (alias.startsWith("/")) {
urls.add(alias);
}
}
return StringUtils.toStringArray(urls);
}
}
determineUrlsForHandler 实现了父类留的坑, 此处主要是检测 beanName 或其别名 是否是以 "/" 开头的, 如果是, 则对其执行注册方法 registerHandler(与前面4里是同一个方法). 
在此例中, 并没有 beanName 是以 "/" 开头的, 所以这里并没有进行任何注册操作.

6. RequestMappingHandlerMapping

RequestMappingHandlerMapping 并不是通过 initApplicationContext() 来进行扫描触发的.

其祖先类 AbstractHandlerMethodMapping 实现了 InitializingBean 接口, 也就是说, 在属性设置后, 会调用其 afterPropertiesSet() 方法.

但是  RequestMappingHandlerMapping 重写了 afterPropertiesSet() 方法:
@Override
public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setUrlPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
this.config.setContentNegotiationManager(getContentNegotiationManager()); super.afterPropertiesSet();
}
super.afterPropertiesSet() 调用的就是 AbstractHandlerMethodMapping 的方法了.
@Override
public void afterPropertiesSet() {
initHandlerMethods();
} private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget."; protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
//获取容器中所有的 beanName
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
obtainApplicationContext().getBeanNamesForType(Object.class));

  //遍历 beanName
for (String beanName : beanNames) {
      //判断 beanName是否以 scopedTarget. 开头
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
}
}
if (beanType != null && isHandler(beanType)) {
          //进一步处理
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}

isHandler() 是一个过滤方法, 判断 bean 是否有 Controller 或 RequestMapping 注解:

@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
detectHandlerMethods() 能进来的, 此例中就 bookController 了.
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) {
final 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.isDebugEnabled()) {
logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
}
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}

methods 就是 BookController 里面的那三个方法: add , getById, getAll

查找的基本思路:

1. 拿到spring容器中所有的 beanNames

2. 遍历beanNames, 进行过滤, 过滤依据: bean上是否有  Controller 或  RequestMapping 注解修饰

3. 对 bean 进行处理, 拿到他和他所有父类中的方法

4. 对这些方法进行过滤, 过滤依据为: 方法上是否有  RequestMapping 注解修饰, 并创建 RequestMappingInfo 对象 - A.

5. 为 bean 也创建 RequestMappingInfo 对象 - B.

  如果 B 为空(bean上没有 RequestMappingInfo注解修饰), 则跳过合并操作

  如果B不为空, 则对 A 和 B 进行合并操作. 路径 "/book/add"也就组合出来了.

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
} public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
assertUniqueMethodMapping(handlerMethod, mapping); if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
}
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();
}
}

这里的 mappingLookup 存放的是 RequestMappingInfo -> HandlerMethod

而 urlLookup 存放的是 url -> RequestMappingInfo

private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();

private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
												

springboot web - 建立路由的更多相关文章

  1. springboot+web文件上传和下载

    一.首先安装mysql数据库,开启web服务器. 二.pom.xml文件依赖包配置如下: <?xml version="1.0" encoding="UTF-8&q ...

  2. ASP.NET Web API路由解析

    前言 本篇文章比较长,仔细思考阅读下来大约需要15分钟,涉及类图有可能在手机显示不完整,可以切换电脑版阅读. 做.Net有好几年时间了从ASP.NET WebForm到ASP.NET MVC再到ASP ...

  3. ASP.NET Web API 路由对象介绍

    ASP.NET Web API 路由对象介绍 前言 在ASP.NET.ASP.NET MVC和ASP.NET Web API这些框架中都会发现有路由的身影,它们的原理都差不多,只不过在不同的环境下作了 ...

  4. ASP.NET Web API路由系统:路由系统的几个核心类型

    虽然ASP.NET Web API框架采用与ASP.NET MVC框架类似的管道式设计,但是ASP.NET Web API管道的核心部分(定义在程序集System.Web.Http.dll中)已经移除 ...

  5. ASP.NET Web API路由系统:Web Host下的URL路由

    ASP.NET Web API提供了一个独立于执行环境的抽象化的HTTP请求处理管道,而ASP.NET Web API自身的路由系统也不依赖于ASP.NET路由系统,所以它可以采用不同的寄宿方式运行于 ...

  6. Asp.Net Web API 2第六课——Web API路由和动作选择

    Asp.Net Web API 导航 Asp.Net Web API第一课——入门http://www.cnblogs.com/aehyok/p/3432158.html Asp.Net Web AP ...

  7. ASP.NET Web API 路由

    路由系统是请求消息进入ASP.NET Web API消息处理管道的第一道屏障,其根本目的是利用注册的路由表(RouteTable)对请求的URI进行解析以确定目标HttpController和Acti ...

  8. Web API路由与动作(三)

    本章包括三个小节  如果你输入了mvc的路由规则 这个可以粗略过一遍即可  内容说明有点繁琐 原文地址:http://www.asp.net/web-api/overview/web-api-rout ...

  9. Asp.Net Web APi 路由的特点

    在ASP.NET Web API中,路由是基于HTTP协议 GET请求路由到以GET开头的控制器方法,POST请求路由到以POST开头的控制器方法中,GET方法和GetProducts,都能与GET请 ...

随机推荐

  1. ubuntu 安装LAMP web 服务器, phpmyadmin 安装后无法打开解决

    安装方法: http://blog.chinaunix.net/uid-26495963-id-3173291.html 在上述文档中需要增加apache 支持mysql 功能. apt-get in ...

  2. ReLU函数

    Rectifier(neural networks) 在人工神经网络中,rectfier(整流器,校正器)是一个激活函数,它的定义是:参数中为正的部分. , 其中,x是神经元的输入.这也被称为ramp ...

  3. [PowerShell]Python虚拟环境激活失败

    用Activate.ps1激活还是失效的情况下, 用ISE打开发现路径中的中文乱码了. 所以解决方案有两种, 一是把中文路径改成英文 二是把Activate.ps1脚本用记事本打开另存为ANSI编码即 ...

  4. 利用视频解析网站免费观看各大平台VIP电影

    需求: 观看VIP电影.VIP电视 准备: 1.视频解析网站 2.VIP电影URL 教程开始: 1.百度搜索[视频解析],会索引出大量的视频解析网站,随便选择一个网站. 2.找到想观看的VIP视频,复 ...

  5. Python3(十一) 原生爬虫

    一.爬虫实例 1.原理:文本分析并提取信息——正则表达式. 2.实例目的:爬取熊猫TV某个分类下面主播的人气排行 分析网站结构 操作:F12查看HTML信息,Ctrl+Shift+C鼠标选取后找到对应 ...

  6. prometheus operator(Kubernetes 集群监控)

    一.Prometheus Operator 介绍 Prometheus Operator 是 CoreOS 开发的基于 Prometheus 的 Kubernetes 监控方案,也可能是目前功能最全面 ...

  7. LDAP安装

    一.介绍 LDAP 全称:Lightweight Directory Access Protocol,即“轻量级目录访问协议”. LDAP目录以树状的层次结构来存储数据.如果你对自顶向下的DNS树或U ...

  8. linux 手工释放内存 高内存 内存回收 方法思路

    linux  跑的apache,apache工作模式有   Prefork.Worker和 Event  三种,分别是基于进程.线程.综合模式.        本文中使用的apache是 Event  ...

  9. Cacti 抓取数据方式 安装spine

    安装好cacti后首先要设置获取数据的方式 Cacti 获取数据的方式有两种,1.监控端的脚本(可以是php, shell, perl 或其他脚本)2.或者 snmp 协议获取. Cacti 会在固定 ...

  10. Android 7.0新特性“Nougat”(牛轧糖)。

    1.Unicode 9支持和全新的emoji表情符号 Android Nougat将会支持Unicode 9,并且会新增大约70种emoji表情符号.这些表情符号大多数都是人形的,并且提供不同的肤色, ...