SpringMVC是一种基于Java实现的MVC设计模式的轻量级Web框架,它是Spring框架的一部分,主要用于简化Web开发。 SpringMVC采用MVC架构模式,将应用程序分为模型(Model)、视图(View)和控制器(Controller)三个层次,从而实现业务逻辑、数据和界面的分离。模型层负责处理数据,视图层负责展示数据,而控制器层则负责根据输入调用相应的模型和视图。
SpringMVC的优点包括结构松散、易于扩展和与Spring框架无缝集成。它支持多种视图技术,如JSP,并且各个模块分离,耦合度低,使得系统更加灵活和易于维护。在开发过程中,SpringMVC通过前端控制器DispatcherServlet来接收请求,通过HandlerMapping查找相应的处理器(Controller),然后通过适配器(Adapter)调用控制器方法,最终返回ModelAndView对象,由视图解析器解析并渲染视图,最终向用户响应结果。
springmvc与servlet的关系:Springmvc对servlet进行了封装,servlet的性能较好,springmvc的开发效率较高。
HandlerExecutionChain mappedHandler=getHandler(processedRequest,false);
HandlerAdapter ha=getHandlerAdapter(mappedHandler.getHandler);
ModelAndView mv=ha.handle(processedRequest,response,mappedHandler.getHandler());
1、用户通过浏览器发送url请求(如http://localhost:8080/spring-mvc/queryItems.action),前端控制器DispatcherServlet接收到用户发送的url请求,通过DispatcherServlet类中的doService方法调用doDispatch方法处理请求。
2、在doDispatch方法中调用getHandler方法,进而调用处理器映射器HandlerMapping的getHandler方法,根据url请求获取处理器执行链HandlerExecutionChain,其中包括能处理url请求的handler(例如此处处理映射路径为/queryItems.action的handler)和多个HandlerInterceptor拦截器。
3、在doDispatch方法中调用DispatcherServlet对象的getHandlerAdapter方法获取能够执行handler的处理器适配器HandlerAdapter对象。在doDispatch方法中调用HandlerAdapter对象的handle方法执行handler,并返回ModelAndView对象给前端控制器。ModelAndView对象中存储了用户请求的数据,以及显示数据的视图。通过ModelAndView对象的addObject方法将用户请求的数据存储到model属性(实际上是Map)中,通过ModelAndView对象的setViewName方法或构造器将要显示数据的逻辑视图存储到view属性中。
4、在doDispatch方法中调用DispatcherServlet对象的processDispatchResult方法,在processDispatchResult方法中调用DispatcherServlet对象的render方法,在render方法中调用DispatcherServlet对象的resolveViewName方法,进而调用视图解析器ViewResolver对象的resolveViewName方法,通过视图解析器ViewResolver将逻辑视图名拼接得到物理视图view,即前缀prefix+逻辑视图名+后缀suffix。
5、前端控制器得到物理视图view后,将模型数据填充到request域,进行视图渲染,并将渲染结果输出给Response对象。在DispatcherServlet对象的render方法中调用视图view对象的render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)方法,该方法的职责就是接收model对象、Request对象、Response对象,并渲染输出结果给Response对象。其中,在view对象的render方法中调用view对象的renderMergedOutputModel方法,接着调用view对象的exposeModelAsRequestAttributes方法,该方法没有被子类重写,所以调用的是父类AbstractView的方法,即调用被传递到了AbstractView类的exposeModelAsRequestAttributes方法中,就是在这个方法中,模型数据被一个一个的放入到了HttpServletRequest对象中。
6、前端控制器将响应Response对象返回给用户,用户可以看到请求的页面。
在后端开发过程中,开发人员只需要编写controller层和model层(包括service层和mapper层)代码,view层的代码是由前端开发人员编写的(在前后端分离之前,view层的代码是通过后端人员编写的jsp文件实现的)。
jsp与servlet有什么区别?
答:jsp与servlet差不多,都可以加载动态页面。jsp是在HTML页面中加入java代码,侧重于视图,servlet是在java代码中加入HTML标签,侧重于业务逻辑,jsp完成的功能,都可以通过servlet来完成。
1、前端控制器DispatcherServlet源码
2、ModelAndView
public class ModelAndView {
private Object view;//该属性用来存储返回的视图信息
private ModelMap model;//该属性用来存储处理后的结果数据
private boolean cleared = false;
public ModelAndView() {
}
public ModelAndView(String viewName) {
this.view = viewName;
}
public ModelAndView(View view) {
this.view = view;
}
public void setViewName(String viewName) {
this.view = viewName;
}
public String getViewName() {
return (this.view instanceof String ? (String) this.view : null);
}
public void setView(View view) {
this.view = view;
}
public View getView() {
return (this.view instanceof View ? (View) this.view : null);
}
/*如果视图是通过名称指定的,并且要由DispatcherServlet通过ViewResolver解析,则返回true*/
public boolean isReference() {
return (this.view instanceof String);
}
......
}
3、视图解析器
在Spring MVC 中,controller不会负责具体的页面渲染,它仅仅是调用业务逻辑并返回model数据给view层,至于view层具体怎么用HTML展现,由专门的view层具体负责,这就是MVC模式,业务层与展示层是松耦合的。那么,Spring MVC是如何解耦合请求处理逻辑和页面渲染的呢?controller在处理业务逻辑之后会返回一个逻辑view的字符串,那么Spring MVC是怎么根据这个逻辑view名找到真正的物理view页面呢?这个工作就由Spring的视图解析器ViewResolvers负责。
对于普通的JSP页面,最常用到的view resolver就是InternalResourceViewResolver,它有两个属性,一个是匹配物理view的前缀,一个是后缀。前缀一般就是view页面的路径位置,后缀就是文件的格式,而前缀后缀之间的就是逻辑view名称。
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
比如按照上面的配置,如果controller返回的逻辑view名称是home的话,InternalResourceViewResolver会根据这个逻辑view名home找到其对应的实际物理view:/WEB-INF/views/home.jsp。
@Controller
@RequestMapping(value = "/")
public class HomeController {
@RequestMapping(method= RequestMethod.GET)
public String home() {
return "home";
}
}
ViewResolver相关接口
ViewResolver接口:
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}
这个接口很简单,当我们传入一个逻辑view名和Locale对象,就会返回一个View实例。
View接口:
public interface View {
String getContentType();
void render(Map<String, ?> var1, HttpServletRequest var2, HttpServletResponse var3) throws Exception;
}
View接口的职责就是接收model对象、Request对象、Response对象,并渲染结果输出给Response对象。如果我们要自己写一个ViewResolver也很简单,我们只需要实现ViewResolver和View两个接口,在View接口中把需要渲染的内容输出到Response对象中去就可以了。
4、将模型model中的数据放到request中
作为存储模型数据以及视图名称的ModelAndView对象会在DispatcherServlet中被取出,然后DispatcherServlet会先把模型数据存储在request对象中,接着通过视图解析器转发到具体的视图上。
虽然Model是个接口,不过我们并不需要去实现Model接口,只需要在方法参数上进行声明,SpringMVC就会自动帮我们把Model对象传递过来,然后调用相应的方法存储数据即可。
代码示例:
package org.zero01.test;
import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class Test {
@RequestMapping("/test.do")
public String testModel(Model model) {
model.addAttribute("name","Jon");
model.addAttribute("age","15");
model.addAttribute("address","USA");
return "index";
}
}
我们来看一下详细的执行过程,看看模型数据最后是否真的会被存储在request对象中。在以上代码中的 return "index"; 那一行打个断点,然后通过debug运行。如下,我们可以看到,在DispatcherServlet的doDispatch方法中视图名称以及模型数据是存储在ModelAndView对象中的,而不是Model中:
而ModelAndView对象中的模型数据则会被存储在HttpServletRequest对象中,所以我们才可以在视图上直接获取结果数据。这一点我们也可以通过debug来看到:
1.首先ModelAndView对象被拿出来之后,就会调用processDispatchResult方法,将ModelAndView对象传递到该方法中进行处理:
2.在processDispatchResult方法中,如果ModelAndView对象不为空的话,就会调用render方法,并把ModelAndView对象传递过去:
3.在render方法中,会把ModelAndView对象中的模型数据拿出来,传递到View对象中的render方法中(这个View的实现类是AbstractView):
4.在view对象中的render方法中,会把模型数据传递到createMergedOutputModel方法中进行合并:
5.在createMergedOutputModel方法中会把几个数据合并到一个集合里,但是这里除了model之外其他都为空,所以只合并了model数据:
在控制台中可以看到mergedModel对象里的数据如下:
6.得到mergedModel对象后,继续往下执行,接着就会调用renderMergedOutputModel方法,把mergedModel、request以及response对象都传递过去:
7.但是renderMergedOutputModel是一个抽象方法,所以该方法的调用被传递到了它的一个子类中(该子类是InternalResourceView),这个子类实现的renderMergedOutputModel方法中调用了exposeModelAsRequestAttributes方法并把模型数据和request对象传递了过去:
8.而exposeModelAsRequestAttributes方法没有被子类重写,所以调用的是父类的,也就是AbstractView类的,所以调用被传递到了AbstractView类的exposeModelAsRequestAttributes方法中。就是在这个方法中,模型数据被一个一个的放入到了HttpServletRequest对象中:
我们可以来看看将模型数据添加到request对象中的具体过程:
第一个数据:
控制台:
第二个数据:
控制台:
第三个数据:
控制台:
以上的一系列复杂的流程走完之后,我们在视图中,才可以直接使用EL表达式进行拿值:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Test</title>
</head>
<body>
<div>
<p>name::
<span>${requestScope.name}</span>
</p>
<p>age::
<span>${requestScope.age}</span>
</p>
<p>address::
<span>${requestScope.address}</span>
</p>
</div>
</body>
</html>
浏览器访问结果:
5、拦截器
过滤器中的chain.doFilter(request, response);这个方法的调用作为分水岭。事实上调用Servlet的doService()方法是在这个方法中进行的。
拦截器与过滤器的区别:
两者都是AOP编程思想的实现,都能够实现权限控制和日志记录等问题的处理,但是两者粒度不同,拦截对象不一样。
1、适用范围不同:Filter是servlet的规范,只能用于web程序,但是拦截器可以用于application等程序。
2、规范不同:Filter是servlet的规范。但是Interceptor是spring容器支撑,有spring框架支持。
3、使用资源不一样:spring的拦截器由于依赖spring,也是spring的一个组件,因此能够在拦截器中使用spring的任何资源和对象。例如service对象,数据源,事务管理等,通过ioc注入拦截器即可,而filter不能。
4、粒度不同:Filter只能在servlet的前后起作用,而拦截器能在方法前后和异常前后执行,更加灵活,粒度更小,spring框架程序优先使用拦截器。