SpringMVC 入门
1. HelloWorld 入门程序
// 1. 导入 jar 包;
// 2. 配置 WEB-INF/web.xml
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
// 3. 在WEB-INF创建springDispatcherServlet-servlet.xml 配置文件
// 选择: spring Bean Configuration File, 名称, beans, context, mvc, 完成
<!-- 配置通用扫描的包路径 -->
<context:component-scan base-package="cn.itcast.springmvc"></context:component-scan>
<!-- 配置通用的视图解析器 -->
<bean id="internalResourceViewResolver"
class="org.springframework.web.servlet.view.internalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
// 3.1 也可以在 src 目录下,创建SpringMVC的配置文件, 此时,web.xml 的配置为:
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- 配置springmvc 配置文件路径 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servet-mapping>
// 4. 创建WEB-INF/views/ok.jsp, 即结果页面
// 5. 创建WebContent/index.jsp, 即请求页面
示例:<a href="${pageContext.request.contextPath}/helloworld">点击这里</a>
// 6. cn.itcast.springmvc.handler(或controller)
@Controller
public class HelloWorld{
@RequestMapping(value="/helloworld",method=RequestMethod.GET)
public String helloworld(){
System.out.println("程序运行正常");
return "ok";
}
}
2. HelloWorld 深度解析
- 客户端请求提交到 DispatcherServlet;
- 由 DispatcherServlet 控制器查询一个或多个 HandlerMapping, 找到处理请求的 Controller;
- DispatcherServlet 将请求提交到 Controller;
- Controller 调用业务逻辑处理后,返回 ModelAndView;
- DispatcherServlet 查询一个或多个ViewResolver视图解析器,找到ModelAndView指定的视图;
3. 注解 RequestMapping
3.1 请求地址
@Controller
@RequestMapping(value="/crm")
public class HelloWorld{
@RequestMapping(value="/helloworld",method=RequestMethod.GET)
public String helloworld(){
System.out.println("程序运行正常");
return "ok";
}
}
// 备注: 此时访问地址: /crm/helloworld
3.2 RequestMapping 中的参数介绍
- 常用方法:
method=RequestMethod.GET
method=RequestMethod.POST
- 请求参数映射 params
params={"age","tel!=110"}
- 表示请求参数中必须有age和tel,且tel不是110;
- 请求头映射 headers
headers={"Host=localhost:8080"}
- 表示请求主机必须是 localhost:8080;
- URL 映射
// 1. 占位符URL映射
// ? 匹配一个字符; * 匹配零个或多个字符; ** 匹配零个或多个路径
public class Demo{
@RequestMapping(value="/test?", method=RequestMethod.GET)
public String test(){
System.out.println("程序运行正常");
return "ok";
}
}
// 2. @PathVariable 占位符URL映射
// index.jsp
示例: <a href="${pageContext.request.contextPath}/tpv/887">testPathVariable</a>
// cn.itcast.springmvc.demo
@Controller
public class Demo{
@RequestMapping(value="/tpv/{bookId}",method=RequestMethod.GET)
public String test2(@PathVariable("bookId") String bookId){
System.out.println("程序运行正常"+bookId);
return "ok";
}
}
4. REST 介绍
- REST, Representational State Transfer, (资源)表现层状态转化;
- 资源:网络上的一个具体信息,可以是一段文本,一张图片等等;每种资源对应一个特定的URI,要获取这个资源,
访问它的 URI 即可, 因此, URI 即为每一个资源的独一无二的识别符; - 表现层: 把资源具体呈现出来的形式; 例如文本可以用txt格式表现,也可以用HTML格式,XML格式或JSON格式
- 状态转化:每发出一个请求,就代表了客户端和服务器的一次交互过程. HTTP协议,是一个无状态协议,即所有
的状态都保存在服务端.因此,如果客户端想要操作服务器,必须通过某种手段,让服务器发生"状态转化".而
这种转化是建立在表现层之上的,所以就是"表现层状态转化".具体说,就是HTTP协议里面,四个表示操作方式
的动词: GET,POST,PUT,DELETE,分别对应四种基本操作: GET 用来获取资源, POST 用来新建资源,
PUT 用来更新资源, DELETE 用来删除资源;
// 需求: 发送RESTful风格请求
// 1. web.xml 中配置 HiddenHttpMethodFilter
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
// 2. index.jsp
<form action="${pageContext.request.contextPath}/order/224" method="post">
<input type="hidden" name="_method" value="DELETE"/>
<input type="submit" value="删除订单"/>
</form>
// 3. cn.itcast.springmvc.demo
@Controller
public class Demo{
@RequestMapping(value="/order/{orderId}",method=RequestMethod.DELETE)
public String delete(@PathVariable("orderId") String orderId){
System.out.println("删除订单成功"+orderId);
return "ok";
}
}
5. 获取请求相关数据
@RequestParam
@PathVariable
@RequestHeader
@CookieValue
// @RequestParam, 获取请求参数
// index.jsp
<a href="${pageContet.reqeust.contextPath}/testRequestParam?name=zhangsan">点击这里</a>
// cn.itcast.springmvc.demo
@Controller
public class Demo{
@RequestMapping(value="/testRequestParam",method=RequestMethod.GET)
public String testRequestParam(@RequestParam("name") String name){
System.out.println("姓名:"+name);
return "ok";
}
// @RequestParam 中的参数: required, defaultValue
@RequestMapping(value="/testRequestParam",method=RequestMethod.GET)
public String test03(@RequestParam(value="name",required=true,
defaultValue="lisi") String name){
System.out.println("姓名:"+name);
return "ok";
}
}
// 请求中有多个参数
// index.jsp
<a href="${pageContext.request.contextPath}/test04?name=zhangsan&roleId=manager&roleId=actory">
点击这里
</a>
// demo.class
@Controller
public class Demo{
@RequestMapping(value="/test04",method=RequestMethod.GET)
public String test04(@RequestParam(value="name") String name,
@RequestParam("roleId") List<String> roleIds){
for(String rId : roleIds){
System.out.println(rId);
}
System.out.println(name);
return "ok";
}
}
// @RequestHeader, 获取请求头的值
// @CookieValue, 获取 cookie 的值
// index.jsp
<a href="${pageContext.request.contextPath}/test05">点击这里</a>
// demo.class
@Controller
public class Demo{
@RequestMapping(value="/test05",method=RequestMethod.GET)
public String test05(@RequestHeader("Host") String host,
@CookieValue("JSESSIONID") String JSESSIONID){
System.out.println(host + JSESSIONID);
return "ok";
}
}
// 使用 POJO 对象
// User.class(略)
// index.jsp
<form action="${pageContext.request.contextPate}/test06" method="post">
用户名:<input type="text" name="username"><br/>
密码:<input type="password" name="password"><br/>
<input type="submit" value="注册"/>
</form>
// demo.class
@Controller
public class Demo{
@RequestMapping(value="/test06",method=RequestMethod.POST)
public String test06(User user){
System.out.println(user);
return "ok";
}
}
// 使用Servlet的原生API获取参数
// test06() 中的参数: HttpServletRequest, HttpServletResponse,HttpSession,
// java.security.Principal, Locale, InputStream, OutputStream,
// Reader, Writer
// index.jsp(略)
// demo.class
@Controller
public class Demo{
@RequestMapping(value="/test07",method=RequestMethod.GET)
public void test06(HttpServletRequest request, HttpServletResponse response,
Writer writer) throws IOException{
System.out.println(request.getContextPath());
writer("程序运行正常");
wirter.close();
}
}
6. 处理模型数据
6.1 ModelAndView
- SpringMVC 框架会将 ModelAndView 中的数据放进request请求域中, ModelAndView 底层实际就是
request.setAttribute(k,v)
;
// index.jsp
<a href="${pageContext.request.contextPath}/test07">点击这里</a>
// demo.class
@Controller
public class Demo{
@RequestMapping(value="/test07", method=RequestMethod.GET)
public ModelAndView test07(){
ModelAndView mv = new ModelAndView();
mv.addObject("username","zhangsan");
mv.setViewName("ok");
return mv;
}
}
// ok.jsp
用户名:${requestScope.username}
6.2 Map+Model+ModelMap
- Map:
java.util.Map
, 接口; - Model:
org.springframework.ui.Model
, 接口; - ModelMap:
org.springframework.ui.ModelMap
, 实现类;
// index.jsp
<a href="${pageContext.request.contextPath}/test08">点击这里</a>
// demo.class
@Controller
public class Demo{
@RequestMapping(value="/test08",method=RequestMethod.GET)
public String test08(Map<String,Object> m1, Model m2, ModelMap m3){
m1.put("m1","java.util.Map<K,V>");
m2.addAttribute("m2","org.springframework.ui.Model");
m3.addAttribute("m3","org.springframework.ui.ModelMap");
System.out.println(m1 == m2);
System.out.println(m2 == m3);
System.out.println(m1 == m3);
System.out.println(m1.getClass().getName());
System.out.println(m2.getClass().getName());
System.out.println(m3.getClass().getName());
return "ok";
}
}
// ok.jsp
m1: ${requestScope.m1}<br/>
m2: ${requestScope.m2}<br/>
m3: ${requestScope.m3}<br/>
6.2.1 结果说明
- 此处虽然注入的是三个不同的类型(Map m1, Model m2, ModelMap m3), 但三者是同一个对象,都是
同一个BindingAwareModelMap实例; - SpringMVC 框架会将模型数据放进request请求域中;
- 为了方便重构,使用
java.util.Map
较多;
6.3 SessionAttributes
- 该注解只能放在类上面;
// index.jsp
<a href="${pageContext.request.contextPath}/test09">点击这里</a>
// demo.class
@Controller
@SessionAttributes(value="user",types=String.class)
public class Demo{
@RequestMapping(value="/test09",method=RequestMethod.GET)
public String test09(Map<String,Object> map){
User user = new User("zhangsan",22,"zhangsan@163.com");
map.put("user",user);
map.put("address","Beijing");
return "ok";
}
}
// ok.jsp
用户名: ${sessionScope.user}
地址: ${sessionScope.address}
6.4 ModelAttribute
- 该注解作用在:方法或参数上,
@Target({ElementType.PARAMETER, ElementType.METHOD})
// 需求: 用户李四将自己的用户名改为"zhangsan",年龄改为"25",密码和邮箱不变
// index.jsp
<h2>修改信息</h2>
<form action="${pageContext.request.contextPath}/test10" method="post">
<input type="hidden" name="_method" value="PUT"/><br/>
<input type="hidden" name="id" value="11"/><br/>
用户名:<input type="text" name="username" value="zhangsan"/><br/>
年龄: <input type="text" name="age" value="22"/><br/>
Email: <input type="text" name="email" value="zhangsan@163.com"/><br/>
<input type="submit" value="修改"/><br/>
</form>
// demo.class
@Controller
@SessionAttributes(value="user")
public class Demo{
@RequestMapping(value="/test10",method=RequestMethod.PUT)
public String test10(User user){
System.out.println(user);
return "ok";
}
}
6.4.1 异常一: "Session attribute 'user' required - not found in session"
- 解决方法: 将
@SessionAttributes(value="user")
注释掉;
6.4.2 异常二: 此时,收到的password=null
// 修改 demo.class
@Controller
public class Demo{
@RequestMapping(value="/test10",method=RequestMethod.PUT)
public String test10(User user){
Sysetm.out.println("修改之后的User: "+ user);
return "ok";
}
// 从数据库中根据 id 查询
@ModelAttribute
public void getUserById(@RequestParam(value="id") Integer id){
System.out.println("come in @ModelAttribute ##############");
if(null != id){
// 模拟根据 id,从数据库查询出对应的 User 信息
// User user = UserService.getUserById(id);
// user(id,name,password,age,email);
User user = new User(11,"lisi","12345",22,"lisi@163.com");
System.out.println("数据库查询到的初始User: "+ user);
}
}
}
运行结果, 另外,点击其他请求,出现如下异常:"Required Integer parameter 'id' is not present"
结果分析
// 修改代码, 增加属性 `required=false`,将数据库查询到 user,存入到隐藏域
@ModelAttribute
public void getUserById(@RequestParam(value="id", required=false) Integer id,
Map<String,Object> map){
System.out.println("come in @ModelAttribute ##############");
if(null != id){
// 模拟根据 id,从数据库查询出对应的 User 信息
// User user = UserService.getUserById(id);
// user(id,name,password,age,email);
User user = new User(11,"lisi","12345",22,"lisi@163.com");
// 将user放入隐藏域
map.put("user",user);
System.out.println("数据库查询到的初始User: "+ user);
}
}
运行结果
总结一:
- 根据ID从数据库中查询出对象,并将对象放入Map,其中key为user(POJO类名首字母小写),完成修改;
- 在方法头上添加
@ModelAttribute
- 每个 Controller 类下面的业务方法都会先调用标注了
@ModelAttribute
注解的方法; - 放入到Map的 key,需要与目标方法的参数首字母小写的对象一致;
- 每个 Controller 类下面的业务方法都会先调用标注了
- 在方法入参前使用
@ModelAttribute
- 查询数据库放入map的 key与
@ModelAttribute
修改的方法入参前名字,是否一致;不一致; 不写;
- 查询数据库放入map的 key与
// 第一种方式: 在方法入参前,使用 @ModelAttribute("user"), 即与隐藏域map中的key名称一致
@RequestMapping(value="test10",method=RequestMethod.PUT)
public String test10(@ModelAttribute("user") User user){
System.out.println("修改之后的User: "+user);
return "ok";
}
// 第二种方式:在方法入参前,使用 @ModelAttribute("abc"),即与隐藏域map中的key名称不一致;
// 此时,password 为 null;
@RequestMapping(value="test10",method=RequestMethod.PUT)
public String test10(@ModelAttribute("abc") User user){
System.out.println("修改之后的User: "+user);
return "ok";
}
@ModelAttribute
public void getUserById(@RequestParam(value="id",required=false) Integer id,
Map<String,Object> map){
System.out.println("come in @ModelAttribute #######");
if(null != id){
User user = new User(11,"lisi","12345",22,"lisi@163.com");
}
map.put("user",user);
System.out.println("数据库查询到的初始User: "+user);
}
总结二:
- 确定key的值,如果有@ModelAttribute进行修饰,则key的值就是@ModelAttribute注解的value属性值;
如果没有写,默认key值就是传入POJO类名的第一个字母小写; - 从Map中获取key对应的Bean,如果有,就直接赋值并作为后续入参传入;
如果没有,那就去Session域中查找,也就是当前Handler的@SessionAttributes
注解的value值或者
types值是否和key或者Bean的类型匹配,如果匹配就从Session域中获取;- 如果Session中有对象,则返回该值作为目标方法的入参;
- 如果Session中没有对象,就会抛出异常
HttpSessionRequiredException
;
- 如果
implictModel
中没有,sessionAttribute
中也没有,那就通过反射创建一个 bindObject,
bindObject = BeantUtils.instantiateClass(paramType);
参考资料