该博客仅为本人学习时笔记记录。不能保证没有错误,请结合自己思想参考。
项目源码:
github地址:https://github.com/JYG0723/springmvcpractice/tree/master
业务流程:
SpringMVC的整体模块架构:
分析:
1. 由最先的HTPP发送请求,由所配置的XML中的DispatcherServlet处理。
DispatcherServlet接受到这个请求后,根据请求的信息及HandlerMapping的配置找到处理请求的处理器Handler(Controller类)。
得到HandlerMapping对应的Handler后,通过HandlerAdapter对Handler进行封装,再以统一的适配器接口调用Handler。
处理器完成业务逻辑的处理后返回一个ModelAndView给DispatcherServlet。
ModelAndView包含逻辑视图名,而非真正的视图对象,DispatcherServlet借由ViewResolver完成逻辑视图名到真实视图对象的解析工作。
当得到真实View对象后,DispatcherServlet就对这个View对象进行渲染。
最终返回到客户的HTTP响应。
前端控制器
Mvc 是开发 Web 应用程序的通用的架构方式
用户请求页面后台实现流程:
1. 首先用户端的请求通过 http 协议到达了前端控制器。
2. 前端控制器了解这个请求应当被谁来处理,所以将这个请求代理给了具体的控制器。
3. 控制器了解业务逻辑细节,所以调用业务逻辑生成了业务数据。并且业务数据返回给了前端控制器。
4. 前端控制器再将接受到的数据分发给业务视图,由业务视图最终呈现用户页面。再将呈现好的用户页面返回给前端控制器,并最终将这个页面返回给浏览器端。此时用户就可以看到了请求页面。
以医院看病为例。前端控制器扮演者 分诊台 的作用,而其他具体 Controller 控制器扮演者各个科室的角色。我们到了医院分诊台会根据我们具体的病情安排我们到什么科室去就诊
所以可以明白:MVC的核心思想是业务数据抽取同业务数据呈现相分离
Mvc概念
Model + View + Controllerr
View(视图层):为用户提供UI,重点关注数据的呈现
Model(模型层):业务数据的信息表示,关注支撑业务的信息构成。
Controller(控制层):调用业务逻辑产生合适的Model,同时传递数据给视图层用于呈现。
静态概念
SpringMvc 中前端控制器具体的实现是 DispatcherServlet。用户请求到达DispatcherServlet 进行分发到达具体的 Controller。
HandlerAdapter:HandlerAdapter 是 DispatcherServlet 内部使用的一个类。其实也就是 Controller 的一个表现形式。因为 DispatcherServlet 不能直接识别每个具体的 Controller 而 HandlerAdapter 却可以,他用了Java的适配器模式 可以将不同的 Controller 适配成 DispatcherServlet 可以使用的 Handler。这样 DispatcherServlet 就可以很轻松的调用原来的 Controller。
HandlerInterceptor:为我们的Controller加一些料。是一个接口,如果配置了这个类,并提供了实现。就可以在调用 Controller 之前,调用之后,以及 Model 呈现到 View 之后 可以做很多事情。它有三个实现方法
- preHandle
- postHandle
- afterCompletion
HandlerMapping:基于 annotation。Handler 是请供求从前段控制器到 Controller 的一个中间过度类。所以 HandlerMapping 的作用就是告诉 DispatcherServlet 这个请求到达哪个 Controller。同时它也会知道这个Controller包裹了哪些 HaandlerInterceptor 将一个执行链条交给 HandlerExecutionChain。
HandlerExecutionChain:HandlerMapping返回过来的数据包括,Controller 和 Interceptor 形成的执行链条。然后首先去 执行 Interceptor 的 preHandler,然后去调用 Controller 某个业务方法。然后去掉用 postHandlr 最后调用 afterCompletion。
ModelAndView:ModelAndView 是 Springmvc 中对 Model 的一种表现形式。Springmvc中 还有其他的类比如 Model,或者 Java 中的 Map 来实现 Model 的功能。Controller 中使用的 Model 或者 Map 这样的类在 DispatcherServlet 中都会将其转发成 ModelAndView。所以它可以看成一个 Model 的具体表现
ViewResolver:视图解析器。会告诉 DispatcherServlet 你需要用哪个视图来进行视图的呈现,根据配置来找出那一个需要的视图对象。并将 ModelAndView的数据传递给 View。
View:负责呈现页面。
第一个SpringMVC程序
- 首先需要一个 Servlet 和一个 ServletMapping 将前段控制器配置到web.xml中。
web.xml:
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--DispatcherServlet对应的上下文配置,默认为/WEB-INF/$servlet-name$-servlet.xml
-->
<init-param><!--这里配置了contextConfigLocation 修改了配置的位置-->
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/configs/spring/mvc-dispatcher-servlet.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
- 然后根据 web.xml 文件中配置的 DispatcherServlet 对应的上下文配置文件创建 mvc-dispatcher-servlet.xml。
<context:annotation-config/>
<context:component-scan base-package="nuc.jyg.controller">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 开启HandlerMapping -->
<mvc:annotation-driven/>
<!-- 配置视图解析器,告诉DispatcherServlet将用哪个 viewResolver 来获取view -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/><!-- viewResolver -->
<property name="prefix" value="/WEB-INF/jsps/"/>
<property name="suffix" value=".jsp"/>
</bean>
- 最后再创建具体的 Controller 来负责接收用户端请求。
@Controller
@RequestMapping("/hello")
public class HelloMvcController {
@RequestMapping("/mvc")
public String helloMvc() {
return "home";//home.jsp
}
}
配置文件解析
+ WebApplicationContext(s):spring配置文件生成的上下文。它由ContextLoaderListener加载生成。他为所有应用提供了一些公共的组件和服务,Service层Dao层等,这些服务被整个 应用所共享。所以不会局限在一个DispatcherServlet中。
+ WebApplicationContext:与特定的DispatcherServlet相关的上下文。与其相关的一些有 Controller HandlerMapping ViewResolver等。
+ 可以有多个DispatcherServlet。针对不同的群体 分发来自这些不同群体的不同请求。两个群体所请求的服务不同,根据每个DispatcherServlet 不同的urlPattern映射路径来对应具体的DispatcherServlet进行群体区分并详细分发。
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Archetype Created Web Application</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/configs/spring/applicationContext*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--DispatcherServlet对应的上下文配置,默认为/WEB-INF/$servlet-name$-servlet.xml
-->
<init-param><!--这里配置了 contextConfigLocation 修改了配置的位置-->
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/configs/spring/mvc-dispatcher-servlet.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
- 该web.xml文件是maven默认生成的。它运用了一个webapp2.3的标准,这个标准下jsp页面会默认关闭EL表达式语言。所以如果希望将其打开的话可以使用2.4版本对其替换掉
- listener:加入了 spring 的声明
- context-param:spring 的配置文件的路径
mvc-dispatcher-servlet.xml:
<!-- 激活Spring annotation的DI。
比如@Controller可能会用到一些其他的服务来调用业务逻辑
,这里它需要依赖注入的方式来获取这些服务,这个标签的配置就完成了这项功能。-->
<context:annotation-config/>
<!-- DispatcherServlet配置上下文。只需要管理@Controller类型的Bean。
忽略其他类型的bean,如@Service。其他类型的Bean交给Spring上下文管理 -->
<context:component-scan base-package="nuc.jyg.controller">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- HandlerMapping,无需配置,Spring MVC可以默认启动。
DefaultAnnotationHandlerMapping 这个类可以去解析一些基于注解的annotationMapping
所以不需要对HandlerMapping做一些配置
-->
<!--扩充了 注解驱动
可以将请求参数绑定到控制器参数-->
<mvc:annotation-driven/>
<!-- 如果没有这项配置 将没有办法获得静态资源
这里它将 resource 映射到了本地资源 resource下,该目录下一般放置了一些css js 图片等
-->
<mvc:resources mapping="/resources/**" location="/WEB-INF/resources/"/>
<!-- 配置 ViewResolver
可以用多个 ViewResolver
可以使用 Order 属性来进行排序
InternalResourceViewResolver 需要放在最后 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<!-- 给 Controller 中分发出来的jsp的视图名称加上 路径的前缀和后缀
这样视图解析器才能正确的 找到每个视图并将ModelAndView数据传输过去 -->
<property name="prefix" value="/WEB-INF/jsps/"/>
<property name="suffix" value=".jsp"/>
</bean>
层次化的上下文:
spring最外层,然后向内细分。配置springmvc特定的配置文件。
+ context:annotation-config:激活Spring annotation的DI。比如@Controller可能会用到一些其他的服务来调用业务逻辑,这里它需要依赖注入的方式来获取这些服务,这个标签的配置就完成了这项功能。
+ context:component-scan:上面的配置文件中这样配置,是因为该配置文件属于DispatcherServlet配置上下文。只需要管理@Controller类型的Bean。忽略其他类型的bean,如@Service。其他类型的Bean交给Spring上下文管理
+ mvc:annotation-driven:扩展了注解驱动。是注解功能更加丰富。可以将请求参数中的参数直接绑定到映射方法的输入参数中
+ mvc:resources:如果没有这项配置 将没有办法获得静态资源这里它将 resource 映射到了本地资源 resource下,该目录下一般放置了一些css js 图片等
+ bean class=”org.springframework.web.servlet.view.InternalResourceViewResolver:配置 ViewResolver,可以用多个 ViewResolver,可以使用 Order 属性来进行排序。InternalResourceViewResolver 需要放在最后
applicationContext.xml:
<!-- 启动基于annotation的DI管理 -->
<context:annotation-config/>
<!-- 关闭对@Controller注解类的管理,因为已经交由下面的SpringMVC去管理了 -->
<context:component-scan base-package="nuc.jyg">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
- context:annotation-config:spring容器中的各式各样的bean 也会用到其他的服务来调用业务逻辑。也需要通过依赖注入的方式调用这些服务,所以也需要配置这个标签。
- context:component-scan:这里开启注解扫描标签。同时排除掉已经在dispatchServlet配置文件中 扫描过的 @Controller 的注解。提升程序性能
Binding
Controller:
@Controller
@RequestMapping("/hello")
public class HelloMvcController {
@RequestMapping(value = "/mvc", params = "add", method = RequestMethod.GET)
public String helloMvc() {
return "/course/home2";
}
}
- params:请求的参数限定。即请求中必须携带该请求参数
- return “/course/home2”:返回的jsp视图的映射路径。根据dispatchServlet配置文件的 资源文件的映射路径的配置 以及此处的详细映射路径的指定 可以找到具体的视图。
Controller:
//@RequestMapping(value = "/mvc/{id}",method = RequestMethod.GET)
@RequestMapping(value = "/mvc", params = "username", method = RequestMethod.GET)
public String helloMvc(HttpServletRequest httpServletRequest, Model model) {
logger.info("param1" + httpServletRequest.getRequestURI() + "|| param2" + httpServletRequest.getRequestURL());
logger.info(httpServletRequest.getParameter("username"));
logger.info(httpServletRequest.getContextPath());
model.addAttribute(httpServletRequest);
return "/course/home2";
}
@RequestMapping(value = "/save", method = RequestMethod.POST)
public String doSave(User user) {
//如果这里的映射方法中需要接受表单中完整 User模型对象的数据。可以直接在参数中加上一个User对象。
// 这样就必须保证前台表单中的input标签的name属性的值要和User对象中的属性名一致。这样就完成了数据绑定
//如果这里不是user对象,而是String username,String password。也一样,要想实现数据绑定前台input标签中的name属性也要是username和password
user.setUsername("li4");
return "redirect:mvc?username=" + user.getUsername();
return "redirect:mvc/"+user.getId;
}
- return “redirect:当插入课程成功之后,需要对插入课程的详细信息进行展示。在dosave方法对前台传过来的课程数据持久化之后就可以重定向到相应的展示方法进行插入的课程的详细信息的展示。
- logger.info(ReflectionToStringBuilder.toString(user)):这个方法他会将接收到的参数内容以键值对的形式输出到日志中。
- doSave方法中的User参数:如果这里的映射方法中需要接受表单中完整 User模型对象的数据。可以直接在参数中加上一个User对象。这样就必须保证前台表单中的input标签的id属性的值要和User对象中的属性名一致。这样就完成了数据绑定
@ModelAttribute
在Spring MVC里,@ModelAttribute通常使用在Controller方法的参数注解中,用于解释model entity,但同时,也可以放在方法注解里。
如果把@ModelAttribute放在方法的注解上时,代表的是:该Controller的所有方法在调用前,先执行此@ModelAttribute方法。
比如我们有一个Controller:TestController:
@Controller
@RequestMapping(value="test")
public class PassportController {
@ModelAttribute
public void preRun() {
System.out.println("Test Pre-Run");
}
@RequestMapping(method=RequestMethod.GET)
public String index() {
return "login/index";
}
@RequestMapping(value="login", method=RequestMethod.POST)
public ModelAndView login(@ModelAttribute @Valid Account account, BindingResult result)
:
:
}
@RequestMapping(value="logout", method=RequestMethod.GET)
public String logout() {
:
:
}
}
在调用所有方法之前,都会先执行preRun()方法。
我们可以把这个@ModelAttribute特性,应用在BaseController当中,所有的Controller继承BaseController,即可实现在调用Controller时,先执行@ModelAttribute方法。
比如权限的验证(也可以使用Interceptor)等
单文件上传
首先需要在DiapatherServlet包中配置这样一个bean。
<bean id="" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="209715200"/>
<property name="defaultEncoding" value="UTF-8"/>
<property name="resolveLazily" value="true"/>
</bean>
这个bean专门用来解析上载的文件。
然后再在pom.xml中引入这个类依赖的commons-fileupload包。
jsp:
<form method="post" action="/hello/mvc" enctype="multipart/form-data">
<input type="file" name="file">
</form>
文件上传时用到的form表单中必须有enctype这个属性。并且指定其属性值为 multipart/form-data。表示该表单是要处理的
Controller:
@RequestMapping(value = "/doUpload", method = RequestMethod.POST)
public String doUploadFile(@RequestParam("file") MultipartFile multipartFile) throws IOException {
if (!multipartFile.isEmpty()) {
logger.info("11111", multipartFile.getOriginalFilename());
FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), new File("E:\\test\\", System.currentTimeMillis() + multipartFile.getOriginalFilename()));
}
return "course/success";
}
对象的输入输出流的作用: 用于写入对象 的信息和读取对象的信息。 使得对象持久化。
Controller 中对应的映射方法就可以通过 MultipartFile 这个接口来接收传递来的文件对象。并对上传来的文件对象进行操作。
FileUtils:这个类可以对文件进行拷贝,以及对文件流操作。
多文件上传
多文件上传其实很简单,和上传其他相同的参数如 checkbox 一样,表单中的每一个 input的file元素 使用相同的名称,然后 action 中将MultipartFile参数类定义为数组就可以。
jsp:
<body>
<h2>上传多个文件 实例</h2>
<form action="filesUpload.html" method="post"
enctype="multipart/form-data">
<p>
选择文件:<input type="file" name="files">
<p>
选择文件:<input type="file" name="files">
<p>
选择文件:<input type="file" name="files">
<p>
<input type="submit" value="提交">
</form>
</body>
Controller:
/***
// 保存文件的逻辑,单独抽出一个来做一个方法。方便单文件上传和多文件上传不同的调用
private boolean saveFile(MultipartFile file) {
// 判断文件是否为空
if (!file.isEmpty()) {
try {
// 文件保存路径
String filePath = request.getSession().getServletContext().getRealPath("/") + "upload/"
+ file.getOriginalFilename();
// 转存文件
file.transferTo(new File(filePath));
return true;
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
@RequestMapping("filesUpload")
public String filesUpload(@RequestParam("files") MultipartFile[] files) {
//判断file数组不能为空并且长度大于0
if(files!=null&&files.length>0){
//循环获取file数组中得文件
for(int i = 0;i<files.length;i++){
MultipartFile file = files[i];
//保存文件
saveFile(file);
}
}
// 重定向
return "redirect:/list.html";
}
mvn jetty:run
cmd 中利用maven中你配置的项目部署插件。注意要切换到项目根目录下启动cmd来运行 mvn jetty:run 启动项目
<build>
<plugins>
<plugin>
<!-- https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-maven-plugin -->
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.4.3.v20170317</version>
</plugin>
</plugins>
</build>
Json
SpringMVC中默认开启对json的支持
ViewResolver:可以将相同的数据呈现成不同的数据表现形式。
SpringMVC有自己默认的数据表现形式。你也可以在dispatcherServlet.xml文件中进行覆盖,选用自己配置的ViewResolver。
数据形式的多种形式表述(html/xml/json/pdf/excel),spring有两种表述形式:
org.springframework.web.servlet.view.ContentNegotiatingViewResolver
・ org.springframework.http.converter.HttpMessageConverte
可以根据自己的需要选择表述形式。或对选择好的表述形式的属性进行自定义覆盖配置。其都含有自己默认的配置
@PathVariable:当该注解没有指定当前参数对应绑定的路径中的请求参数的名称的时候。默认根据参数名去路径变量中寻找对应参数进行匹配