springboot web - 建立路由
一. 测试代码
@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 - 建立路由的更多相关文章
- springboot+web文件上传和下载
一.首先安装mysql数据库,开启web服务器. 二.pom.xml文件依赖包配置如下: <?xml version="1.0" encoding="UTF-8&q ...
- ASP.NET Web API路由解析
前言 本篇文章比较长,仔细思考阅读下来大约需要15分钟,涉及类图有可能在手机显示不完整,可以切换电脑版阅读. 做.Net有好几年时间了从ASP.NET WebForm到ASP.NET MVC再到ASP ...
- ASP.NET Web API 路由对象介绍
ASP.NET Web API 路由对象介绍 前言 在ASP.NET.ASP.NET MVC和ASP.NET Web API这些框架中都会发现有路由的身影,它们的原理都差不多,只不过在不同的环境下作了 ...
- ASP.NET Web API路由系统:路由系统的几个核心类型
虽然ASP.NET Web API框架采用与ASP.NET MVC框架类似的管道式设计,但是ASP.NET Web API管道的核心部分(定义在程序集System.Web.Http.dll中)已经移除 ...
- ASP.NET Web API路由系统:Web Host下的URL路由
ASP.NET Web API提供了一个独立于执行环境的抽象化的HTTP请求处理管道,而ASP.NET Web API自身的路由系统也不依赖于ASP.NET路由系统,所以它可以采用不同的寄宿方式运行于 ...
- 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 ...
- ASP.NET Web API 路由
路由系统是请求消息进入ASP.NET Web API消息处理管道的第一道屏障,其根本目的是利用注册的路由表(RouteTable)对请求的URI进行解析以确定目标HttpController和Acti ...
- Web API路由与动作(三)
本章包括三个小节 如果你输入了mvc的路由规则 这个可以粗略过一遍即可 内容说明有点繁琐 原文地址:http://www.asp.net/web-api/overview/web-api-rout ...
- Asp.Net Web APi 路由的特点
在ASP.NET Web API中,路由是基于HTTP协议 GET请求路由到以GET开头的控制器方法,POST请求路由到以POST开头的控制器方法中,GET方法和GetProducts,都能与GET请 ...
随机推荐
- 使用Apache服务器实现Nginx反向代理
实验环境:centos7 注:因为本次实验在同一台服务器上,Apache与Nginx同为80端口,所以改Apache端口为60 1 配置Nginx服务器: 编辑Nginx配置文件,写入以下内容 loc ...
- Intel发布神经网络压缩库Distiller:快速利用前沿算法压缩PyTorch模型——AttributeError: module ‘tensorboard' has no attribute 'lazy'
转载自:CSDN Nine-days 近日,Intel 开源了一个用于神经网络压缩的开源 Python 软件包 Distiller,它可以减少深度神经网络的内存占用.加快推断速度及节省能耗.Dis ...
- MingGW Posix VS Win32 - 明瓜娃的毒因
MinGW-posix和win32纠缠的瓜娃子 官方首席佛偈(SourceForge)的官网下载页 法克油啊,让我一个小白情何以堪. 盘TA wiki posix wiki中文-UNIX API标准 ...
- 题解 bzoj3688【折线统计】
考虑 \(dp\) . 首先把所有节点按 \(x\) 从小到大排序是很有必要的. 记 f[i][j][0] 表示满足以第 \(i\) 个节点做折线结尾,选取的点集 \(S\) 满足 \(f(S)=j\ ...
- num10---适配器模式
1.类适配器 Adapter类,通过继承 被适配的类,实现目标类的接口,完成适配. 分析: java 单继承,所以适配器类 需要继承 被适配类,这就要求目标类必须是接口,有一定局限性. 被适配类的方法 ...
- C++:析构函数的调用时机
结论:只有当类的某个实例化对象的构造函数执行完毕,而且当该对象退出作用域时,才会执行析构函数. 如果在执行构造函数的过程中抛出了异常,就不会调用析构函数 上测试代码: class Test { pub ...
- 【WPF学习】第四十五章 可视化对象
前面几章介绍了处理适量适中的图形内容的最佳方法.通过使用几何图形.图画和路径,可以降低2D图形的开销.即使正在使用复杂的具有分层效果的组合形状和渐变画刷,这种方法也仍然能够正常得很好. 然而,这样设计 ...
- Hanoi塔问题——递归
/////////////Hanoi塔问题///////#include<iostream>using namespace std;void hanoi(int i,char A,char ...
- 【Codeforces #312 div2 A】Lala Land and Apple Trees
# [Codeforces #312 div2 A]Lala Land and Apple Trees 首先,此题的大意是在一条坐标轴上,有\(n\)个点,每个点的权值为\(a_{i}\),第一次从原 ...
- JVM解毒——类加载子系统
带着问题,尤其是面试问题的学习才是最高效的.加油,奥利给! 点赞+收藏 就学会系列,文章收录在 GitHub JavaEgg ,N线互联网开发必备技能兵器谱 直击面试 看你简历写得熟悉JVM,那你说说 ...