SpringMVC源码解析
一:springmvc运行过程:
1. dispatcherServlet 通过 HandlerMapping 找到controller
2. controller经过后台逻辑处理得到结果集modelandview
3. 视图解析器解析model,渲染view展示页面。
二:springmvc容器是什么:
很多人喜欢把spring和springmvc混为一谈, 其实它们是完全不同的两个概念。spring主要是创建对像管理依赖的; 而springmvc是一种mvc分层思想将功能模块化,处理用户请求的。而且spring和springmvc是有父子容器关系存在的。子容器可以获取父容器中的对象(可以理解成面向对象中的继承),例如controller通过@Autowired注入spring中的对象, 而如果把controller交给spring来管理,就会出现404异常,因为spring初始化只管理bean,并不会把url和method关联起来。
三:springmvc案例解析:
3.1 自定义springmvc注解@MyController和@MyRequestMapping
3.2 实现过程
public class MyDispatcherServlet extends HttpServlet {
// springmvc 容器对象 key:类名id ,value bean对象
private ConcurrentHashMap<String, Object> springmvcBeans = new ConcurrentHashMap<String, Object>();
// springmvc 容器对象 keya:请求地址 ,vlue 类对象
private ConcurrentHashMap<String, Object> urlBeans = new ConcurrentHashMap<String, Object>();
// springmvc 容器对象 key:请求地址 ,value 方法名称
private ConcurrentHashMap<String, String> handlermapping = new ConcurrentHashMap<String, String>();
/**
* 容器启动时
*/
@Override
public void init() throws ServletException {
// 1. 扫描当前包(及子包)下面的所有类
List<Class<?>> classes = ClassUtil.getClasses("org.wulei.controller");
try {
// 将这些类放到mvc容器
this.findClassMVCAnnotation(classes);
} catch (Exception e) {
}
// 将url和方法绑定起来
this.headlerMapping();
} /* 初始化有controller注解的bean,放入mvc容器。*/
public void findClassMVCAnnotation(List<Class<?>> classes)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
for (Class<?> classInfo : classes) {
// 2. 判断类上是否有加上controller注解
MytController myController = classInfo.getDeclaredAnnotation(MytController.class);
if (myController != null) {
// 默认bean名称是类名小写
String beanName = ClassUtil.toLowerCaseFirstOne(classInfo.getSimpleName());
// 3. 实例化对象 , 放入mvc容器
Object object = ClassUtil.newInstance(classInfo);
springmvcBeans.put(beanName, object);
}
}
} /* 通过handlerMapping将url和method关联起来 */
public void headlerMapping() {
// 4. 遍历mvc的bean容器所有的类
for(Map.Entry<String, Object> mvcBean : springmvcBeans.entrySet()) {
Object object = mvcBean.getValue();
Class<? extends Object> classInfo = object.getClass();
// 5. 判断类上面是否有url映射(即:@RequestMapping注解)
MyRequestMapping requestMapping = classInfo.getDeclaredAnnotation(MyRequestMapping.class);
// 6. 获取类上的url地址
String baseUrl = "";
if(requestMapping != null) {
baseUrl = requestMapping.value();
}
// 7. 获取方法上的url映射路径
Method[] methods = classInfo.getDeclaredMethods();
if(methods.length>0) {
for(Method method : methods) {
MyRequestMapping methodRequestMapping = method.getDeclaredAnnotation(MyRequestMapping.class);
if(methodRequestMapping != null) {
// 8. 将 classUrl 和 methodUrl 拼接起来,得到完整url路径。
String methodUrl = baseUrl+methodRequestMapping.value();
// 9. 将url和method绑定起来,存入容器。
urlBeans.put(methodUrl, object);
handlermapping.put(methodUrl, method.getName());
/**
* 到上面容器就就初始化完成,可以对外提供服务了。
* 现在可以每个方法断点调试看运行过程。
*/
}
}
}
}
} /**
* 处理用户请求
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 获取请求url地址
String url = req.getRequestURI();
if(StringUtils.isEmpty(url)) return;
// 2. 通过url获取容器中的controller对象
Object object = urlBeans.get(url);
if(object == null) {
resp.getWriter().println("ERROR: not found 404 url");
return;
}
// 3. 使用url获取方法.
String methodName = handlermapping.get(url);
if(StringUtils.isEmpty(methodName)) resp.getWriter().println("ERROR: not found method");
// 4. java反射获取被调用的方法
String resultPage = null;
try {
Method method = object.getClass().getMethod(methodName);
resultPage = (String) method.invoke(object);
resp.getWriter().println(resultPage);
} catch (Exception e) {
e.printStackTrace();
}
// 5.调用视图转换器渲染给页面展示
this.myResourceViewResolver(resultPage, req, resp);
}
/**
* 视图解析器
*/
private void myResourceViewResolver(String pageName, HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
// 根路径
String prefix = "/";
String suffix = ".jsp";
req.getRequestDispatcher(prefix + pageName + suffix).forward(req, res);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
3.3 测试 (正确的url会跳转到相应页面,错误的url会找不到路径)
四:源码解析
4.1 springmvc初始化阶段
4.1.1 我们在用springmvc的时候, 首先会配置 dispatcherServlet,它会拦截符合规则的url请求。
<servlet>
<servlet-name>WEB</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定springmvc扫描文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/spring-*.xml</param-value>
</init-param>
<!-- 优先级 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<!-- 拦截规则 -->
<servlet-name>WEB</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
4.1.2 可以看出 dispatcherServlet 最终还是继承于httpservlet, 说明它也会满足servlet的工作原理。
Servlet 生命周期:
Servlet 加载—>实例化—>服务—>销毁。
init():在Servlet的生命周期中,仅执行一次init()方法。负责初始化Servlet对象。
service():负责响应客户的请求。每次请求都会调用相应的doGet/doPost功能。
destroy():仅执行一次,在服务器端停止且卸载Servlet时执行该方法,负责释放占用的资源。
4.1.3 HttpServletBean 重写了 httpservlet 的 init()方法, 通过initServletBean() 方法来初始化bean, 具体实现在子类FrameworkServlet中。
/**
* Map config parameters onto bean properties of this servlet, and
* invoke subclass initialization.
* @throws ServletException if bean properties are invalid (or required
* properties are missing), or if subclass initialization fails.
*/
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
} // 初始化DespatcherService的配置参数
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
} // 让子类来初始化bean
initServletBean(); if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
4.1.4 FrameworkServlet重写了initServletBean(),并在这个方法中调用了initWebApplicationContext()方法
protected WebApplicationContext initWebApplicationContext() {
// 获取 ContextLoaderListener 初始化
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
// 将 Spring 的容器设为 SpringMVC 容器的父容器
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
wac = findWebApplicationContext();
}
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// dispatcherServlet 重写这个方法
onRefresh(wac);
}
if (this.publishContext) {
// 发布这个容器到 ServletContext 中
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
4.1.5 dispatcherServlet 重写onRefresh(),调用initHandlerMappings将url和method关联起来
/**
* Initialize the HandlerMappings used by this class.
* <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
* we default to BeanNameUrlHandlerMapping.
*/
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null; if (this.detectAllHandlerMappings) {
// 获取HandlerMapping实例
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// 排序
OrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
} // Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
4.2 springmvc运行阶段
4.2.1 上面已经说过每次请求都会执行servlet的service()方法,FrameworkServlet重写了这个方法。
/**
* Override the parent class implementation in order to intercept PATCH
* requests.
*/
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取请求方式例如 get / post
String method = request.getMethod();
if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
4.2.2 然后会根据请求方式去httpsrevlet中调用具体的方法。
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException { String method = req.getMethod();
// get请求
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// Invalid date header - proceed as if none was set
ifModifiedSince = -1;
}
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
// head请求
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
// post请求
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
// put请求
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp); } else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp); } else {
// 以上都不满足, 说明请求方式有问题。
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
4.2.3 FrameworkServlet重写了这些方法,并最终都是通过 processRequest( ) 方法来处理请求的。
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { processRequest(request, response);
} /**
* Delegate POST requests to {@link #processRequest}.
* @see #doService
*/
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { processRequest(request, response);
} /**
* Delegate PUT requests to {@link #processRequest}.
* @see #doService
*/
@Override
protected final void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { processRequest(request, response);
}
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { long startTime = System.currentTimeMillis();
Throwable failureCause = null;
// 获取当前请求的相关信息
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request); RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
// 初始化 ContextHolders
initContextHolders(request, localeContext, requestAttributes); try {
// 最终处理是dispatcherServlet 中的doService()方法调用 doDispatch()来处理的
doService(request, response);
}
4.2.4 doDispatch() 方法的主要过程是通过 HandlerMapping 获取 Handler,再找到用于执行它的 HandlerAdapter,执行 Handler 后得到 ModelAndView
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try {
ModelAndView mv = null;
Exception dispatchException = null; try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request); // 获取处理请求的具体方法
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
} // 获取处理请求的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 检查请求路径
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
} // 得到modelandview
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) {
return;
} applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
// 处理请求结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
4.2.5 跳转页面
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { boolean errorView = false;
// 有异常就直接抛出异常
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
} // 所有条件都符合就跳转页面
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
SpringMVC源码解析的更多相关文章
- SpringMVC源码解析- HandlerAdapter - ModelFactory(转)
ModelFactory主要是两个职责: 1. 初始化model 2. 处理器执行后将modle中相应参数设置到SessionAttributes中 我们来看看具体的处理逻辑(直接充当分析目录): 1 ...
- springMVC源码解析--ViewResolver视图解析器执行(三)
之前两篇博客springMVC源码分析--ViewResolver视图解析器(一)和springMVC源码解析--ViewResolverComposite视图解析器集合(二)中我们已经简单介绍了一些 ...
- SpringMVC源码解析- HandlerAdapter - ModelFactory
ModelFactory主要是两个职责: 1. 初始化model 2. 处理器执行后将modle中相应参数设置到SessionAttributes中 我们来看看具体的处理逻辑(直接充当分析目录): 1 ...
- springMVC源码解析--ViewResolverComposite视图解析器集合(二)
上一篇博客springMVC源码分析--ViewResolver视图解析器(一)中我们介绍了一些springMVC提供的很多视图解析器ViewResolver,在开发的一套springMVC系统中是可 ...
- springMVC源码解析--HandlerMethodArgumentResolverComposite参数解析器集合(二)
上一篇博客springMVC源码分析--HandlerMethodArgumentResolver参数解析器(一)中我们已经介绍了参数解析相关的东西,并且也提到了HandlerMethodArgume ...
- springmvc源码解析MvcNamespaceHandler之<mvc:view-resolvers>
说在前面 本次主要介绍springmvc配置解析. springmvc配置解析 本次介绍MvcNamespaceHandler. 进入到这个方法org.springframework.web.serv ...
- 深入了解SpringMVC源码解析
Spring MVC源码解析 Spring MVC的使用原理其实是通过配置一个Servlet来接管所有的请求,所有的请求由这个Servlet来进行分发处理. 我们可以从web.xml里面看出这一点 & ...
- SpringMVC源码解析- HandlerAdapter初始化
HandlerAdapter初始化时,主要是进行注解解析器初始化注册;返回值处理类初始化;全局注解@ControllerAdvice内容读取并缓存. 目录: 注解解析器初始化注册:@ModelAttr ...
- springmvc源码解析-初始化
1. 概述 对于Web开发者,MVC模型是大家再熟悉不过的了,SpringMVC中,满足条件的请求进入到负责请求分发的DispatcherServlet,DispatcherServlet根 ...
随机推荐
- SDRAM学习笔记
摘自“开源骚客视频教程” 1.仲裁模块就是用来控制什么时候读.写.刷新 2.模块中的状态机 3.初始化时序图说明,来自“IS42S116160.pdf”文件 4.SDRAM写时序图,来自“IS42S1 ...
- pycharm问题集锦
1.pycharm的激活方式 参考博客https://blog.csdn.net/u014044812/article/details/78727496 2.总是出现波浪线,如下问题 问题原因:knn ...
- Ubuntu16.04 同时连接无线网络和以太网端口
背景: 激光雷达VLP16通过以太网线连接电脑.在本博客所述的设置之前,一旦连接以太网线,本机(dell笔记本)的无线网络立即断开,即无法同时连接无线网络和以太网端口. 问题查找: 命令行 $ ip ...
- 从源码编译安装PCL并运行第一个小例子
如何通过源码编译方式安装PCL 对于很多想学习PCL的同学而言,往往会被如何安装困扰很长时间.我就是这其中的一员,为了不让大家在安装问题上浪费太多时间,我决心写下这篇小小的随笔,希望对大家有所帮助. ...
- Luogu P5469 [NOI2019]机器人 (DP、多项式)
不用FFT的多项式(大雾) 题目链接: https://www.luogu.org/problemnew/show/P5469 (这题在洛谷都成绿题了海星) 题解: 首先我们考虑,一个序列位置最右边的 ...
- 深入浅说服务如何以Jar包的方式发布
序言 笔者前段时间在使用自研框架NF( 传送门 )开发一个自动模板生成工具之后,想将他发布到Linux下,之前一直使用IDE直接run as运行,在遇到发布的时候考虑过发布为war或者jar,在一番抉 ...
- SQL server 安装教程
双击sql server 2008的.exe安装文件,进入[SQL Server 安装中心]. 点击界面左侧的[安装],然后点击右侧的[全新SQL Server 独立安装或向现有安装添加功能],进 ...
- 第四周学习总结&实验报告
学习总结; String类: 1.方法只会开辟一块堆内存空间,且会自动保存在对象池中以供下次重复使用: 2方法会开辟两块堆内存空间,其中一块会成为垃圾空间. 1."=="比的是地址 ...
- 一、基础篇--1.1Java基础-Exception、Error、RuntimeException与一般异常有何异同
Throwable.Error.Exception.RuntimeException 关系如下类图所示: Throwable: Throwable类是java语言中所有错误或者异常的超类.它的两个子类 ...
- SpringMVC中mvc:view-controller的使用
1.重定向 <mvc:view-controller path="/" view-name="redirect:/admin/index"/> 即如 ...