java spring mvc处理器映射器介绍

目录
  • 一、RequestMappingHandlerMapping解析映射简单介绍
  • 二、@RequestMapping解析源码流程
  • 三、@RequestMapping映射源码流程
  • 四、@RequestMapping解析源码
  • 五、@RequestMapping映射源码

前言:

  • 本文源码基于spring-framework-5.3.10
  • mvcspring源码中的一个子模块!

一、RequestMappingHandlerMapping解析映射简单介绍

  • @RequestMapping通过RequestMappingHandlerMapping进行解析!
  • HandlerMapping是一个根据URL映射到Handler的方法。
  • RequestMappingHandlerMapping是HandlerMapping的一个子类!
  • RequestMappingHandlerMapping他的父类有InitializingBean,所有在spring启动实例化的时候会调用afterPropertiesSet()方法。解析逻辑就在这里。
  • RequestMappingHandlerMapping有俩个过程:解析、映射

二、@RequestMapping解析源码流程

  • 容器加载,调用RequestMappingHandlerMappingafterPropertiesSet()。
  • 调用父类的afterPropertiesSet()方法。
  • 调用initHandlerMethods()方法。
  • 循环每一个Bean,看方法上有@RequestMapping或者@Controller的Bean。
  • 解析HandlerMethods,进行封装RequestMappingInfo。
  • 将封装好的RequestMappingInfo存起来:key为路径,值为mapping(RequestMappingInfo)

三、@RequestMapping映射源码流程

  • 请求进来,调用getHandler方法。
  • 获取当前请求对应的HandlerMethod
  • 通过UrlPathHelper对象,用于来解析从们的request中解析出请求映射路径。
  • 更具路径去pathLookup中找。
  • 上面没找到,从所有的里面找有通配符的。
  • 找到多个进行排序,优先级:? > * > {} >** 。
  • 不为空拿到第一个返回。
  • 如果为空获取默认的。默认还是空的,直接返回null。
  • 封装拦截器,返回。

四、@RequestMapping解析源码

/**
 * 解析的开始位置。
 * 由于实现了InitializingBean,初始化Bean的时候调用这个方法。
 * 源码位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.afterPropertiesSet()
 */
public void afterPropertiesSet() {

	this.config = new RequestMappingInfo.BuilderConfiguration();
	this.config.setTrailingSlashMatch(useTrailingSlashMatch()); // 尾部斜杠
	this.config.setContentNegotiationManager(getContentNegotiationManager());

	if (getPatternParser() != null) {
		this.config.setPatternParser(getPatternParser());
		Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch,
				"Suffix pattern matching not supported with PathPatternParser.");
	}
	else {
		this.config.setSuffixPatternMatch(useSuffixPatternMatch());
		this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
		this.config.setPathMatcher(getPathMatcher());
	}

	// 调用父类的afterPropertiesSet方法
	super.afterPropertiesSet();
}

/**
 * 父类的afterPropertiesSet方法。
 * 源码位置:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.afterPropertiesSet()
 */
public void afterPropertiesSet() {
	initHandlerMethods();
}

/**
 * 解析@RequestMapping方法
 * 源码位置:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.initHandlerMethods()
 */
protected void initHandlerMethods() {
	// 获得所有候选beanName—— 当前容器所有的beanName
	for (String beanName : getCandidateBeanNames()) {
		// BeanName不是scopedTarget.开头的
		if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
			// *处理候选bean——即解析@RequestMapping和映射路径
			processCandidateBean(beanName);
		}
	}
	// 解析完所有@RequestMapping的时候调用
	handlerMethodsInitialized(getHandlerMethods());
}

/**
 * 处理候选bean——即解析@RequestMapping和映射路径
 * 源码位置:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.processCandidateBean(String)
 */
protected void processCandidateBean(String beanName) {
	Class<?> beanType = null;
	try {
		// 得到当前BeanName得到这个Bean的类型
		beanType = obtainApplicationContext().getType(beanName);
	}
	catch (Throwable ex) {
		// An unresolvable bean type, probably from a lazy bean - let's ignore it.
		if (logger.isTraceEnabled()) {
			logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
		}
	}
	// 这一步判断是关键:是否有Controller 或 RequestMapping注解
	if (beanType != null && isHandler(beanType)) {
		// 解析HandlerMethods
		detectHandlerMethods(beanName);
	}
}

