一、什么是SpringMVC?
SpringMVC是Spring的一个基于MVC三层架构模式的Web应用框架,通过把Model,View,Controller分离,把较为复杂的web应用分成逻辑清晰的几部分,是为了简化开发,减少出错。还是为了组内开发人员之间的配合。总之就是一种分层工作的办法。拥有spring的特性,例如依赖注入。其实简单的来说就是为Java程序提供一个“五星级酒店”,把请求看做是“出入人员”,进出都由最前面的“酒店前台”来控制(具体细节在下面介绍)。SpringMVC框架是以请求为驱动,围绕Servlet设计,将请求发给控制器,然后通过模型对象,分派器来展示请求结果视图。其中核心类是DispatcherServlet,它是一个Servlet,顶层是实现的Servlet接口。
二、SpringMVC工作原理详解
2.1、SpringMVC工作原理详解(图解)
2.2、SpringMVC工作原理详解(文解)
- 用户发送请求至前端控制器DispatcherServlet(也叫中央处理器)【酒店前台:用户来到酒店入住】
- DispatcherServlet收到请求调用HandlerMapping处理器映射器 【酒店前台等到用户选好房间由工作人员(HandlerMapping)带用户去看具体的房间(Handler)】
- 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet 【工作人员带用户找到具体房间看完之后,将用户信息及需求返回给前台】
- DispatcherServlet调用HandlerAdapter处理器适配器 【前台通知酒店服务人员】
- HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)【服务人员根据前台给的信息找到具体房间,根据用户需求进行服务(烧好热水、打扫等)】
- Controller执行完成返回ModelAndView 【服务人员服务完房间之后把效果(ModelAndView)拍照】
- HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet【酒店工作人员把服务人员拍的照片带给前台】
- DisPatcherServlet将ModelAndView传给ViewReslover视图解析器 【前台把照片发给审核人员】
- ViewReslover解析后返回具体View 【审核人员按照用户需求筛选具体的照片并将审核过的照片发送给前台】
- DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)【前台根据审核人员返回的照片对用户进行介绍并安排用户入住】
- DispatcherServlet响应用户 【用户住完,找到前台退房,前台送走用户】
2.3、SpringMVC工作组件介绍
- 前端控制器DispatcherServlet(不需要工程师开发),由框架提供
作用:接收请求,响应结果,相当于转发器,中央处理器。有了dispatcherServlet减少了其它组件之间的耦合度。
用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。 - 处理器映射器HandlerMapping(不需要工程师开发),由框架提供
作用:根据请求的url查找Handler
HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。 - 处理器适配器HandlerAdapter
作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。 - 处理器Handler(需要工程师开发)
注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler
Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。
由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。 - 视图解析器View resolver(不需要工程师开发),由框架提供
作用:进行视图解析,根据逻辑视图名解析成真正的视图(view)
View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。
一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由工程师根据业务需求开发具体的页面。 - 视图View(需要工程师开发jsp…)
View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf…)
2.4、核心架构的具体流程步骤如下:
1、首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
2、DispatcherServlet——>HandlerMapping, HandlerMapping 将会把请求映射为HandlerExecutionChain 对象(包含一个Handler 处理器(页面控制器)对象、多个HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
3、DispatcherServlet——>HandlerAdapter,HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
4、HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView 对象(包含模型数据、逻辑视图名);
5、ModelAndView的逻辑视图名——> ViewResolver, ViewResolver 将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;
6、View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;
7、返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。
下边两个组件通常情况下需要开发:
Handler:处理器,即后端控制器用controller表示。
View:视图,即展示给用户的界面,视图中通常需要标签语言展示模型数据。
MVC原理图
分析:
M-Model 模型(完成业务逻辑:有javaBean构成,service+dao+entity)
V-View 视图(做界面的展示 jsp,html……)
C-Controller 控制器(接收请求—>调用模型—>根据结果派发页面)
三、分析SpringMVC源码
在上面我们了解了,接下来我们分析咱们的springmvc框架,首先看下DispatcherServlet源码
package org.springframework.web.servlet;
@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";
public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";
public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";
public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";
public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";
public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";
public static final String FLASH_MAP_MANAGER_BEAN_NAME = "flashMapManager";
public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class.getName() + ".CONTEXT";
public static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".LOCALE_RESOLVER";
public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_RESOLVER";
public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE";
public static final String INPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".INPUT_FLASH_MAP";
public static final String OUTPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".OUTPUT_FLASH_MAP";
public static final String FLASH_MAP_MANAGER_ATTRIBUTE = DispatcherServlet.class.getName() + ".FLASH_MAP_MANAGER";
public static final String EXCEPTION_ATTRIBUTE = DispatcherServlet.class.getName() + ".EXCEPTION";
public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
private static final Properties defaultStrategies;
static {
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
}
}
/** Detect all HandlerMappings or just expect "handlerMapping" bean? */
private boolean detectAllHandlerMappings = true;
/** Detect all HandlerAdapters or just expect "handlerAdapter" bean? */
private boolean detectAllHandlerAdapters = true;
/** Detect all HandlerExceptionResolvers or just expect "handlerExceptionResolver" bean? */
private boolean detectAllHandlerExceptionResolvers = true;
/** Detect all ViewResolvers or just expect "viewResolver" bean? */
private boolean detectAllViewResolvers = true;
/** Throw a NoHandlerFoundException if no Handler was found to process this request? **/
private boolean throwExceptionIfNoHandlerFound = false;
/** Perform cleanup of request attributes after include request? */
private boolean cleanupAfterInclude = true;
/** MultipartResolver used by this servlet */
private MultipartResolver multipartResolver;
/** LocaleResolver used by this servlet */
private LocaleResolver localeResolver;
/** ThemeResolver used by this servlet */
private ThemeResolver themeResolver;
/** List of HandlerMappings used by this servlet */
private List handlerMappings;
/** List of HandlerAdapters used by this servlet */
private List handlerAdapters;
/** List of HandlerExceptionResolvers used by this servlet */
private List handlerExceptionResolvers;
/** RequestToViewNameTranslator used by this servlet */
private RequestToViewNameTranslator viewNameTranslator;
private FlashMapManager flashMapManager;
/** List of ViewResolvers used by this servlet */
private List viewResolvers;
public DispatcherServlet() {
super();
}
public DispatcherServlet(WebApplicationContext webApplicationContext) {
super(webApplicationContext);
}
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
}
DispatcherServlet类中的属性beans:
HandlerMapping:用于handlers映射请求和一系列的对于拦截器的前处理和后处理,大部分用@Controller注解。
HandlerAdapter:帮助DispatcherServlet处理映射请求处理程序的适配器,而不用考虑实际调用的是 哪个处理程序。
HandlerExceptionResolver:处理映射异常。
ViewResolver:根据实际配置解析实际的View类型。
LocaleResolver:解决客户正在使用的区域设置以及可能的时区,以便能够提供国际化视野。
ThemeResolver:解决Web应用程序可以使用的主题,例如提供个性化布局。
MultipartResolver:解析多部分请求,以支持从HTML表单上传文件。
FlashMapManager:存储并检索可用于将一个请求属性传递到另一个请求的input和output的FlashMap,通常用于重定向。
在Web MVC框架中,每个DispatcherServlet都拥自己的WebApplicationContext,它继承了ApplicationContext。WebApplicationContext包含了其上下文和Servlet实例之间共享的所有的基础框架beans。
HandlerMapping:
HandlerMapping接口处理请求的映射
HandlerMapping接口的实现类:
SimpleUrlHandlerMapping类通过配置文件把URL映射到Controller类。
DefaultAnnotationHandlerMapping类通过注解把URL映射到Controller类。
HandlerAdapter:
HandlerAdapter接口-处理请求映射:
AnnotationMethodHandlerAdapter:通过注解,把请求URL映射到Controller类的方法上。
HandlerExceptionResolver:
HandlerExceptionResolver:
HandlerExceptionResolver接口-异常处理接口
SimpleMappingExceptionResolver通过配置文件进行异常处理。
AnnotationMethodHandlerExceptionResolver:通过注解进行异常处理。
ViewResolver:
ViewResolver接口解析View视图。
UrlBasedViewResolver: 通过配置文件,把一个视图名交给到一个View来处理。
其实通过上面的分析我们可以发现,springMVC的核心就是DispatcherServlet,由DispatcherServlet去调用各类组件,而DispatcherServlet其实本质上就是一个Servlet(所有WEB层框架本质上都离不开Servlet,就好比ORM框架离不开JDBC),所以说我下面写一个迷你版的来加深大家对SpringMVC的理解。
四、像大佬一样手写一个迷你版
4.1、分析SpringMVC在日常中的使用
配置springmvc-servlet.xml
分析:在这个文件中最重要的其实是配置扫包和视图解析器,而这俩个操作都是在程序启动时配置的。
配置web.xml
Archetype Created Web Application
SpringMVC
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
/WEB-INF/springmvc-servlet.xml
1
SpringMVC
/
分析:在web.xml中配置DispatcherServlet,而tomcat启动时会先去加载web.xml,加载到web.xml时因为当中配置了DispatcherServlet,所以此时回去加载DispatcherServlet,而DispatcherServlet在上面的讲解中会发现它除了是组件调用器时,还是springmvc的初始化构建器。
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
4.2、手写实操
定义DispatcherServlet继承HttpServlet:
public class DispacherServlet extends HttpServlet {
/***
* 1. 定义一个处理器来接收用户的请求
* 2. 配置扫描包路径
* 3. 定义注解
* 4. 处理用户请求
* 遍历Controller类,找到所有RequestMapper注解的类或者方法保存起来,下次用户访问时直接去容器中找
*/
Map HandlerMap;
@Override
public void init() throws ServletException {
System.out.println("项目启动了.....");
//扫描包路径 原本是xml配置中读取的
String packagePath = "com.sixstar.mvc.controller";
//根据注解扫描包
Set> ControllerClassSet = ClassUtil.scanPackageByAnnotation(packagePath, Controller.class);
System.out.println("ControllerClassSet:" + ControllerClassSet.size());
HandlerMapping handlerMapping = new HandlerMapping();
HandlerMap = handlerMapping.urlMapping(ControllerClassSet);
System.out.println("HandlerMap的长度:" + HandlerMap.size());
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//得到客户端请求路径
StringBuffer requestURL = req.getRequestURL();
System.out.println("客户端请求路径:" + requestURL);
//判断客户端请求路径中是否包含项目名称,包含的话使用空字符替换掉
String path = new String(requestURL).replace("http://" + req.getServerName() + ":" + req.getServerPort(), "");
System.out.println("客户端请求RequestMapping映射路径:" + path);
//去容器中使用path查找对应的InvocationHandler,得到对应的Class和Method
InvocationHandler handler = HandlerMap.get(path);
Object object = handler.getObject();
Method method = handler.getMethod();
//判断该方法上是否添加了ResponseBody注解 true:返回json格式 false:跳转页面
boolean f = method.isAnnotationPresent(ResponseBody.class);
System.out.println("方法上是否添加了ResponseBody注解:" + f);
if (f){
try {
//通过反射的方式调用方法
Object invoke = method.invoke(object);
//将执行后返回的结果通过Response以json的格式写给客户端
resp.getWriter().print(invoke.toString()); //使用阿里巴巴的json转换包转换成json返回,此处使用toString代替
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} else{
String URL = "http://" + req.getServerName() + ":" + req.getServerPort() + "/" + req.getContextPath();
System.out.println("URL:" + URL);
//使用自定义的前后缀拼接,然后使用response或者request跳转
String prefix = "";
String suffix = ".jsp";
try {
Object invoke = method.invoke(object);
if(invoke instanceof ModelAndView){
} else{
String str = (String)invoke;
if(str.indexOf("forward:") != -1){
System.out.println("转发");
req.getRequestDispatcher("index.jsp").forward(req,resp);
}
if(str.indexOf("redirect:") != -1){
resp.sendRedirect(URL + prefix + str.replace("redirect:","") + suffix);
}
if(str.indexOf("forward:") == -1 && str.indexOf("redirect:") == -1){
resp.sendRedirect(URL + prefix + str + suffix);
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
在web.xml加载DispacherServlet
Archetype Created Web Application
dispacherServlet
com.mvc.DispacherServlet
1
dispacherServlet
/
自定义注解
Controller:
@Retention(RetentionPolicy.RUNTIME) //注解的生命周期:运行时期有效
@Target(ElementType.TYPE) //注解的生效范围:生效于类上面
public @interface Controller {
//@interface 元注解:JDK封装的专门用来实现自定义注解的注解
}
RequestMapping:
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME) //注解的生命周期:运行时期有效
public @interface RequestMapping {
String value() default ""; //允许该注解可以填String类型的参数,默认为空
}
ResponseBody:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) //注解的生命周期:运行时期有效
public @interface ResponseBody {
}
HandlerMapping:
public class HandlerMapping {
public Map urlMapping(Set> ControllerClassSet){
//初始化MAP集合
HashMap HandlerHashMap = new HashMap<>();
//遍历Controller集合
for (Class> aClass : ControllerClassSet) {
//获取aClass类上的RequestMapping注解的值
String requestMappingClassValue = AnnotationUtil.getAnnotationValue(aClass, RequestMapping.class);
System.out.println("requestMappingClassValue:" + requestMappingClassValue);
Method[] declaredMethods = aClass.getDeclaredMethods();
System.out.println("该controller类中方法数量为:" + declaredMethods.length);
if (declaredMethods != null && declaredMethods.length != 0) {
for (Method declaredMethod : declaredMethods) {
boolean flag = declaredMethod.isAnnotationPresent(RequestMapping.class);
if (flag){
//获取方法上RequestMapping注解的值
String requestMappingMethodAnnotationValue = AnnotationUtil.getAnnotationValue(declaredMethod, RequestMapping.class);
//判断得到的值是否为空
String requestMappingMethodValue = requestMappingMethodAnnotationValue == null || requestMappingMethodAnnotationValue.equals("") || requestMappingMethodAnnotationValue.equals("null") ? "" : requestMappingMethodAnnotationValue;
System.out.println("requestMappingMethodValue:" + requestMappingMethodValue);
//将得到的值封装成InvocationHandler对象
try {
InvocationHandler invocationHandler = new InvocationHandler(aClass.newInstance(), declaredMethod);
HandlerHashMap.put(requestMappingClassValue + requestMappingMethodValue, invocationHandler);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
return HandlerHashMap;
}
}
InvocationHandler:
public class InvocationHandler {
private Object object;
private Method method;
public InvocationHandler(){}
public InvocationHandler(Object object, Method method) {
this.object = object;
this.method = method;
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
@Override
public String toString() {
return "InvocationHandler{" +
"object=" + object +
", method=" + method +
'}';
}
}
测试:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/get")
@ResponseBody
public User get(){
return new User(1,"竹子爱熊猫","男",18);
}
@RequestMapping("/test")
@ResponseBody
public String test(){
return "test";
}
@RequestMapping("/edit")
public String toEdit(){
return "redirect:edit";
}
public String TEST(){
return null;
}
}
五、总结
最后谈谈我自己对于springMVC工作流程的理解,其实在咱们把一个JavaWeb程序打成war包丢入Tomcat后,当我们启动Tomcat,它就会先去加载web.xml文件,而加载web.xml配置文件时会碰到DispacherServlet需要被加载,当加载DispacherServlet时其实就是把SpringMVC的组件初始化和所有Controller的URL资源全部映射到一个容器中存储,然后当请求经过DispacherServlet时,DispacherServlet就去装URL的容器里面找到这个请求的URL资源,找到之后再调用组件去执行具体的Controller,当执行完毕之后结果又会回到DispacherServlet,此时DispacherServlet又会去调用相关组件处理执行后的结果,最后才将渲染后的结果响应。
六、面试中注意的点
如果在面试中遇到了面试官问你SpringMVC的工作原理(流程),如果按照最开始我们分析的那一套走下来其实面试官也清楚,你可以对于这个框架的理解程度不够,可能是在面试前刷了一下面试题靠临时记忆来面试的,这在面试中其实没有给你带来任何加分点和给面试官看到亮点,因为对于他来说,每天都有人来面试,同一个这样的问题他可能问过很多人,而你们的答案都是相同的,面试官也不是傻子。所以如果要想去真正的应对面试靠刷面试题还是不够,更多的还是要看自己对于技术的理解程度,还有你的思维逻辑,对于WEB框架其实本质是Servlet,而一般的流程就是“发送请求 -- 相关处理 -- 响应数据”。