我是学习了尚硅谷的李守红老师的spring MVC视频之后根据课程的内容做的如下总结。不得不说李老师讲课不是很好,不建议初学者学习。
spring MVC是一个MVC框架,是基于spring开发的,所以可以和spring无缝衔接。
我们从spring MVC的helloWorld开始,一步步扩展需求,看spring MVC是如何解决新需求,如何改善繁琐的流程,简化开发者的任务的。
一、SpringMVC的HelloWorld
1、项目目录
任何web的框架搭建的基础都是web.xml,它是web应用程序容器的入口。看一个web项目时,只要看web.xml就可以看到该项目用到了哪些框架。
2、配置web.xml
(1)项目入口
web项目的入口:listener、servlet、filter等,作用是截取客户端的请求。
springmvc使用的入口是一个servlet(DispatcherServlet)。
...
<display-name>springMVC1</display-name>
....
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
....
</servlet>
....
(2)配置springmvc拦截哪些请求
下面的配置是拦截所有请求,也就是说,对于所有的请求,都会在配置文件中寻找与之对应的controller。
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
(3)配置启动时间
下面的配置是指tomcat一启动,spring的servlet就会初始化
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--下面的配置是指tomcat一启动,spring的servlet就会初始化-->
<load-on-startup>1</load-on-startup>
</servlet>
(4)配置SpringMVC的配置文件路径
SpringMVC的配置文件是指spring-servlet.xml
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--指定配置文件的路径-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:config/spring-servlet.xml</param-value>
</init-param>
<!--下面的配置是指tomcat一启动,spring的servlet就会初始化-->
<load-on-startup>1</load-on-startup>
</servlet>
3、配置文件spring-servlet.xml内容
(1)配置视图解析器
web项目最终要给用户展示视图,这个视图解析器就是配置视图的一些基本参数的。
<!--视图解析器-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--前缀-->
<property name="prefix" value="/"></property>
<!--后缀-->
<property name="suffix" value=".jsp"></property>
</bean>
(2)配置controller与浏览器访问路径的映射关系
<!--当浏览器中输入localhost:8080/springMVC1/test1/helloworld这个网址时,会自动去调用com.tgb.web.controller.HelloWorldController这个类-->
<bean name="/test1/helloworld" class="com.tgb.web.controller.HelloWorldController" />
4、编写Controller类
因为已经在spring-servlet.xml中指定了controller的路径的名称,则按照配置新建controller。
HelloWorldController
package com.tgb.web.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
public class HelloWorldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest arg0,
HttpServletResponse arg1) throws Exception {
System.out.println("-------hello tgb-----");
return new ModelAndView("/welcome");
}
}
新建的controller类实现了Controller接口。重写Controller接口的方法handleRequest()其返回值是ModelAndView类型的。
上述代码被执行时,首先在工作台打印**"-------hello tgb-----"**,然后返回"/wecome"页面,因为配置文件中有前缀和后缀配置,所以最终返回的是://welcome.jsp,若工程中有该页面,则会显示该页面。
(1)ModelAndView
public class ModelAndView {
private Object view;
private ModelMap model;
private boolean cleared = false;
public ModelAndView() {
}
public ModelAndView(String viewName) {
this.view = viewName;
}
ModelAndView类有很多构造函数,若输入参数为一个string字符串,则将视图(view)命名为字符串。
5、编写welcome.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
hello world!
</body>
</html>
JSP加载时,将在页面上显示“hello world!”
二、给界面传递参数
需求:View从Controller中拿数据
helloworld中是在jsp页面上写死了页面的内容是"hello world",这个所谓的MVC架构中,View端并没有从Model端拿任何数据。那如果现在想要拿数据呢?
我们知道数据是从Model传到Controller再从Controller传到View的,那我们先看一步:Controller是怎么传到View的,我们先假设Controller已经拿到了数据,不需要Model的参与。
传递数据有很多种,可大体分成两类,一种是基本数据类型,一种是键值对。
1、传递一个字符串
(1)在controller里面定义一个字符串,并返回
public class HelloWorldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest arg0,
HttpServletResponse arg1) throws Exception {
String hello = "lsh hello 提高班";
return new ModelAndView("/welcome","result",hello);
}
}
返回的页面的参数名为“result”,值为变量hello的值。
(2)在JSP页面中获取传递的参数
<body>
欢迎大家收看我的视频!提高班 李守宏
<br/>
<h>传递数据</h>
${result }
</body>
2、传递一个Map
(1)在controller中定义一个map,并返回
public class HelloWorldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest arg0,
HttpServletResponse arg1) throws Exception {
Map<String,Object> map = new HashMap<String,Object>();
map.put("map1", "提高班1");
map.put("map2", "提高班2");
map.put("map3", "提高班3");
return new ModelAndView("/welcome","map",map);
}
}
(2)在JSP页面中获取传递的参数
<body>
<div>
<c:forEach items="${map }" var="m">
${m.key } -----> ${m.value }
</c:forEach>
</div>
</body>
三、一个controller中写多个方法
问题:helloworld中,一个网址和一个controller绑定。在浏览器输入网址后,调用controller执行里面的方法,然后再返回视图。若controller里面有两个方法的话,程序会不知道调用哪个。所以,若是按照helloworld的设计,一个方法必须对应一个controller才行。
spring MVC当然有解决方法。
1、新建一个controller定义两个方法
MultiController.java
package com.tgb.web.controller;
public class MultiController extends MultiActionController {
public ModelAndView add(HttpServletRequest request,HttpServletResponse response){
System.out.println("----add----");
return new ModelAndView("/multi","method","add");
}
public ModelAndView update(HttpServletRequest request,HttpServletResponse response){
System.out.println("----update----");
return new ModelAndView("/multi","method","update");
}
}
注意这个controller类不再实现Controller接口了,而是集成了MultiActionController 类。
定义了两个方法,add和update。两个方法虽然返回的页面相同,但是传递的数据不同。
2、配置文件修改
(1)注册支持多个方法的解析器组件
springMVC和spring一样,所有的功能都是一个,只要将类注入,就拥有了该功能。
<bean id="paramMethodResolver" class="org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver">
<property name="paramName" value="a"></property>
</bean>
里面的value值可以任意设置,只要不重复就行,最后在浏览器输入网址时需要使用。
(2)注册调用新建的MultiController的路径的配置
<bean name="/test1/multi" class="com.tgb.web.controller.MultiController">
<property name="methodNameResolver">
<ref bean="paramMethodResolver"/>
</property>
</bean>
注意这个bean调用了(1)步创建的解析器组件paramMethodResolver。
3、新建一个JSP页面
multi.jsp
调用method变量的值。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h>多方法controller</h>
本次方法是${method }
</body>
</html>
启动项目后浏览器中输入网址
localhost:8080/SpringMVC2/test1/multi?a=add
结果:本次方法是add
localhost:8080/SpringMVC/test1/multi?a=update
结果:本次方法是update
四、访问静态文件
问题:之前在web.xml文件中配置了拦截,是拦截所有的请求,将拦截的请求在注册的映射中搜索,找到请求对应的 controller类的具体方法,然后调用该方法。
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
当若页面请求静态文件(图片、样式等等)时,本应该是截取去项目路径去拿就好,配置文件中并没有配置静态文件和controller的映射关系的。所以,若还是按照之前的配置,将会访问不到静态资源。
1、配置静态资源
下面的配置是指,当浏览器访问/img/路径时,映射到/img/**路径下,并且该路径下的所有文件都不会被拦截。
<!-- 静态资源访问 -->
<mvc:resources location="/img/" mapping="/img/**"/>
<mvc:resources location="/js/" mapping="/js/**"/>
五、启用注解
问题:一个controller就需要注册一个bean,当controller很多时,注册会很麻烦,配置文件管理也成问题。
可以使用注解来代替繁琐的注册bean,达到的效果相同。
1、新建一个配置类
springAnnotation-servlet.xml
以前的配置文件中,值保留静态资源访问的配置和视图解析器的配置,其他的都删掉。
<!-- 注解扫描包 -->
<context:component-scan base-package="com.tgb.web.controller.annotation" />
<!-- 开启注解 -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"></bean>
<!-- 静态资源访问 -->
<mvc:resources location="/img/" mapping="/img/**"/>
<mvc:resources location="/js/" mapping="/js/**"/>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
(1)首先配置注解扫描包
上面的配置是,扫描com.tgb.web.controller.annotation路径下的所有类。
(2)然后开启注解
有两个需要注册
DefaultAnnotationHandlerMapping是url和类的映射;
AnnotationMethodHandlerAdapter是类和方法的映射。
2、在web.xml中将原来的配置文件的路径改成springAnnotation-servlet.xml
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:config/springAnnotation-servlet.xml</param-value>
</init-param>
3、新建annotation路径,并在下面新建一个控制类
UserController.java
package com.tgb.web.controller.annotation;
//使用注解来代替以前实现Controller接口或继承MultiActionController 类
@Controller
public class UserController {
//通过注解可以指定访问该方法的路径和访问方式
@RequestMapping(value="/user/delUser",method=RequestMethod.GET)
public ModelAndView delUser(){
String result ="this is delUser------";
return new ModelAndView("/jquery","result",result);
}
//通过注解可以指定访问该方法的路径和访问方式
@RequestMapping(value="/user/toUser",method=RequestMethod.GET)
public ModelAndView toUser(){
return new ModelAndView("/jquery");
}
}
六、其他优化
1、开启注解配置的优化
问题:启动注解时固定要注册两个bean,还是嫌麻烦,能不能再简单点儿
(1)优化前
因为每次使用注解都要注册两个
<!-- 开启注解 -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"></bean>
(2)优化后
<!-- 开启注解 -->
<mvc:annotation-driven/>
2、请求路径的优化
问题:通常一个controller下请求的路径都属于同一个根目录,每个方法从根目录写起,嫌麻烦,能不能简单点儿
把根目录提取,利用注解写到类上。
(1)优化前
@Controller
public class User2Controller {
@RequestMapping(value="/user2/addUser")
public String addUser(...){
...
}
@RequestMapping(value="/user2/delUser")
public String delUser(...){
...
}
(2)优化后
@Controller
@RequestMapping(value="/user2")
public class User2Controller {
@RequestMapping(value="/addUser")
public String addUser(HttpServletRequest request){
...
}
@RequestMapping(value="/delUser")
public String delUser(HttpServletRequest request){
...
}
3、“value=”也可以去掉
问题:请求路径前都要写“value=”每个都写,嫌麻烦,能不能去掉
(1)优化前
@Controller
@RequestMapping(value="/user2")
public class User2Controller {
@RequestMapping(value="/addUser")
public String addUser(HttpServletRequest request){
...
}
@RequestMapping(value="/delUser")
public String delUser(HttpServletRequest request){
...
}
(2)优化后
@Controller
@RequestMapping("/user2")
public class User2Controller {
@RequestMapping("/addUser")
public String addUser(HttpServletRequest request){
...
}
@RequestMapping("/delUser")
public String delUser(HttpServletRequest request){
...
}
4、请求方式可以不写
问题:每个方法都要写接口的类型,嫌麻烦,而且GET和POST傻傻分不清楚,能不能不写
优化后可以直接忽略掉,忽略之后,既可以使用GET方法也可以使用POST方法。
(1)优化前
@RequestMapping("/addUser",method=RequestMethod.POST)
public ModelAndView addUser(){
...
}
@RequestMapping("/delUser",method=RequestMethod.GET)
public ModelAndView delUser(){
...
}
(2)优化后
@RequestMapping("/addUser")
public ModelAndView addUser(){
...
}
@RequestMapping("/delUser")
public ModelAndView delUser(){
...
}
5、ModelAndView去掉,替换成返回值类型
问题:返回值类型总是ModelAndView,嫌麻烦,能不能不写
可以去掉,若有参数需要传递,可以使用经典的request.setAttribute()方法。
(1)优化前
@Controller
@RequestMapping("/user2")
public class UserController {
@RequestMapping("/addUser")
public ModelAndView addUser(){
String result ="this is addUser------";
return new ModelAndView("/jquery","result",result);
}
@RequestMapping("/delUser")
public ModelAndView delUser(){
String result ="this is delUser------";
return new ModelAndView("/jquery","result",result);
}
}
(2)优化后
@Controller
@RequestMapping("/user2")
public class UserController {
@RequestMapping("/addUser")
public String addUser(HttpServletRequest request){
String result ="this is addUser------";
request.setAttribute("result", result);
return "/jquery";
}
@RequestMapping("/delUser")
public String delUser(){
String result ="this is delUser------";
request.setAttribute("result", result);
return "/jquery";
}
}
七、从View向Controller传递数据
前面讲到了如何从controller想View传递数据。那么有没有从View向Controller传递数据的需求呢?有的,设想一下如下场景:新用户注册时,用户输入了账号、年龄、性别、密码等个人信息,点击注册,此时应该把用户填写的内容存储到数据库中,这样下次用户登录时,才可以在用户信息表中找到已经注册的用户信息。想存储到数据库中,首先要先传到Controller。
1、创建一个addUser.jsp页面,页面上可以输入用户名和年龄信息,还有提交按钮
2、controller中创建addUser()方法
@RequestMapping("/addUser")
//若方法传入的参数名和jsp页面返回的参数名相同时,会自动将值传递,进行实例化
public String addUser(String userName,String age,HttpServletRequest request){
System.out.println(userName);
System.out.println(age);
}
若传递的参数有很多个时,可以通过创建POJO类型来简化参数的传递
(1)创建一个实体类User.java
package com.tgb.web.controller.entity;
public class User {
private String userName;
private String age;
get() set()
}
(2)Controller中接收参数
@RequestMapping("/addUser")
public String addUser(User user,HttpServletRequest request){
request.setAttribute("userName", user.getUserName());
request.setAttribute("age", user.getAge());
return "/userManager";
}
八、从View页面上传文件
也会有上传文件的需求,比如在登录某个填报系统的时候,需要上传附件。
1、配置文件
注册组件
Annotation-servlet.xml
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8" />
<property name="maxUploadSize" value="10485760000" />
<property name="maxInMemorySize" value="40960" />
</bean>
2、upload.jsp
创建该页面上传文件
<body>
<h>添加用户</h>
<form name="userForm" action="/springMVC7/file/upload2" method="post" enctype="multipart/form-data" >
选择文件:<input type="file" name="file">
<input type="submit" value="上传" >
</form>
</body>
3、UpLoadController.java
@RequestMapping("/upload2")
public String upload2(HttpServletRequest request,HttpServletResponse response) throws IllegalStateException, IOException{
//初始化一个解析器,解析spring MVC的上下文
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext());
//使用解析器去解析request,看是否是Multipart类型
if(multipartResolver.isMultipart(request)){
//定义一个spring mvc封装好的MultipartHttpServletRequest去接收request
MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest)request;
//迭代文件名
Iterator<String> iter = multiRequest.getFileNames();
while(iter.hasNext()){
//根据文件名拿到文件
MultipartFile file = multiRequest.getFile((String)iter.next());
if(file != null){
String fileName = "demoUpload" + file.getOriginalFilename();
String path = "D:/" + fileName;
//新建一个文件类型
File localFile = new File(path);
//将file文件写到指定的文件localFile中
file.transferTo(localFile);
}
}
}
return "/success";
}
4、success.jsp
<body>
上传文件成功!!
</body>
九、总结
spring MVC是MVC设计理念的一种实现方式,用户只需要在JSP页面上写前端展示相关的代码,在controller中写页面跳转和数据传递的逻辑,在Model中处理数据逻辑就可以实现web开发。
spring MVC简化和优化了一些MVC的规则,大大简化了开发人员的配置工作。
关于Spring MVC的基础的使用大概就这些了,读者们肯定会纳闷儿,光将了View和Controller的交互,为啥没有Controller和Model的交互呢。
因为Model和数据库相关,直接写数据可操作的JDBC特别麻烦,而且关于对数据库的操作有成型的工具,比如Hibernate和Mybatis,所以我们都是使用Hibernate或Mybatis来间接操作数据库。所以要将Controller与Model的交互,要先介绍Hibernate或Mybatis。这部分操作放在下一讲进行。