/**
 * 解析HandlerMethods
 * 源码位置:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.detectHandlerMethods(Object)
 */
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 {
						// 根据Method得到Mapping映射
						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));
		}
		else if (mappingsLogger.isDebugEnabled()) {
			mappingsLogger.debug(formatMappings(userType, methods));
		}
		// 遍历每一个方法,这里是所有Bean的所有方法
		methods.forEach((method, mapping) -> {
			Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
			// pathLookup放入:key为路径,值为mapping(RequestMappingInfo)
			registerHandlerMethod(handler, invocableMethod, mapping);
		});
	}
}

/**
 * 根据Method得到Mapping映射
 * 源码位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.getMappingForMethod(Method, Class<?>)
 */
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
	// 如果方法上面有@RequestMapping:解析出RequestMappingInfo
	// RequestMappingInfo 是用来在请求的时候做匹对的
	RequestMappingInfo info = createRequestMappingInfo(method);
	if (info != null) {
		// 如果方法上面有@RequestMapping,看看类上面是不是有@RequestMapping
		RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
		// 类上面也有@RequestMapping  那就合并
		// 比如 类:/user  方法:/info 合并为 /user/info
		if (typeInfo != null) {
			info = typeInfo.combine(info);
		}

		// 合并前缀   5.1新增  默认null
		// 可通过 WebMvcConfigurer#configurePathMatch 进行定制
		String prefix = getPathPrefix(handlerType);
		if (prefix != null) {
			info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
		}
	}
	return info;
}

/**
 * 创建请求映射信息的外部逻辑
 * 源码位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.createRequestMappingInfo(AnnotatedElement)
 */
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
	// 获取RequestMapping注解信息
	RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
	// 获取请求调解:[可扩展], 如果有:该条件会在请求时匹对
	RequestCondition<?> condition = (element instanceof Class ?
			getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
	// 如果有RequestMapping注解,封装成RequestMappingInfo
	return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}

/**
 * 创建请求映射信息的内部逻辑
 * 源码位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.createRequestMappingInfo(RequestMapping, RequestCondition<?>)
 */
protected RequestMappingInfo createRequestMappingInfo(
		RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
	// 将@RequestMapping注解属性的值构建成一个 RequestMappingInfo
	RequestMappingInfo.Builder builder = RequestMappingInfo
			//构建路径
			.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
			//构建方法(get还是post等)
			.methods(requestMapping.method())
			//参数 对应http request parameter
			.params(requestMapping.params())
			//头部
			.headers(requestMapping.headers())
			//request的提交内容类型content type,如application/json, text/html
			.consumes(requestMapping.consumes())
			//指定返回的内容类型的content type,仅当request请求头中的(Accept)类型中包含该指定类型才返回
			.produces(requestMapping.produces())
			.mappingName(requestMapping.name());
	if (customCondition != null) {
		builder.customCondition(customCondition);
	}
	// 构造RequestMappingInfo:将上面的属性构建成一个个的RequestCondition对象方便在请求的时候组合匹对
	return builder.options(this.config).build();
}

/**
 * 得到所有的方法
 * 源码位置:org.springframework.core.MethodIntrospector.selectMethods(Class<?>, MetadataLookup<T>)
 */
public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
	final Map<Method, T> methodMap = new LinkedHashMap<>();
	Set<Class<?>> handlerTypes = new LinkedHashSet<>();
	Class<?> specificHandlerType = null;
	//获取原始的class对象
	if (!Proxy.isProxyClass(targetType)) {
		specificHandlerType = ClassUtils.getUserClass(targetType);
		handlerTypes.add(specificHandlerType);
	}
	//获取class的接口
	handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));
	//循环我们的class集合
	for (Class<?> currentHandlerType : handlerTypes) {
		final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);

		ReflectionUtils.doWithMethods(currentHandlerType, method -> {
			//获取具体的方法对象
			Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
			 /**回调 即解析@RequestMapping 返回RequestMappingInfo
			  * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#getMappingForMethod(java.lang.reflect.Method, java.lang.Class)*/
			T result = metadataLookup.inspect(specificMethod);
			if (result != null) {
				// 看看有没有桥接方法:泛型实现类jvm会自动生成桥接类
				Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
				if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
					//把方法对象作为key,RequestMappingInfo对象作为value保存到map中
					methodMap.put(specificMethod, result);
				}
			}
		}, ReflectionUtils.USER_DECLARED_METHODS);
	}

	return methodMap;
}

