Spring MVC 解读——@RequestMapping
为了降低文章篇幅,使得文章更目标化,简洁化,我们就不例举各种@RequestMapping的用法等内容了.
文章主要说明以下问题:
-
Spring怎样处理@RequestMapping(怎样将请求路径映射到控制器类或方法)
-
Spring怎样将请求分派给正确的控制器类或方法
-
Spring如何实现灵活的控制器方法的
在Spring MVC 3.1 之前的版本中,Spring默认使用 DefaultAnnotationHandlerMapping,AnnotationMethodHandlerAdapter来处理 @RequestMapping注解和请求方法调用,而从3.1开始提供了一组新的API完成这些工作。相比之下,新的API更加的合理完善,开放,易拓 展,面向对象。这篇文章便是基于3.1的新API进行剖析的。
一、概念解析
在开始之前我们先了解下新的API中引入的新接口或者类,这会有助于后面的处理过程的理解。不得不说新的API提供了更多漂亮的抽象,你能感受到面向对象的魅力。
-
RequestMappingInfo 这个类是对请求映射的一个抽象,它包含了请求路径,请求方法,请求头等信息。其实可以看做是@RequestMapping的一个对应类。
-
HandlerMethod 这个类封装了处理器实例(Controller Bean)和 处理方法实例(Method)以及方法参数数组(MethodParameter[])
-
MethodParameter 这个类从2.0就有了,它封装了方法某个参数的相关信息及行为,如该参数的索引,该参数所属方法实例或构造器实例,该参数的类型等。
-
HandlerMapping 该接口的实现类用来定义请求和处理器之前的映射关系,其中只定义了一个方法getHandler。
-
AbstractHandlerMethodMapping 这是HandlerMapping的一个基本实现类,该类定义了请求与HandlerMethod实例的映射关系。
-
RequestMappingInfoHandlerMapping 这个是AbstractHandlerMethodMapping的实现类,他维护了一个RequestMappingInfo和HandlerMethod的Map属性。
-
RequestMappingHandlerMapping 这个是RequestMappingInfoHandlerMapping的子类,它将@RequestMapping注解转化为RequestMappingInfo实例,并为父类使用。也就是我们处理@RequestMapping的终点。
-
InitializingBean 这个接口定义了其实现Bean在容器完成属性设置后可以执行自定义初始化操作,我们的AbstractHandlerMethodMapping便实现了这个接口,并且定义了一组自定义操作,就是用来检测处理我们的@RequestMapping注解。
概念讲的太多总不是什么好事。但明白了上述概念基本上就成功一半了,其中的实现相对@Autowired那篇简单多了。
二、InitialiZingBean.afterPropertySet()
我们从头开始,看看到底Spring是怎样检测并处理我们@RequestMapping注解的。不知大家还记不记的这段代码:
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
这是BeanFactory创建Bean过程中需要执行的一段代码,其中populateBean方法便是@Autowired注解的处理过程,执行的属性的自动注入等操作。因为initializeBean方法当时与主题无关没有讲,不过这时它便是我们关注的焦点了。(上一篇@Autowired 详解)
上面概念中我们讲到InitiaizingBean接口,它的实现Bean会在容器完成属性注入后执行一个自定义操作,这不就满足initializeBean方法的执行唤醒嘛,我们来看它的实现:
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
invokeAwareMethods(beanName, bean);
return null;
}
}, getAccessControlContext());
}
else {//这里检测当前Bean是否实现一些列Aware接口,并调用相关方法,我们不关心。
invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {//BeanPostProcessor 的回调,不关心
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
invokeInitMethods(beanName, wrappedBean, mbd);//这是我们需要关心的,下面看下它的实现
}
if (mbd == null || !mbd.isSynthetic()) {//BeanPostProcessor 的回调,不关心
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
我们接着来看下invokeInitMethods方法的实现:
protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
throws Throwable {
//是否是InitializingBean的实例
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean &&
(mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
if (System.getSecurityManager() != null) {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
public Object run() throws Exception {//利用系统安全管理器调用
((InitializingBean) bean).afterPropertiesSet();
return null;
}
}, getAccessControlContext());
}
}
else {//调用InitializingBean的afterPropertiesSet方法。
((InitializingBean) bean).afterPropertiesSet();
}
}
//调用自定义初始化方法。。。省略,不关心
}
上一篇关于<mvc:annotation-driven/>的文章,我们说过了,当在配置文件中加上该标记后,Spring(3.1后)会默认为我们注册RequestMappingHandlerMapping
等Bean定义。而RequestMappingHandlerMapping
实现了InitializingBean接口,因此,在初始化并装配该Bean实例时,执行到上述代码是,便会执行他的afterPropertySet方法。我们接下来看看他的afterPropertySet方法:
public void afterPropertiesSet() {
initHandlerMethods();
}
//Scan beans in the ApplicationContext, detect and register handler methods.
protected void initHandlerMethods() {
//扫描所有注册的Bean
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(),
Object.class) : getApplicationContext().getBeanNamesForType(Object.class));
//遍历这些Bean,依次判断是否是处理器,并检测其HandlerMethod
for (String beanName : beanNames) {
if (isHandler(getApplicationContext().getType(beanName))){
detectHandlerMethods(beanName);
}
}
//这个方法是个空实现,不管他
handlerMethodsInitialized(getHandlerMethods());
}
它直接调用了initHandlerMethods()方法,并且该方法被描述为:扫描ApplicationContext中的beans,检测并注册处理器方法。we are close。
三、检测@RequestMapping
我们再看它是怎样判断是否是处理器的,以及怎么detect Handler Methods 的:
@Override
protected boolean isHandler(Class<?> beanType) {
return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
(AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
}
啊哈,很简单,就是看看有没有被@Controller或者@RequestMapping注解标记
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = (handler instanceof String) ?
getApplicationContext().getType((String) handler) : handler.getClass();
final Class<?> userType = ClassUtils.getUserClass(handlerType);
Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter(){
public boolean matches(Method method) {//只选择被@RequestMapping标记的方法
return getMappingForMethod(method, userType) != null;
}
});
for (Method method : methods) {
//根据方法上的@RequestMapping来创建RequestMappingInfo实例。
T mapping = getMappingForMethod(method, userType);
//注册请求映射
registerHandlerMethod(handler, method, mapping);
}
}
整个的检测过程大致清楚了:1)遍历Handler中的所有方法,找出其中被@RequestMapping注解标记的方法。2)然后遍历这些方法,生成RequestMappingInfo实例。3)将RequestMappingInfo实例以及处理器方法注册到缓存中。
下面我们看看细节:
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = null;
//获取方法method上的@RequestMapping实例。
RequestMapping methodAnnotation =
AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (methodAnnotation != null) {//方法被注解了
RequestCondition<?> methodCondition = getCustomMethodCondition(method);//始终返回null
info = createRequestMappingInfo(methodAnnotation, methodCondition);//创建MappingInfo
//检查方法所属的类有没有@RequestMapping注解
RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType,
RequestMapping.class);
if (typeAnnotation != null) {//有类层次的@RequestMapping注解
RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);//null
//将类层次的RequestMapping和方法级别的RequestMapping结合
info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info);
}
}
return info;
}
很清晰吧,先获取方法上的@RequestMapping信息,然后获取类级别上的@RequestMapping 信息,然后将两者结合,这里我们有必要再了解下怎样创建RequestMappingInfo对象的(包括他的内部结构),以及怎样将类级别的request mapping信息和方法级别的进行结合的?
private RequestMappingInfo createRequestMappingInfo(RequestMapping annotation,
RequestCondition<?> customCondition) {
return new RequestMappingInfo(
new PatternsRequestCondition(annotation.value(), getUrlPathHelper(), getPathMatcher(),
this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions),
new RequestMethodsRequestCondition(annotation.method()),
new ParamsRequestCondition(annotation.params()),
new HeadersRequestCondition(annotation.headers()),
new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),
new ProducesRequestCondition(annotation.produces(), annotation.headers(),
getContentNegotiationManager()),
customCondition
);
}
其中涉及到了几个类,我们大致了解下含义:
-
PatternRequestCondition 它其实就是URL模式的封装,它包含了一个URL模式的Set集合。其实就是@RequestMapping注解中的value值得封装。
-
RequestMethodRequestCondition 它是@RequestMapping 注解中method属性的封装
-
ParamsRequestCondition 它是@RequestMapping注解中params属性的封装
等等,依次类推。因此RequestMappingInfo其实就是对@RquestMapping 的封装。
下面我们再看看怎样进行Combine的:
public RequestMappingInfo combine(RequestMappingInfo other) {
PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);
RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);
ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);
HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition);
ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition);
ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);
RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);
return new RequestMappingInfo(patterns, methods, params, headers, consumes,
produces, custom.getCondition());
}
很清晰,对每一个元素都进行combine操作,我们这里只看PatternRequestCondition是怎么结合的,就是看看怎样合并url的。其他没太大必要。
public PatternsRequestCondition combine(PatternsRequestCondition other) {
Set<String> result = new LinkedHashSet<String>();
if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) {
for (String pattern1 : this.patterns) {
for (String pattern2 : other.patterns) {
result.add(this.pathMatcher.combine(pattern1, pattern2));
}
}
}
else if (!this.patterns.isEmpty()) {
result.addAll(this.patterns);
}
else if (!other.patterns.isEmpty()) {
result.addAll(other.patterns);
}
else {
result.add("");
}
return new PatternsRequestCondition(result, this.urlPathHelper, this.pathMatcher,
this.useSuffixPatternMatch,this.useTrailingSlashMatch, this.fileExtensions);
}
1)两个pattern都存在是,调用PathMatcher的combine方法合并两个pattern。
2)只有一个有时,使用这个。
3)两个都没有时,为空“”。
现在真正的url拼接是由PathMatcher来完成的了。我们就不看他的代码了就是一串if else的组合,重点是考虑进各种情况,我们来看下方法的注释吧:
清晰,全面吧,有兴趣的可以看一下代码,这里不讲了。
四、注册请求映射
上面我们已经讲了@RequestMapping的检测和处理,并且根据@RequestMapping生成了RequestMappingInfo实例,那Spring必定需要将这些信息保存起来,以处理我们的请求。
第三节中我们提到一个方法还没有分析,就是registerHandlerMethod 方法:
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
HandlerMethod handlerMethod;
if (handler instanceof String) {
String beanName = (String) handler;
handlerMethod = new HandlerMethod(beanName, getApplicationContext(), method);
}
else {
handlerMethod = new HandlerMethod(handler, method);
}
//上面几行是根据新的处理器实例,方法实例,RequestMappingInfo来生成新的HandlerMethod实例
//下面是从缓存中查看是否有存在的HandlerMethod实例,如果有并且不相等则抛出异常
HandlerMethod oldHandlerMethod = handlerMethods.get(mapping);
if (oldHandlerMethod != null && !oldHandlerMethod.equals(handlerMethod)) {
throw new IllegalStateException();
}
//handlerMethods 是一个Map键是RequestMappingInfo对象,值是HandlerMethod实例
//因此一个HandlerMethod实例可能处理多个mapping,而一个mapping实例只能由一个method处理
this.handlerMethods.put(mapping, handlerMethod);
//这里获取mapping实例中的所有url。
Set<String> patterns = getMappingPathPatterns(mapping);
for (String pattern : patterns) {
if (!getPathMatcher().isPattern(pattern)) {
//urlMap也是Map,键是url 模式,值是RequestMappingInfo实例
//因此一个mapping实例可能对应多个pattern,但是一个pattern只能对应一个mapping实例
this.urlMap.add(pattern, mapping);
}
}
}
这里可能稍微有点绕,其实道理很简单,当请求到达时,去urlMap中需找匹配的url,以及获取对应mapping实例,然后去handlerMethods中获取匹配HandlerMethod实例。
相关推荐
《Spring MVC之@RequestMapping详解》 在Java Web开发中,Spring MVC框架因其强大的功能和灵活性而备受青睐。在处理HTTP请求时,@RequestMapping注解扮演着至关重要的角色,它负责将客户端的请求映射到控制器中的...
在Spring MVC框架中,`@RequestMapping`注解是核心组件之一,它用于处理HTTP请求映射,使得控制器类(Controller)中的方法能够与特定的URL路径关联起来。本篇文章将深入探讨`@RequestMapping`的使用、功能以及相关...
在Spring MVC框架中,`@RequestMapping`、`@RequestParam`和`@PathVariable`是三个非常重要的注解,它们用于处理HTTP请求并绑定请求参数到控制器方法的参数上。接下来,我们将详细探讨这三个注解的工作原理以及如何...
在Spring MVC框架中,`@RequestMapping`注解是核心组件之一,它用于处理HTTP请求映射,使得控制器类或方法能够与特定的URL路径关联。本文将深入解析`@RequestMapping`的工作原理,并介绍一个简化版的自定义实现,...
在SpringMybatis项目中,利用`@RequestMapping`和`RequestAttribute`实现登录注册功能是Web开发中的常见实践。`@RequestMapping`是Spring MVC框架中用于处理HTTP请求映射的注解,而`RequestAttribute`则用于在请求...
在Spring MVC框架中,`@RequestMapping` 和 `@RequestParam` 是两个非常重要的注解,它们在处理HTTP请求和参数绑定方面起着核心作用。本文将深入探讨这两个注解的使用和功能。 `@RequestMapping` 是Spring MVC用于...
总结起来,`@RequestMapping`是Spring MVC中实现请求映射的关键工具,它允许开发者灵活地根据URL、请求方法、参数和头信息来映射处理逻辑。而`HiddenHttpMethodFilter`则解决了在非RESTful环境中模拟其他HTTP方法的...
1. 配置Spring MVC:在`web.xml`中配置DispatcherServlet,并在`servlet-context.xml`中配置相关的MVC组件。 2. 创建Controller:定义一个包含`@RequestMapping`注解的Controller类,处理特定的HTTP请求。 3. 编写...
。。Java S11pring MVC 学习笔记:@RequestMapping用法详解.docx
。。Java S11pring MVC 学习笔记:@RequestMapping用法详解.pdf
Spring MVC的@RequestMapping注解是核心的控制器层注解,它用于映射HTTP请求到特定的处理方法。在本文中,我们将深入探讨这个注解的各个方面,包括它的使用场景、属性以及如何结合其他注解实现更复杂的请求处理。 ...
Spring MVC 中的 RequestMapping 6个基本用法小结 Spring MVC 是一个基于 Java 的 Web 应用程序框架,提供了强大的 RequestMapping 机制来处理 HTTP 请求。在 Spring MVC 中,RequestMapping 是一个核心组件,负责...
在Spring MVC框架中,`@RequestMapping`注解是核心组件之一,它负责将HTTP请求映射到控制器类的处理方法。这个注解可以应用于类级别和方法级别,以定义请求的URL路径、HTTP方法以及其他的匹配条件。接下来,我们将...
1.创建第一个 Spring MVC 程序案例 2.Spring MVC @RequestMapping 注解案例 3.Spring MVC 请求参数的获取案例 4.Spring MVC 域对象共享数据案例 5.Spring MVC @ModelAttribute 注解案例 6.Spring MVC 国际化案例 7....
**Spring MVC —— 第一个应用程序** Spring MVC 是 Spring 框架的一个模块,主要用于构建 Web 应用程序。它提供了一种模型-视图-控制器(MVC)架构,简化了开发过程,使得开发者可以专注于业务逻辑而不必过于关心...
1. **依赖注入**:Spring MVC 4.0继续支持Spring框架的核心功能,依赖注入(DI),允许开发者通过配置来管理对象及其依赖关系,降低了代码耦合度,提高了可测试性。 2. **ModelAndView对象**:在处理完请求后,控制...
02@RequestMapping映射.md
Spring 容器管理的 Bean,所以在这里 @Controller 注解起到了标识该类为 Spring MVC 控制器的作用。同时,它还暗示了该 Bean 的作用域,通常默认为 Singleton(单例)。而 @RequestMapping 注解则用来定义请求映射,...
Spring MVC 是一个强大的Java Web开发框架,它是Spring框架的一部分,专为构建高度可扩展和模块化的Web应用程序而设计。在2015年的版本中,Spring MVC 4已经相当成熟,提供了许多特性来简化开发流程并提高开发效率。...