Spring MVC源码解析——HandlerMapping(处理器映射器)
Sping MVC 源码解析——HandlerMapping处理器映射器
- 1. 什么是HandlerMapping
- 2. HandlerMapping
- 2.1 HandlerMapping初始化
- 2.2 getHandler解析
- 3. getHandlerInternal()子类实现
- 3.1 AbstractUrlHandlerMapping与AbstractHandlerMethodMapping的区别
- 3.2 AbstractUrlHandlerMapping
- 3.3 AbstractHandlerMethodMapping
- 4. RequestMappingHandlerMapping
- 4.1 加载过程
- 5. SimpleUrlHandlerMapping
- 6. 使用HandlerMapping
- 6.1 如何选择合适的HandlerMapping
- 7. 总结
- 8. 参考文章
注:图片引用自SpringMVC 解析(一)概览 - 御狐神 - 博客园 (cnblogs.com)
1. 什么是HandlerMapping
在Spring MVC中,HandlerMapping(处理器映射器)用于确定请求处理器对象。请求处理器可以是任何对象,只要它们使用了@Controller注解或注解@RequestMapping。HandlerMapping负责将请求(url)映射到适当的处理器对象(Controller)。
注:Handler即绑定了注解@RequestMapping或@Controller的类
HandlerMapping接口定义了一个方法:
public interface HandlerMapping {HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
getHandler方法用于查找处理器对象并返回处理程序的执行链,HandlerExecutionChain包含了处理器对象和一系列拦截器,这些拦截器可以在处理程序执行之前和之后执行一些操作。
HandlerMapping接口继承结构体系
2. HandlerMapping
2.1 HandlerMapping初始化
Http请求由servlet容器分发到DispatcherServlet,在其中会进行九大核心组件的初始化
protected void initStrategies(ApplicationContext context) {this.initMultipartResolver(context);this.initLocaleResolver(context);this.initThemeResolver(context);this.initHandlerMappings(context);this.initHandlerAdapters(context);this.initHandlerExceptionResolvers(context);this.initRequestToViewNameTranslator(context);this.initViewResolvers(context);this.initFlashMapManager(context);
}
这里我们主要关注
initHandlerMappings
public class DispatcherServlet extends FrameworkServlet {private void initHandlerMappings(ApplicationContext context) {...}
}private void initHandlerMappings(ApplicationContext context) {this.handlerMappings = null;//private boolean detectAllHandlerMappings = true; //该值默认为 true,查询所有的handlerMapping// 如果设置为 false , Spring MVC就只会查找名为“handlerMapping”的bean,并作为当前系统的唯一的HandlerMappingif (this.detectAllHandlerMappings) {//在ApplicationContext中查找所有handler映射,包括父类上下文。Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);//存在Handlerif (!matchingBeans.isEmpty()) {//将获取的 HandlerMapping 转换成集合并排序this.handlerMappings = new ArrayList(matchingBeans.values());AnnotationAwareOrderComparator.sort(this.handlerMappings);}} else {//没有找到try {//查找名为“handlerMapping”的bean,并作为当前系统的唯一的HandlerMapping,确保至少有一个HandlerMappingHandlerMapping hm = (HandlerMapping)context.getBean("handlerMapping", HandlerMapping.class);this.handlerMappings = Collections.singletonList(hm);} catch (NoSuchBeanDefinitionException var4) {}}this.handlerMappings = this.getDefaultStrategies(context, HandlerMapping.class);}Iterator var6 = this.handlerMappings.iterator();//迭代HandlerMappingwhile(var6.hasNext()) {HandlerMapping mapping = (HandlerMapping)var6.next();//判断HandlerMapping实例是否启用了解析的PathPatterns//如果启用则DispatcherServlet会自动解析RequestPath,以便在HandlerMappings,HandlerInterceptors和其他组件中访问if (mapping.usesPathPatterns()) {this.parseRequestPath = true;break;}}}
Spring MVC框架中有多个HandlerMapping实现,每个实现都可以使用不同的策略来确定请求处理器对象。最常见的HandlerMapping实现是RequestMappingHandlerMapping和SimpleUrlHandlerMapping。
2.2 getHandler解析
getHandler方法根据请求找到对应的处理器对象, 在
DispatcherServlet
类中,doDispatch()
方法调用getHandler()
方法得到HandlerExecutionChain
对象。
注:
HandlerExecutionChain
是一个类,用于封装处理器对象和拦截器列表。它是HandlerMapping
的getHandler
方法的返回值。
HandlerExecutionChain
的作用是在请求处理流程中,提供一种责任链模式(看图),让拦截器可以在处理器执行前后进行一些额外的操作,例如验证、日志、事务等。拿到这个对象后,DispatcherServlet会调用它的applyPreHandle方法,执行所有拦截器的preHandle方法。如果都返回true,则继续调用处理器的方法;否则,返回响应或者跳转到其他页面。
处理器执行完毕后,DispatcherServlet会调用它的applyPostHandle方法,执行所有拦截器的postHandle方法。这些方法可以对模型和视图进行修改或增强。
最后,在渲染视图之后,DispatcherServlet会调用它的triggerAfterCompletion方法,执行所有拦截器的afterCompletion方法。这些方法可以进行一些清理工作或异常处理
注:图片引用自SpringMVC 解析(一)概览 - 御狐神 - 博客园 (cnblogs.com)
源码解析:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {mappedHandler = this.getHandler(processedRequest);
}
//返回一个HandlerExecutionChain对象
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {Iterator var2 = this.handlerMappings.iterator();//遍历所有的初始化的HandlerMapping列表while(var2.hasNext()) {HandlerMapping mapping = (HandlerMapping)var2.next();//获取一个HandlerExecutionChain对象,它包含了处理请求的handler对象和任何配置的拦截器//接口方法HandlerExecutionChain handler = mapping.getHandler(request);//匹配到就进行返回if (handler != null) {return handler;}}}return null;
}
实现类为
AbstractHandlerMapping
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered, BeanNameAware {public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {……};
}@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {// 根据请求从缓存中查找handler,如果没有则调用子类实现的getHandlerInternal()方法该方法在本类中有定义,是一个protected型的抽象方法Object handler = this.getHandlerInternal(request);// 没有找到,则会使用getDefaultHandler()方法获取默认的处理器if (handler == null) {handler = this.getDefaultHandler();}if (handler == null) {return null;} else {//检查handler对象是否是一个字符串类型。如果是的话,就从应用上下文中根据字符串名称获取对应的bean作为handler对象if (handler instanceof String) {String handlerName = (String)handler;handler = this.obtainApplicationContext().getBean(handlerName);}//如果请求对象中没有缓存的路径信息,就调用initLookupPath方法来解析请求的路径信息,并且保存在请求对象的一个属性中if (!ServletRequestPathUtils.hasCachedPath(request)) {this.initLookupPath(request);}//根据handler对象和请求创建一个HandlerExecutionChain对象,包含处理器对象和拦截器列表HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);return executionChain;}
}
到此为止就获得了executionChain对象实例了,也就是第一张图的第4步。
3. getHandlerInternal()子类实现
getHandlerInternal()
方法在AbstractUrlHandlerMapping
类和AbstractHandlerMethodMapping
类中均有实现, 均继承于AbstractHandlerMapping类
3.1 AbstractUrlHandlerMapping与AbstractHandlerMethodMapping的区别
AbstractUrlHandlerMapping
是基于URL映射的HandlerMapping
,它支持字面匹配和模式匹配,如"/test/*“,”/test/"等, 它返回的Handler是一个类级别的对象**,例如一个Controller类或一个Bean对象
AbstractHandlerMethodMapping
是基于方法级别的HandlerMapping
,它支持使用@RequestMapping注解来指定请求路径和方法。它返回的Handler是一个方法级别的对象,例如一个Controller类中的某个方法或一个Bean对象中的某个方法
示例:
AbstractUrlHandlerMapping
的一个子类是SimpleUrlHandlerMapping
,它可以在XML配置文件中定义URL和Handler的映射关系
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"><property name="mappings"><props><prop key="/test.do">testController</prop></props></property>
</bean><bean id="testController" class="com.example.TestController"/>
AbstractHandlerMethodMapping
的一个子类是RequestMappingHandlerMapping
,它可以在Java类中使用@RequestMapping注解来定义URL和方法的映射关系
@Controller
@RequestMapping("/test")
public class TestController {@RequestMapping("/do")public String doSomething() {// ...}
}
这两种方式都可以实现URL和Handler的绑定,但是后者更灵活和简洁
3.2 AbstractUrlHandlerMapping
//根据用户请求信息中的URL查找handler
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {//初始化请求的查找路径,也就是去掉上下文路径和后缀名的URL路径String lookupPath = this.initLookupPath(request);Object handler;//是否使用路径模式来选择不同的查找handler的方法//if (this.usesPathPatterns()) {//获取请求的RequestPath对象,它封装了请求的URL路径、上下文路径、后缀名等信息。RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request);//根据RequestPath对象、查找路径和请求对象,调用lookupHandler()方法来查找匹配的handlerhandler = this.lookupHandler(path, lookupPath, request);} else {handler = this.lookupHandler(lookupPath, request);}//没有找到匹配的handler时,尝试获取根handler或者默认handler,并进行验证和包装。if (handler == null) {Object rawHandler = null;if (StringUtils.matchesCharacter(lookupPath, '/')) {rawHandler = this.getRootHandler();}if (rawHandler == null) {rawHandler = this.getDefaultHandler();}if (rawHandler != null) {if (rawHandler instanceof String) {String handlerName = (String)rawHandler;rawHandler = this.obtainApplicationContext().getBean(handlerName);}this.validateHandler(rawHandler, request);handler = this.buildPathExposingHandler(rawHandler, lookupPath, lookupPath, (Map)null);}}//返回获取到的handler对象return handler;
}
3.3 AbstractHandlerMethodMapping
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {//初始化请求的查找路径,也就是去掉上下文路径和后缀名的URL路径String lookupPath = this.initLookupPath(request);//获取映射注册表的读锁,以保证线程安全this.mappingRegistry.acquireReadLock();HandlerMethod var4;//获取读锁后,尝试查找匹配的handler方法,并在最后释放读锁try {//根据查找路径和请求对象,在映射注册表中查找匹配的handler方法HandlerMethod handlerMethod = this.lookupHandlerMethod(lookupPath, request);//判断是否找到了handler方法//如果找到了,则调用createWithResolvedBean()方法来创建一个新的HandlerMethod实例,并解析其关联的bean对象;//如果没有找到,则返回null。var4 = handlerMethod != null ? handlerMethod.createWithResolvedBean() : null;} finally {this.mappingRegistry.releaseReadLock();}//返回获取到的handler对象return var4;
}
下面简单介绍一下AbstractUrlHandlerMapping与AbstractHandlerMethodMapping的常用子类实现
4. RequestMappingHandlerMapping
RequestMappingHandlerMapping
是Spring MVC中最常用的HandlerMapping实现之一。它使用@RequestMapping
注解来确定请求处理器对象。
下面是一个简单的控制器示例:
@Controller
@RequestMapping("/user")
public class UserController {@GetMapping("/{id}")public String getUser(@PathVariable int id, Model model) {User user = userRepository.findById(id);model.addAttribute("user", user);return "user";}
}
在这个示例中,
UserController
类被@Controller注解标记为一个控制器,并且所有请求路径以/user开头。@GetMapping注解用于处理HTTP GET请求,{id}是一个路径变量,它将匹配任何非空字符串,并将其解析为一个整数。getUser方法将通过id获取用户对象并将其添加到模型中,最后返回一个视图名为"user"的字符串。
RequestMappingHandlerMapping
会扫描所有@Controller注解标记的类,并将所有带有@RequestMapping注解的方法添加到HandlerMapping中。在请求到达服务器时,RequestMappingHandlerMapping
将匹配请求路径和请求方法,并将请求映射到适当的处理器方法。
4.1 加载过程
RequestMappingHandlerMapping
实现了接口InitializingBean
,在bean加载完成后会自动调用afterPropertiesSet
方法,在此方法中调用了initHandlerMethods()来实现初始化initHandlerMethods()
会遍历所有bean,如果bean实现带有注解@Controller或者@RequestMapping 则进一步调用detectHandlerMethods
处理,处理逻辑大致就是根据@RequestMapping配置的信息,把解析结果封装成RequestMappingInfo
对象,也就是说RequestMappingInfo
对象是用来装载方法的匹配相关信息,每个匹配的方法都会对应一个RequestMappingInfo
对象,然后注册到MappingRegistry
中
具体流程:
- 当Spring容器启动时,它会扫描所有带有@Controller或@RestController注解的类,并将它们作为处理器对象注册到
RequestMappingHandlerMapping
中。RequestMappingHandlerMapping
会遍历每个处理器对象中的所有方法,并使用getMappingForMethod()
方法来获取每个方法上定义或继承的@RequestMapping注解,然后将这些注解转换为RequestMappingInfo
对象,包含了请求路径、请求方法、请求参数、请求头等信息。RequestMappingHandlerMapping
会将每个RequestMappingInfo
对象和对应的处理器方法封装成一个HandlerMethod
对象,并将这些对象存储在一个Map结构中,以便于后续查找。- 当一个HTTP请求到达
DispatcherServlet
时,它会调用RequestMappingHandlerMapping
的getHandler()
方法来根据请求URI找到匹配的处理器方法。RequestMappingHandlerMapping
会遍历Map中的所有键值对(即每个RequestMappingInfo和HandlerMethod
),并使用PathMatcher
或PathPatternParser
来判断请求URI是否与RequestMappingInfo
中定义的路径匹配。如果匹配,就返回对应的HandlerMethod
;如果不匹配,就继续查找下一个键值对。- 如果找到了匹配的
HandlerMethod
,DispatcherServlet
就会根据其类型选择合适的HandlerAdapter
来执行它,并返回响应结果;如果没有找到匹配的HandlerMethod
,DispatcherServlet
就会抛出异常或者返回404错误页面。
步骤1的注册过程
public void afterPropertiesSet() {//……super.afterPropertiesSet();
}
//父类AbstractHandlerMethodMapping 的方法
public void afterPropertiesSet() {this.initHandlerMethods();
}//扫描容器中的bean,检测和注册handler方法
protected void initHandlerMethods() {//获取候选的bean名称数组String[] var1 = this.getCandidateBeanNames();int var2 = var1.length;//遍历for(int var3 = 0; var3 < var2; ++var3) {String beanName = var1[var3];//用于过滤掉以"scopedTarget."开头的bean名称的。//这些bean名称是由Spring创建的代理对象,用于支持不同作用域的bean//例如session或request。这些代理对象不是真正的handler,所以要排除掉。if (!beanName.startsWith("scopedTarget.")) {//处理候选的bean,判断候选的bean是否是一个handler,也就是是否有@Controller注解或者@RequestMapping注解//如果是的话,就调用detectHandlerMethods方法,用于检测和注册handler方法this.processCandidateBean(beanName);}}//初始化handler方法, 对所有handler方法进行排序和日志输出this.handlerMethodsInitialized(this.getHandlerMethods());
}//检测和注册handler方法
protected void detectHandlerMethods(Object handler) {//存储handler对象的类信息//如果handler是一个字符串,那么就使用应用程序上下文来获取对应的类类型;否则就直接使用handler.getClass()方法来获取类类型。Class<?> handlerType = handler instanceof String ? this.obtainApplicationContext().getType((String)handler) : handler.getClass();if (handlerType != null) {//存储handlerType去除代理和增强后的原始类类型。这是为了避免AOP对方法检测造成干扰。Class<?> userType = ClassUtils.getUserClass(handlerType);//存储userType中所有带有映射注解(如@RequestMapping)的方法及其对应的映射信息Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (method) -> {try {//获取每个方法上定义或继承的映射信息return this.getMappingForMethod(method, userType);} catch (Throwable var4) {throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, var4);}});//遍历methods中每个键值对(即每个方法及其映射信息)methods.forEach((method, mapping) -> {//存储经过AopUtils.selectInvocableMethod()方法处理后可以被调用(即没有被final修饰)//且与userType匹配(即没有被覆盖) 的原始或桥接(即泛型擦除后生成) 方法Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);//注册到处理器映射this.registerHandlerMethod(handler, invocableMethod, mapping);});}}
注:
RequestMappingInfo
类,主要用来记录方法上@RequestMapping()
注解里面的参数,针对RequestMappingHandlerMapping
映射器来使用。
步骤2:
RequestMappingHandlerMapping
会遍历每个处理器对象中的所有方法,并使用getMappingForMethod()
方法来获取每个方法上定义或继承的@RequestMapping注解,然后将这些注解转换为RequestMappingInfo
对象,包含了请求路径、请求方法、请求参数、请求头等信息。
//根据处理器类和方法上的@RequestMapping注解来创建一个RequestMappingInfo对象(封装了请求映射的信息,如请求路径、请求方法、请求参数、请求头等)
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {//获取方法上定义或继承的@RequestMapping注解,并将其转换为一个RequestMappingInfo对象RequestMappingInfo info = this.createRequestMappingInfo(method);if (info != null) {//获取处理器类上定义或继承的@RequestMapping注解,并将其转换为另一个RequestMappingInfo对象RequestMappingInfo typeInfo = this.createRequestMappingInfo(handlerType);if (typeInfo != null) {//合并两个RequestMappingInfo对象info = typeInfo.combine(info);}//获取处理器类上定义或继承的@PathPrefix注解,并将其转换为一个字符串前缀String prefix = this.getPathPrefix(handlerType);if (prefix != null) {//根据指定的路径前缀和配置选项来构建一个新的请求映射信息,并且与原有信息进行合并info = RequestMappingInfo.paths(new String[]{prefix}).options(this.config).build().combine(info);}}return info;
}
步骤3:
RequestMappingHandlerMapping
会将每个RequestMappingInfo
对象和对应的处理器方法封装成一个HandlerMethod
对象,并将这些对象存储在一个Map结构中,以便于后续查找。
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {super.registerHandlerMethod(handler, method, mapping);this.updateConsumesCondition(mapping, method);
}//AbstractHandlerMethodMapping类中的方法
protected void registerHandlerMethod(Object handler, Method method, T mapping) {this.mappingRegistry.register(mapping, handler, method);
}public void register(T mapping, Object handler, Method method) {try {// 创建HandlerMethod对象HandlerMethod handlerMethod = AbstractHandlerMethodMapping.this.createHandlerMethod(handler, method);this.validateMethodMapping(handlerMethod, mapping);//获取匹配条件对应的直接路径,添加到pathLookup中Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);Iterator var6 = directPaths.iterator();while(var6.hasNext()) {String path = (String)var6.next();this.pathLookup.add(path, mapping);}//如果有命名策略,获取handler方法的名称,添加到nameLookup中String name = null;if (AbstractHandlerMethodMapping.this.getNamingStrategy() != null) {name = AbstractHandlerMethodMapping.this.getNamingStrategy().getName(handlerMethod, mapping);this.addMappingName(name, handlerMethod);}//将匹配条件和MappingRegistration对象(封装了handler方法、直接路径、名称、跨域配置等信息)添加到registry中this.registry.put(mapping, new AbstractHandlerMethodMapping.MappingRegistration(mapping, handlerMethod, directPaths, name, corsConfig != null));} finally {}}
步骤4:当一个HTTP请求到达
DispatcherServlet
时,它会调用RequestMappingHandlerMapping
的getHandler()
方法来根据请求URI找到匹配的处理器方法。(即3.3节提到的AbstractHandlerMethodMapping
父类中的getHandler()
方法)
步骤5:
RequestMappingHandlerMapping
会遍历Map中的所有键值对(即每个RequestMappingInfo和HandlerMethod
),并使用PathMatcher
或PathPatternParser
来判断请求URI是否与RequestMappingInfo
中定义的路径匹配。如果匹配,就返回对应的HandlerMethod
;如果不匹配,就继续查找下一个键值对。
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {// 创建一个空的Match列表,用来存放匹配的RequestMappingInfo和HandlerMethod对象List<AbstractHandlerMethodMapping<T>.Match> matches = new ArrayList();// 从mappingRegistry中获取直接路径匹配(即没有通配符或变量)的RequestMappingInfo列表List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);if (directPathMatches != null) {// 调用addMatchingMappings方法,判断直接路径匹配的RequestMappingInfo是否与请求条件匹配//如果匹配,就添加到matches列表中this.addMatchingMappings(directPathMatches, matches, request);}if (matches.isEmpty()) {// 如果matches列表为空,说明没有直接路径匹配的RequestMappingInfo与请求条件匹配//那么就从mappingRegistry中获取所有注册过的RequestMappingInfo,//并调用addMatchingMappings方法判断是否与请求条件匹配//如果匹配,就添加到matches列表中this.addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);}if (matches.isEmpty()) {// 如果matches列表仍然为空,说明没有任何一个RequestMappingInfo与请求条件匹配//那么就调用handleNoMatch方法处理没有匹配结果的情况,并返回null或抛出异常return this.handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);} else {// 如果matches列表不为空,说明至少有一个RequestMappingInfo与请求条件匹配// 那么就从matches列表中获取第一个Match对象(即最先添加进去的),作为最佳匹配结果AbstractHandlerMethodMapping<T>.Match bestMatch = (AbstractHandlerMethodMapping.Match)matches.get(0);if (matches.size() > 1) {// 如果matches列表中有多个Match对象(即有多个RequestMappingInfo与请求条件匹配)//那么就需要进行排序和筛选// 首先创建一个比较器comparator,根据getMappingComparator方法返回的比较器对每个Match对象进行比较(主要比较它们包含的RequestMappingInfo)Comparator<AbstractHandlerMethodMapping<T>.Match> comparator = new AbstractHandlerMethodMapping.MatchComparator(this.getMappingComparator(request));// 然后对matches列表进行排序,根据comparator比较器确定每个Match对象之间的优先级和顺序matches.sort(comparator);// 再次从matches列表中获取第一个Match对象(即排序后最优先级最高、顺序最靠前、//最符合请求条件、最具体化、最少参数化等等)作为最佳匹配结果bestMatch = (AbstractHandlerMethodMapping.Match)matches.get(0);}}request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());this.handleMatch(bestMatch.mapping, lookupPath, request);return bestMatch.getHandlerMethod();}}
步骤6:如果找到了匹配的
HandlerMethod
,DispatcherServlet
就会根据其类型选择合适的HandlerAdapter
来执行它,并返回响应结果;如果没有找到匹配的HandlerMethod
,DispatcherServlet
就会抛出异常或者返回404错误页面。
5. SimpleUrlHandlerMapping
SimpleUrlHandlerMapping
是另一个常用的HandlerMapping
实现。它允许指定URL模式和Handler的映射关系。
下面是一个简单的SimpleUrlHandlerMapping示例:
public class MySimpleUrlHandlerMapping extends SimpleUrlHandlerMapping {public MySimpleUrlHandlerMapping() {Properties mappings = new Properties();mappings.setProperty("/hello", "helloController");setMappings(mappings);}
}
在这个示例中,我们创建了一个名为
MySimpleUrlHandlerMapping
的自定义HandlerMapping
类。我们通过创建一个Properties
对象并将请求路径"/hello"映射到控制器名为"helloController"来设置URL映射。当请求到达服务器时,
SimpleUrlHandlerMapping
将查找请求路径并将其与已注册的URL模式进行匹配。如果找到匹配项,则返回关联的处理程序对象。
下面是一个使用SimpleUrlHandlerMapping的控制器示例:
public class HelloController implements Controller {@Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {ModelAndView mav = new ModelAndView("hello");mav.addObject("message", "Hello, World!");return mav;}
}
在这个示例中,我们创建了一个名为HelloController
的控制器类,并实现了Controller接口。handleRequest
方法将返回一个名为"hello"的视图,并将一个名为"message"的字符串添加到模型中。
我们将HelloController
添加到MySimpleUrlHandlerMapping
中:
MySimpleUrlHandlerMapping handlerMapping = new MySimpleUrlHandlerMapping();
Map<String, Object> urlMap = new HashMap<>();
urlMap.put("/hello", new HelloController());
handlerMapping.setUrlMap(urlMap);
在这个示例中,我们将HelloController
添加到MySimpleUrlHandlerMapping
的URL映射中,并将其注册到Spring MVC应用程序上下文中。
介绍一下这个类中的一个重要方法
registerHandlers()
, 这个方法是在Spring框架中用来注册URL映射器的,它可以将URL模式和请求处理器(handler)关联起来
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {if (urlMap.isEmpty()) {//不作操作} else {//遍历urlMap中的每一对键值对,键是URL模式,值是handler对象urlMap.forEach((url, handler) -> {//对于每一个URL模式,如果它没有以斜杠(/)开头,则在前面加上一个斜杠if (!url.startsWith("/")) {url = "/" + url;}//对于每一个handler对象,如果它是一个字符串,则去掉字符串两端的空白字符if (handler instanceof String) {handler = ((String)handler).trim();}//调用父类AbstractUrlHandlerMapping的registerHandler方法,将URL模式和handler对象注册到内部的handlerMap中this.registerHandler(url, handler);});}}
6. 使用HandlerMapping
在Spring MVC中,使用HandlerMapping
非常简单。在控制器中,您可以使用@Autowired注解来注入HandlerMapping
实现,并使用它来查找适当的处理程序对象。
@Controller
public class MyController {@Autowiredprivate HandlerMapping handlerMapping;@RequestMapping("/my/path")public String handleRequest(HttpServletRequest request) throws Exception {HandlerExecutionChain chain = handlerMapping.getHandler(request);Object handler = chain.getHandler();// ...}
}
在这个示例中,我们使用@Autowired注解将HandlerMapping注入到控制器中。在handleRequest方法中,我们使用HandlerMapping
的getHandler方法来获取请求的处理程序对象。我们可以进一步操作处理程序对象,例如调用其方法或检查其注解。
6.1 如何选择合适的HandlerMapping
SpringMVC提供了多种HandlerMapping实现类,你可以根据不同的方式来配置URL和Handler的映射关系,例如:
- 使用XML配置文件来定义
SimpleUrlHandlerMapping
或BeanNameUrlHandlerMapping
,这种方式比较传统,但是可以集中管理所有的映射关系,也可以自定义拦截器。 - 使用@RequestMapping注解来定义
RequestMappingHandlerMapping
,这种方式比较流行,但是需要在每个Controller类或方法上添加注解,也可以使用@PathVariable或@RequestParam等注解来获取请求参数。 - 使用@ControllerAdvice注解来定义
ExceptionHandlerExceptionResolver
,这种方式可以用来统一处理异常情况,也可以使用@ExceptionHandler或@ResponseStatus等注解来自定义异常处理逻辑。
你可以根据你的喜好和需求来选择一种或多种HandlerMapping
实现类,并且可以通过设置order属性来调整它们的优先级
7. 总结
HandlerMapping
是Spring MVC中非常重要的一个组件,它负责将请求映射到适当的处理程序对象。RequestMappingHandlerMapping
和SimpleUrlHandlerMapping
是两个常用的HandlerMapping实现。RequestMappingHandlerMapping
使用@RequestMapping注解来确定处理程序对象,而SimpleUrlHandlerMapping
使用URL模式来确定处理程序对象。
8. 参考文章
SpringMVC 解析(一)概览 - 御狐神 - 博客园 (cnblogs.com)
RequestMappingHandlerMapping详解 - 朱子威 - 博客园 (cnblogs.com)
SpringMVC工作原理之处理映射HandlerMapping] - 简书 (jianshu.com)
越看越糊涂,打算停一停准备找实习了,希望大家都能有个好结果!!!
相关文章:

Spring MVC源码解析——HandlerMapping(处理器映射器)
Sping MVC 源码解析——HandlerMapping处理器映射器1. 什么是HandlerMapping2. HandlerMapping2.1 HandlerMapping初始化2.2 getHandler解析3. getHandlerInternal()子类实现3.1 AbstractUrlHandlerMapping与AbstractHandlerMethodMapping的区别3.2 AbstractUrlHandlerMapping3…...

【Word/word2007】将标题第1章改成第一章
问题:设置多级列表没有其他格式选的解决办法和带来的插入图注解的问题,将标题第1章改成第一章的问题其他方案。 按照百度搜索的方法设置第一章,可以是没有相应的样式可以选。 那就换到编号选项 设置新的编号值 先选是 然就是变得很丑 这时打开…...

NLP预训练模型
Models Corpus RoBERTa: A Robustly Optimized BERT Pretraining Approach 与BERT主要区别在于: large mini-batches 保持总训练tokens数一致,使用更大的学习率、更大的batch size,adam β20.98\beta_20.98β20.98;dynamic ma…...

Typora上传文档图片链接失效的问题+PicGo布置图床在Github
文章目录typora图片链接失效原因PicGO开源图床布置先配置Github2.1先创建新仓库、用于存放图片2.2生成一个token,用picGo访问github3.下载picGo,并进行配置3.1 配置v4.1typora图片链接失效原因 因为你是保存在本地的,因此图片是不能访问,可以…...

win10安装oracle
文件放到最后。我的电脑是win11的,因为老师让写下安装笔记,在11上安装的时候没有截屏,所以在虚拟机上重新安装下吧。室友说要把文件夹放到c盘才能打开。我试了下,具体的是要把Oracle11g文件夹放到c盘根目录下。如果解压后不是这个…...

AQS为什么用双向链表?
首先,在AQS中,等待队列是通过Node类来表示的,每个Node节点包含了等待线程的信息以及等待状态。下面是Node类的部分源码:static final class Node {// 等待状态volatile int waitStatus;// 前驱节点volatile Node prev;// 后继节点…...

AtCoder Beginner Contest 292——A-E题讲解
蒟蒻来讲题,还望大家喜。若哪有问题,大家尽可提! Hello, 大家好哇!本初中生蒟蒻讲解一下AtCoder Beginner Contest 292这场比赛的A-E题! A题 原题 Problem Statement You are given a string SSS consisting of lo…...

(蓝桥真题)最长不下降子序列(权值线段树)
样例输入: 5 1 1 4 2 8 5 样例输出: 4 分析:看到这种对其中连续k个数进行修改的我们就应该想到答案是由三部分组成,因为求的是最长不下降子序列,那么我们可以找到一个最合适的断点i,使得答案是由区间[1…...

数据类型及参数传递
1.数据类型 java中的基本数据类型: 数值型: 整数型:byte short long int 浮点型:float double 布尔型: boolean字符串: char java中的引用数据类型: 数组(array) 类(class…...

永春堂1300系统开发|解析永春堂1300模式商城的五大奖项
电商平台竞争越来越激烈,各种营销方式也是层出不穷,其中永春堂1300营销模式,以其无泡沫和自驱动性强等特点风靡一时。在这套模式中,虽然单型价格差异较大,但各种奖励的设计,巧妙的兼顾了平台和所有会员的利…...

最近一年我都干了什么——反思!!
过去一年不管是学习方式还是心态上都和以往有了许多不同的地方,比较昏昏沉沉。最近慢慢找到状态了,就想赶紧记录下来。 学习 在学习新技术的过程中开始飘了,总感觉有了一些开发经验后就觉得什么都不用记,知道思路就行遇到了现场百…...

Docker学习(十七)save 和 export 命令的区别
Docker 中有两个命令可以将镜像导出为本地文件系统中的 tar 文件:docker save 和 docker export。尽管它们的作用类似,但它们之间有一个重要的区别。 1.使用方式的不同: docker save 的使用示例: docker save -o test.tar image…...

【数据结构初阶】详解“树”
目录 前言 1.树概念及结构 (1)树的概念 (2)树的名词介绍 (3)树的表示 编辑 2.二叉树概念及结构 (1)概念 (2)特殊的二叉树 (3࿰…...

20230304 CF855 div3 vp
Dashboard - Codeforces Round 855 (Div. 3) - Codeforces呃呃,评价是,毫无进步呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃呃该加训了该加训了该加训了该加训了该加训了该加训了该加训了该加训了该加训了该加训了该加训了该加训了该加训了该加训了该加训了该加训…...

UML 时序图
时序图(Sequence Diagram)是显示对象之间交互的图,是按时间顺序排列的。 时序图中显示的是参与交互的对象及其对象之间消息交互的顺序。 时序图包括的建模元素主要有:对象(Actor)、生命线(Lif…...

详解进程 及 探查进程
进程的概念PCB是什么task_struct的作用如何执行进程进程的探查什么是bashps命令的使用(查看进程)创建进程探究父子进程进程的概念 简而言之,进程就是正在在执行的程序 之前说过,程序执行的第一步Windows是双击程序Linux是 ./ &a…...
汇编相关问题
汇编语言期末复习题DX:单项选择题 DU:多项选择题 TK:填空题 MC:名词解释 v JD:简答题 CXFX:程序分析题 CXTK:程序填空题 BC:编程题第1章:基础知识1、在汇编语言程序的开发…...

华为OD机试Golang解题 - 火星文计算 2 | 包含思路
华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典文章目录 华为Od必看系列使用说明本期题目…...

成功解决configure: error: the HTTP rewrite module requires the PCRE library
文章目录 前言问题复现问题解决思考环节总结前言 大家好,我是沐风晓月,本专栏是记录日常实验中的所有疑难杂症,教程的安装,程序的bug,甚至各类报错,如果你也遇到了困惑和问题,欢迎与我一起交流学习。 另外不要解决完问题就结束了,思考环节也要好好看看哦。 问题复现…...

UNIX--GDB调试
通常,在为调试而编译时,我们会关掉编译器优化选项(-O),并打开调试选项(-g)。另外,-Wall 在尽量不影响程序行为的情况下选项打开所有 warning,也可以发现许多问题,避免一些不必要的 BUG。 GDB 命令-启动、退…...

孤单数算法
1.背景 腾讯终面:孤单的QQ号码怎么找? 问题一:有n个QQ号码,除1个孤单的QQ号码外,其余的QQ号码都是成双成对的,求这个孤单的QQ号码,要求:时间复杂度为O(n), 空间复杂度为O(1). 问题…...

triangulate_object_model_3d算子总结
目录 1.去掉固定方向的点云干扰 2.增加八叉树深度,实现更高细节级别的三角测量 3.腐蚀和膨胀,得到更平滑的点云 1.去掉固定方向的点云干扰 例程:triangulate_object_model_3d_xyz_mapping.hdev...

ZincSearch Java 客户端教程
ZincSearch Zinc 简单、强大,不了解的同学可以参见我之前的博客。今天我们这里谈谈 Java 环境如何集成 Zinc 客户端,跟如何使用的。 安装 Zinc 到 Github 的官方 Releases 下载: 我的是 Windows 开发环境,下载 zincsearch_0.4…...

数据结构(一)(嵌入式学习)
数据结构干货总结(一)基础线性表的顺序表示线性表的链式表示单链表双链表循环链表循环单链表循环双链表栈顺序存储链式存储队列队列的定义队列的常见基本操作队列的顺序存储结构顺序队列循环队列队列的链式存储结构树概念二叉树二叉树的创建基础 数据&a…...

合成复用原则-快速理解
什么是合成/聚合复用原则? 合成/聚合复用原则是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用已有功能的目的。 简述为:要尽量使用合成/聚合,尽量不要使用继承…...

Scala04 方法与函数
Scala04 方法与函数 Scala 中的也有方法和函数的概念。 Scala中的 方法 是类的一部分。 Scala中的 函数 是一个对象,可以赋值给变量。 在类中定义的函数就是方法 4.1 方法 Scala 中方法 与 Java 中类似,是组成类的一部分 4.1.1 语法结构 格式&#x…...

XJTUSE专业课与实验指南(已经开源)
文章目录XJTUSE专业课与实验指南大一小学期大二上课程实验大二下课程实验大二小学期大三上课程实验大三下课程实验XJTUSE专业课与实验指南 github地址:https://github.com/yijunquan-afk/XJTUSE-NOTES.git 📄写在前面 1️⃣ 本篇文章仅供参考࿰…...

Spring面试专题
讲师:邓澎波 Spring面试专题 1.Spring应该很熟悉吧?来介绍下你的Spring的理解 1.1 Spring的发展历程 先介绍Spring是怎么来的,发展中有哪些核心的节点,当前的最新版本是什么等 通过上图可以比较清晰的看到Spring的各个时间版本…...

【truncate、delete和drop的6大区别!】
在MySQL中,truncate、delete和drop是三个常用的命令,它们可以用于删除表或表中的数据,下面是它们的六大区别: 语法不同: truncate和delete是SQL语句,drop是DDL(数据定义语言)语句。…...

如何入门Vue:掌握Vue的核心概念和基本用法
Vue是一种流行的JavaScript框架,它可以让开发者更容易地构建响应式的用户界面。Vue的设计理念是简单易懂,它的核心库只关注视图层,可以与其它库或现有项目很好地结合。在本文中,我将介绍Vue的基础概念和如何开始使用Vue。Vue的基本…...