五、@RequestMapping映射源码

/**
 * 获取@RequestMapping映射
 * 源码位置:org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(HttpServletRequest)
 */
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	// 获取当前请求对应的HandlerMethod
	Object handler = getHandlerInternal(request);

	// 获取默认的handler
	if (handler == null) {
		handler = getDefaultHandler();
	}

	// 还是没有handler的时候返回null,404了
	if (handler == null) {
		return null;
	}
	// Bean name or resolved handler?
	// String类型?获取Bean
	if (handler instanceof String) {
		String handlerName = (String) handler;
		handler = obtainApplicationContext().getBean(handlerName);
	}

	// Ensure presence of cached lookupPath for interceptors and others
	if (!ServletRequestPathUtils.hasCachedPath(request)) {
		initLookupPath(request);
	}

	// 获取拦截器相关的调用链
	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

	if (logger.isTraceEnabled()) {
		logger.trace("Mapped to " + handler);
	}
	else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
		logger.debug("Mapped to " + executionChain.getHandler());
	}

	if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
		CorsConfiguration config = getCorsConfiguration(handler, request);
		if (getCorsConfigurationSource() != null) {
			CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
			config = (globalConfig != null ? globalConfig.combine(config) : config);
		}
		if (config != null) {
			config.validateAllowCredentials();
		}
		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
	}

	return executionChain;
}

/**
 * 获取当前请求对应的HandlerMethod
 * 源码位置:org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(HttpServletRequest)
 */
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
	request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
	try {
		// 直接调用父类的getHandlerInternal方法
		return super.getHandlerInternal(request);
	}
	finally {
		ProducesRequestCondition.clearMediaTypesAttribute(request);
	}
}
/**
 * 获取当前请求对应的HandlerMethod---父类的
 * 源码位置:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(HttpServletRequest)
 */
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
	// 通过UrlPathHelper对象,用于来解析从们的request中解析出请求映射路径
	String lookupPath = initLookupPath(request);
	this.mappingRegistry.acquireReadLock();
	try {
		// 通过lookupPath解析最终的handler——HandlerMethod对象
		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
		return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
	}
	finally {
		this.mappingRegistry.releaseReadLock();
	}
}

/**
 * 通过lookupPath解析最终的handler
 * 源码位置:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(String, HttpServletRequest)
 */
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	List<Match> matches = new ArrayList<>();
	// 根据uri从mappingRegistry.pathLookup获取 RequestMappingInfo
	// pathLookup<path,RequestMappingInfo>会在初始化阶段解析好
	List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
	if (directPathMatches != null) {
		// 如果根据path能直接匹配的RequestMappingInfo 则用该mapping进行匹配其他条件(method、header等)
		addMatchingMappings(directPathMatches, matches, request);
	}
	if (matches.isEmpty()) {
		// 如果无path匹配,用所有的RequestMappingInfo  通过AntPathMatcher匹配
		addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
	}
	if (!matches.isEmpty()) {
		// 选择第一个为最匹配的
		Match bestMatch = matches.get(0);
		/**
		 * 如果匹配到多个
		 @RequestMapping(value="/mappin?")
		 @RequestMapping(value="/mappin*")
		 @RequestMapping(value="/{xxxx}")
		 @RequestMapping(value="/**")
		 */
		if (matches.size() > 1) {
			//创建MatchComparator的匹配器对象
			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));

			/** 根据精准度排序  大概是这样的: ? > * > {} >**   具体可以去看:
			 * @see org.springframework.util.AntPathMatcher.AntPatternComparator#compare(java.lang.String, java.lang.String)*/
			matches.sort(comparator);

			// 排完序后拿到优先级最高的
			bestMatch = matches.get(0);
			if (logger.isTraceEnabled()) {
				logger.trace(matches.size() + " matching mappings: " + matches);
			}
			// 是否配置CORS并且匹配
			if (CorsUtils.isPreFlightRequest(request)) {
				for (Match match : matches) {
					if (match.hasCorsConfig()) {
						return PREFLIGHT_AMBIGUOUS_MATCH;
					}
				}
			}
			else {
				//获取第二最匹配的
				Match secondBestMatch = matches.get(1);
				//若第一个和第二个是一样的 抛出异常
				if (comparator.compare(bestMatch, secondBestMatch) == 0) {
					Method m1 = bestMatch.getHandlerMethod().getMethod();
					Method m2 = secondBestMatch.getHandlerMethod().getMethod();
					String uri = request.getRequestURI();
					throw new IllegalStateException(
							"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
				}
			}
		}
		//把最匹配的设置到request中
		request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
		handleMatch(bestMatch.mapping, lookupPath, request);
		//返回最匹配的
		return bestMatch.getHandlerMethod();
	}
	else { // return null
		return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
	}
}

到此这篇关于java spring mvc处理器映射器介绍的文章就介绍到这了,更多相关spring mvc处理器映射器内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringMVC接收java.util.Date类型数据的2种方式小结

    SpringMVC接收java.util.Date类型数据 在Controller中如下定义方法 public PassQueryRequest trade(@ModelAttribute PassQueryRequest tradeRequest, @RequestParam(value="startDate", required=true)Date startDate, @RequestParam(value="endDate", required=true)D

  • java SpringMvc中拦截器的应用

    目录 什么是拦截器 基本使用 实例 总结 什么是拦截器 拦截器(Interceptor)是SpringMVC中的组件.可以使很多个请求被处理时,都会执行拦截器中的代码.拦截器可以选择阻止执行或放行. 举个栗子: 基本使用 在SpringMVC中实现HandlerInteceptor拦截器接口,这个类就是一个拦截器类. 利用拦截器最核心的在用控制preHandle方法的返回值,返回true就成功了,返回false就表示进行拦截处理了. 实例 首先,创建一个类继承拦截器 public class D

  • Java SpringMVC实现自定义拦截器

    目录 SpringMVC实现自定义拦截器 1拦截器(interceptor)的作用 2拦截器和过滤器区别 3.实现过程 3.1创建拦截器类实现HandlerInterceptor接口 3.2配置拦截器 3.3测试拦截器的拦截效果 3.4编写jsp页面 3.5测试结果 4.拦截器链 5.知识小结 总结 SpringMVC实现自定义拦截器 1 拦截器(interceptor)的作用 Spring MVC 的拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理.

  • Java SpringMVC的自定义异常类

    目录 1.异常处理的思路 2.自定义异常处理器 3.web的处理异常机制 总结 1. 异常处理的思路 在java中,对于异常的处理一般有两种方式: 一种在当前方法捕获处理(try-catch),这种处理方式会造成业务代码和异常处理代码的耦合. 另一种是自己不处理,而是抛给调用者处理(throws),调用者在抛给它的调用者,也就是往上抛.这种方法的基础上,衍生除了SpringMVC的异常处理机制. 系统的dao.service.controller出现都通过throws Exception向上抛出

  • Java spring mvc请求详情介绍

    目录 一.源码执行流程 二.源码执行流程图 三.spring mvc中的一核心组件 四.源码分析 五.获取组件相关逻辑: 六.获取参数,执行方法源码分析 七.渲染视图逻辑 前言: 本文源码基于spring-framework-5.3.10. mvc是spring源码中的一个子模块! 一.源码执行流程 用户发送请求至前端控制器DispatcherServlet. DispatcherServlet收到请求调用处理器映射器HandlerMapping.处理器映射器根据请求url找到具体的处理器,生成

  • java spring mvc处理器映射器介绍

    目录 一.RequestMappingHandlerMapping解析映射简单介绍 二.@RequestMapping解析源码流程 三.@RequestMapping映射源码流程 四.@RequestMapping解析源码 五.@RequestMapping映射源码 前言: 本文源码基于spring-framework-5.3.10. mvc是spring源码中的一个子模块! 一.RequestMappingHandlerMapping解析映射简单介绍 @RequestMapping通过Requ

  • Java Spring MVC获取请求数据详解操作

    目录 1. 获得请求参数 2. 获得基本类型参数 3. 获得POJO类型参数 4. 获得数组类型参数 5. 获得集合类型参数 6. 请求数据乱码问题 7. 参数绑定注解 @requestParam 8. 获得Restful风格的参数 9. 自定义类型转换器 1.定义转换器类实现Converter接口 2.在配置文件中声明转换器 3.在<annotation-driven>中引用转换器 10. 获得Servlet相关API 11. 获得请求头 11.1 @RequestHeader 11.2 @

  • 详解Spring MVC的拦截器与异常处理机制

    目录 1.SpringMVC拦截器 1.1拦截器(interceptor)的作用 1.2拦截器和过滤器的区别 1.3拦截器的快速入门 1.4多拦截器操作 1.5拦截器方法说明 2.SpringMVC异常处理 2.1异常处理的思路 2.2异常处理的两种方式 2.3简单的异常处理器SimpleMappingExceptinResolver 2.4自定义异常处理步骤 总结 1. SpringMVC拦截器 1.1 拦截器(interceptor)的作用 Spring MVC 的拦截器类似于 Servle

  • Java Spring MVC 上传下载文件配置及controller方法详解

    下载: 1.在spring-mvc中配置(用于100M以下的文件下载) <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="messageConverters"> <list> <!--配置下载返回类型--> <bean class="or

  • Java Spring中Quartz调度器详解及实例

    一.Quartz的特点 * 按作业类的继承方式来分,主要有以下两种: 1.作业类继承org.springframework.scheduling.quartz.QuartzJobBean类的方式 2.作业类不继承org.springframework.scheduling.quartz.QuartzJobBean类的方式 注:个人比较推崇第二种,因为这种方式下的作业类仍然是POJO. * 按任务调度的触发时机来分,主要有以下两种: 1.每隔指定时间则触发一次,对应的调度器为org.springf

  • Spring MVC拦截器_动力节点Java学院整理

    Spring为我们提供了: org.springframework.web.servlet.HandlerInterceptor接口, org.springframework.web.servlet.handler.HandlerInterceptorAdapter适配器, 实现这个接口或继承此类,可以非常方便的实现自己的拦截器. 有以下三个方法:  Action之前执行: public boolean preHandle(HttpServletRequest request, HttpServ

  • Spring MVC入门_动力节点Java学院整理

    Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spring Web MVC也是要简化我们日常Web开发的.另外还有一种基于组件的.事件驱动的Web框架在此就不介绍了,如Tapestry.JSF等. Spring Web MVC也是服务到工作者模式的实现,但进行可优化.前端控制器是DispatcherServl

  • Java Spring Boot请求方式与请求映射过程分析

    目录 请求方式 非Rest风格 使用Rest风格 表单提交 过程分析 请求映射过程分析 请求方式 Spring Boot支持Rest风格:使用HTTP请求方式的动词来表示对资源的操作 非Rest风格 以前这样写接口: /getUser 获取用户 /deleteUser 删除用户 /updateUser 修改用户 /saveUser 保存用户 @RequestMapping(value = "/getUser", method = RequestMethod.GET) public St

  • Java经典面试题汇总:Spring MVC

    目录 1. 什么是Spring MVC ? 2. Spring MVC 有哪些组件? 3. 说一下 Spring MVC 运行流程? 4. Spring MVC的优点: 5. @RequestMapping 的作用是什么? 6. 如果在拦截请求中,我想拦截get方式提交的方法,怎么配置? 7. SpringMVC常用的注解有哪些? 8. SpingMvc中的控制器的注解一般用那个,有没有别的注解可以替代? 9. Spring MVC和Struts2的区别有哪些? 10. 怎么样在方法里面得到Re

随机推荐