一. Spring MVC介绍
- Spring MVC是WEB层框架[接管了web层组件,比如: 控制器、视图、视图解析器、返回给用户的数据格式,同时支持MVC的开发模式]
- Spring MVC通过注解,让POJO成为控制器,不需要继承或者实现接口
- Spring MVC采用低耦合的组件设计方式,具有更好扩展和灵活性
- 支持REST风格的URL请求
- Spring MVC是基于Spring的,也就是Spring MVC是在Spring基础上的(SpringBoot > Spring > Spring MVC)
二.快速入门
web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--配置前端控制器-->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<!--加载springmvc的配置文件-->
<param-value>classpath:applicationContext-mvc.xml</param-value>
</init-param>
<!--在web项目启动时,就加载这个servlet实例-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
applicationContext-mvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<!--配置自动扫描包-->
<context:component-scan base-package="com.zzti.springmvc.controller"/>
<!--配置springmvc的视图解析器,比如我们的controller return 的是login_ok,
那么这个页面就是/WEB-INF/pages/login_ok.jsp-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<title>登录</title>
</head>
<body>
<h1>登录页面</h1>
<form action="login" method="post">
u: <input name="username" type="text"><br/>
p: <input name="password" type="password"><br/>
<input type="submit" value="登录" />
</form>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<title>登录成功页面</title>
</head>
<body>
<h1>恭喜, 登录成功!!!</h1>
</body>
</html>
package com.zzti.springmvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class UserController {
@RequestMapping("/login")
public String login(){
System.out.println("login ok~~");
return "login_ok";
}
}
注意细节:
- UserController需要注解成@Controller,称为一个Handler处理器
- 关于springmvc的DispatcherServlet的配置文件,如果不在web.xml指定applicationContext-mvc.xml文件,默认在/WEB-INF/DispatcherServlet-servlet.xml 找这个配置文件。
在web.xml文件中,把init-param注释掉,然后再把原来配置的applicationContext-mvc.xml文件,修改一个名字[xx-servlet.xml] (xx是自己配置的DispatcherServlet的名字)
放在/WEB-INF目录下面,在测试一下。
三.Spring MVC的执行流程
四. @RequestMapping注解
- @RequestMapping注解可以指定控制器/处理器的某个方法请求的url
- @RequestMapping可以修饰方法和类,当同时修饰方法和类时,url就是组合
/类请求值/方法请求值
,同时也可以method指定请求方式(method = RequestMethod.POST
),若不按照指定方式请求就会报错。
@Controller
@RequestMapping("/user")
public class UserHandler {
@RequestMapping(value = "buy",method = RequestMethod.POST)
public String buy(){
System.out.println("购买商品");
return "success";
}
}
3. @RequestMapping可指定params 和 headers支持简单表达式
param1
: 表示请求必须包含名为param1的请求参数!=param1
: 表示请求不能包含名为param1的请求参数param != value1
: 表示请求包含名为param1 的请求参数,但是值不能为value1{"param1=value1","param2"}
: 请求必须包含名为param1 和 param2
的两个参数,并且param1参数的值必须为value1
a) param1: 表示请求必须包含名为param1的请求参数
@RequestMapping(value = "/find",params = "bookId" ,method = RequestMethod.GET)
public String search(String bookId){
System.out.println("查询书籍 bookId= " + bookId);
return "success";
}
===================================
查询书籍 bookId= 100
@RequestMapping(value = "/find",params = "bookId=100" ,method = RequestMethod.POST)
b) param != value1: 表示请求包含名为param1 的请求参数,但是值不能为value1
@RequestMapping(value = "/find",params = "bookId!=100" ,method = RequestMethod.POST)
c) 请求必须包含名为param1 和 param2 的两个参数,并且param1参数的值必须为value1
@RequestMapping(value = "/find", params = { "bookId=100", "bookName"},method = RequestMethod.POST)
public String search(String bookId,String bookName){
System.out.println("查询书籍 bookId= " + bookId + ",书籍名称= " + bookName);
return "success";
}
================================
查询书籍 bookId= 100,书籍名称= 三国演义
4. @RequestMapping支持Ant风格资源地址
?
: 匹配文件名中的一个字符
*
: 匹配文件名中的任意字符
* *
:匹配多层路径
应用举例:
/user/*/createUser
==> /user/aa/createUser、/user/bb/createUser等URL
/user/**/createUser
==> /user//createUser、/user/aa/bb/createUser等URL
/user/createUser??
==> /user/createUseraaa、/user/createUserbb等URL
@PostMapping(value = "/message/**")
public String im(String bookId,String bookName){
System.out.println("发送消息");
return "success";
}
=========================
发送消息
- @RequestMapping 可匹配 @PathVariable映射URL绑定的占位符,就不需要在url地址上带参数名了,比较简洁
@RequestMapping(value = "/register/{username}/{userId}")
public String register(@PathVariable("username") String name, @PathVariable("userId") String id){
System.out.println("接收到参数~~username= " + name + ",userId= " + id);
return "success";
}
注意细节:
- 映射的url,不能重复
- 各种请求的简写形式@GetMapping、@PostMapping、@DeleteMapping、@PutMapping
- 若我们确定表单或者超链接会提交某个字段数据(比如: email),要求提交的参数名和目标方法参数保持一致
@GetMapping(value = "/hello")
public String hello(String email){
System.out.println("hello ~ " + email);
return "success";
}
=====================
hello ~ tom@163.com
五. Rest请求风格
- REST(Representational State Transfer) 资源表现层转态转化。
- HTTP协议里,GET(获取资源)、POST(新建资源)、PUT(更新资源)、DELETE(删除资源)
- REST核心过滤器
3.1 当前的浏览器只支持post/get请求,因此为了得到put/delete请求方式,需要使用Spring提供的HiddenHttpMethodFilter过滤器进行转换
3.2 HiddenHttpMethodFilter: 浏览器form表单只支持get、post请求,而delete、put等method并不支持,spring添加了一个过滤器,可以将这些请求转换为标准的http方法,使得支持常见的四种请求方式。
3.3 HiddenHttpMethodFilter能对post请求方式进行转换,需要我没注意这点,这个过滤器需要在web.xml中配置
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>
applicationContext-mvc.xml增加配置
<!--加入两个常规配置-->
<!--能支持Spring MVC高级功能(JSR303校验,映射动态请求)-->
<mvc:annotation-driven/>
<!--将spring mvc不能处理的请求交给Tomcat-->
<mvc:default-servlet-handler/>
BookController.java
package com.zzti.springmvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping(value = "/book")
public class BookController {
@GetMapping("/getBook/{id}")
public String getBook(@PathVariable("id") String id){
System.out.println("查询书籍 id= " + id);
return "success";
}
@PostMapping("/addBook")
public String addBook(String bookName){
System.out.println("添加书籍 bookName= " + bookName);
return "success";
}
@PutMapping("/updateBook/{bookId}")
public String updateBook(@PathVariable("bookId") String id){
System.out.println("修改书籍 id= " + id);
//return "success";//这样会报错(JSPs only permit GET POST or HEAD)
return "redirect:/book/success";
}
@DeleteMapping(value = "/delete/{bookId}")
public String deleteBook(@PathVariable("bookId") String id){
System.out.println("删除书籍 id= " + id);
//return "success";//这样会报错(JSPs only permit GET POST or HEAD)
return "redirect:/book/success";
}
@RequestMapping(value = "/success")
public String scucessView(){
//由该方法转发到success.jsp页面
return "success";
}
}
注意事项:
- HiddenHttpMethodFilter,在将post转成 delete/put 请求时,是按照
_method
参数名来读取的 - 若web项目时运行在Tomcat8及以上,会发现被过滤成DELETE和PUT请求,到达控制器时能顺利,但是返回(以forward方式返回)时会报错:
(JSPs only permit GET POST or HEAD)。
解决方式:将请求转发(forward) 改为 请求重定向(redirect), 重定向到一个Handler,再由这个Handler转发到页面。
六. Spring MVC映射请求数据(@RequestParam)
package com.zzti.springmvc.controller;
import com.zzti.springmvc.entity.Master;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@Controller
@RequestMapping(value = "/vote")
public class VoteController {
//获取到url中的参数值
/**
* @RequestParam(value = "name",required = false)
* 表示一个接收到的参数是name,required = false表示参数可有可无,默认是true
* @param username
* @return
*/
@RequestMapping("/vote01")
public String test01(@RequestParam(value = "name",required = false) String username){
System.out.println("得到的 username= " + username);
return "success";
}
//获取http请求消息头
@RequestMapping("/vote02")
public String test02(@RequestHeader("Accept-Encoding") String ae,
@RequestHeader("Host") String host){
System.out.println("Accept-Encoding= " + ae);
System.out.println("Host= " + host);
return "success";
}
//获取Javabean(entity/pojo)形式的数据
/**
* 1. 支持级联数据获取
* 2. 表单的控件名称name需要和Javabean对象字段对应,否则就是null
* @param master
* @return
*/
@RequestMapping("/vote03")
public String test03(Master master){
System.out.println("主人信息= " + master);
return "success";
}
//获取servlet api
/**
* 开发中,我们可能需要使用到原生的servlet api
* @return
*/
@RequestMapping("/vote04")
public String test04(HttpServletRequest request,
HttpServletResponse response,
HttpSession session){
System.out.println("name= " + request.getParameter("username"));
System.out.println("pwd= " + request.getParameter("pwd"));
System.out.println("session= " + session);
HttpSession requestSession = request.getSession();
System.out.println("requestSession= " + requestSession);
System.out.println(session == requestSession);//true
return "success";
}
}
七. 模型数据
一. 模型数据处理-数据放入 request
开发中,控制器中获取的数据如何放入request域,然后再前端(vue/jsp/html)取出显示
- 通过HttpServletRequest放入request域
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" isELIgnored="false"%>
<html>
<head>
<title>获取数据成功页面</title>
</head>
<body>
<h1>获取的数据显示页面</h1>
<hr>
<h1>取出request域的数据</h1>
<br>
address: ${address}<br>
主人名= ${requestScope.master.name}<br>
主人信息= ${requestScope.master}<br>
宠物名字= ${requestScope.master.pet.name}<br>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<title>测试 模型数据</title>
</head>
<body>
<h1>模型数据</h1>
<form action="vote/vote05" method="post">
主人号: <input name="id" type="text"><br/>
主人名: <input name="name" type="text"><br/>
宠物号: <input name="pet.id" type="text"><br/>
宠物名: <input name="pet.name" type="text"><br/>
<input type="submit" value="添加宠物和主人" />
</form>
</body>
</html>
/**
* 演示将提交的数据-->springmvc封装到java对象-->springmvc会自动的将其放到request中
* 这样我们就可以在调转到的页面取出数据
* @param master
* @param request
* @param response
* @return
*/
@RequestMapping("/vote05")
public String test05(Master master,HttpServletRequest request,
HttpServletResponse response){
request.setAttribute("address","上海");
//修改master属性值
master.setName("nono");
//分析一下,springmvc默认存放对象到request域中,属性名是类名首字母小写
return "vote_ok";
}
- 通过请求的方法参数 Map<String,Object> 放入request域
/**
* 演示通过Map<String,Object> 设置数据到request域
* @param master
* @return
*/
@RequestMapping("/vote06")
public String test06(Master master, Map<String,Object> map){
map.put("address","天津~~");
return "vote_ok";
}
- 通过返回ModelAndView对象,实现request域对象
@RequestMapping("/vote07")
public ModelAndView test07(Master master, Map<String,Object> map){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("address","北京~~");
//指定要跳转的视图名称
modelAndView.setViewName("vote_ok");
return modelAndView;
}
注意事项:
- 从本质看,请求响应的方法return “xxx”,是返回一个字符串,其实本质是返回了一个ModelAndView对象,只是默认被封装起来的
- ModelAndView既可以包含model数据,也可以包含视图信息
- ModelAndView对象的addObjet(),可以添加key-value数据,默认在request域中
- ModelAndView对象setViewName(),可以指定要跳转的视图名称
二. 模型数据处理-数据放入 session
开发中,控制器中获取的数据如何放入session域,然后再前端(vue/jsp/html)取出显示
//模型数据处理-数据放入 session
@RequestMapping("/vote08")
public String test08( Map<String,Object> map,Master master,HttpSession httpSession){
System.out.println("==========test08=================");
map.put("address","广州");
httpSession.setAttribute("master2",master);
return "vote_ok";
}
三. @ModelAttribute 实现prepare方法
开发中,有时需要使用某个前置方法(prepareXxx(),方法名有程序员自己指定)给目标方法准备一个模型对象。@ModelAttribute 注解可以实现这样的需求,在某个方法加上这个注解后,调用该Controller(Handler)的任何一个方法时,都会调用这个方法。
@RequestMapping("/vote08")
public String test08( Map<String,Object> map,Master master,HttpSession httpSession){
System.out.println("==========test08=================");
map.put("address","广州");
httpSession.setAttribute("master2",master);
return "vote_ok";
}
@ModelAttribute
public void prepareModel(){
System.out.println("prepareModel()....完成准备工作.....");
}
=============================
prepareModel()....完成准备工作.....
==========test08=================
应用场景:
修改用户信息,
流程如下:
- 在修改前,在前置方法中从数据库查出这个用户
- 在修改方法(目标方法)中,可以使用前置方法从数据库查询用户
- 如果表单中对用户的某个属性修改了,则以新的数据为准,如果没有修改,则以数据库的信息为准,若某个属性不能修改,就保持原来的值。
八. 视图和视图解析器
一. 基本介绍
在springmvc中的目标方法最终返回都是一个视图,返回的视图都会由一个视图解析器来处理(视图解析器有很多种)
二. 自定义视图
在默认情况下,我们都是返回的默认视图,然后这个返回的视图交给springmvc的InternalResourceViewResolver 视图处理器来处理的。
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
在实际开发中,我们需要自定义视图,这样可以满足更多复杂的需求。
<!--配置自定义图解析器-->
<!--
1. BeanNameViewResolver这个就是可以解析自定义视图的解析器
2. name="order"表示这个解析器设置的优先级 默认优先级很低, 默认值是Integer.MAX_VALUE
3. order值越小,优先级越高
-->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="99"></property>
</bean>
测试自定义视图
/**
* 自定义视图类
*/
@Component(value = "myView")
public class MyView extends AbstractView {
@Override
protected void renderMergedOutputModel(Map<String, Object> map,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws Exception {
System.out.println("进入到自己的视图");
//这里自己确定到哪个页面去, 默认的视图解析机制就无效
httpServletRequest.getRequestDispatcher("/WEB-INF/pages/my_view.jsp").
forward(httpServletRequest,httpServletResponse);
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" isELIgnored="false"%>
<html>
<head>
<title>view</title>
</head>
<body>
<h1>测试自定义视图</h1>
<a href="goods/buy">测试自定义视图</a><br/>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" isELIgnored="false"%>
<html>
<head>
<title>自定义视图页面</title>
</head>
<body>
<h1>自定义视图页面</h1>
<hr/>
<h1>欢迎来到,自定义视图~~</h1>
<br>
</body>
</html>
@Controller
@RequestMapping("/goods")
public class GoodsController {
@RequestMapping(value = "/buy")
public String buy(){
System.out.println("~~buy~~");
return "myView";//填写自己定义的视图名字
}
}
==============测试结果===================
~~buy~~
进入到自己的视图
欢迎来到,自定义视图~~
小结:
- 自定义视图: 创建一个View的bean,该bean需要继承AbstractView,并实现renderMergedOutputModel(), 还要把View加入到ioc容器中。
- 自定义视图的视图处理器,使用BeanNameViewResolver,这个视图处理器也需要配置ioc容器中。
- BeanNameViewResolver的调用优先级需要配置一下,设置order比默认值Integer.MAX_VALUE小的值,以确保在默认视图解析器之前被调用。
自定义视图-工作流程
- Spring MVC调用目标方法,返回自定义View在ioc容器中的id
- Spring MVC调用BeanNameViewResolver解析视图: 从ioc容器中获取
返回id值对应的bean,即自定义的view的对象(若返回自定义View在ioc容器中的id不存在,则仍然按照默认的视图处理机制处理
) - Spring MVC调用自定义视图的renderMergedOutputModel() 渲染视图。
三. 目标方法直接指定转发或重定向
- 默认返回的方式是请求转发,然后在视图处理器进行处理
@GetMapping("/getBook/{id}")
public String getBook(@PathVariable("id") String id){
System.out.println("查询书籍 id= " + id);
return "success";
}
- 也可以在目标方法中直接指定重定向或转发的url,
如果指定重定向,不能定向到/WEB-INF目录中
@GetMapping("/getBook/{id}")
public String getBook(@PathVariable("id") String id){
System.out.println("查询书籍 id= " + id);
//直接指定转发到那个页面
//return "forward:/WEB-INF/pages/my_view.jsp";
//如果是重定向,不能指定到/WEB-INF目录中
return "redirect:/login.jsp";
}
九. 自己实现Spring MVC底层机制
一. 任务1-开发HspDispatcherServlet(充当原生的前端控制器)
HspDispatcherServlet.java
/**
* HspDispatcherServlet充当原生的DispatcherServlet
* 本质是一个servlet,继承HttpServlet
*/
public class HspDispatcherServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("HspDispatcherServlet doGet()执行~~~");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("HspDispatcherServlet doPost()执行~~~");
}
}
web.xml
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--配置HspDispatcherServlet,作为我们自己的前端控制器-->
<servlet>
<servlet-name>HspDispatcherServlet</servlet-name>
<servlet-class>com.zzti.myspringmvc.servlet.HspDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<!--加载springmvc的配置文件-->
<param-value>classpath:myspringmvc.xml</param-value>
</init-param>
<!--HspDispatcherServlet在Tomcat启动时,就自动加载-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>HspDispatcherServlet</servlet-name>
<!--拦截所有的请求-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
myspringmvc.xml
<!--配置自动扫描包-->
<context:component-scan base-package="com.zzti.myspringmvc.controller"/>
二. 任务2-完成客户端/浏览器可以请求控制层
创建Controller和自定义注解
package com.zzti.myspringmvc.controller;
import com.zzti.myspringmvc.annotatipn.Controller;
import com.zzti.myspringmvc.annotatipn.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Controller
public class MonsterController {
@RequestMapping(value = "/list")
public void getListMonster(HttpServletRequest request, HttpServletResponse response){
response.setContentType("text/html;charset=utf-8");
try {
PrintWriter writer = response.getWriter();
writer.write("<h1>妖怪列表信息</h1>");
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
String value() default "";
}
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";
}
配置myspringmvc.xml文件要扫描的包
<!--配置自动扫描包-->
<context:component-scan base-package="com.zzti.myspringmvc.controller,com.zzti.myspringmvc.service"/>
编写XMLParser工具类,可以解析myspringmvc.xml,得到要扫描的包
/**
* XMLParser 用于解析 myspringmvc.xml文件
*/
public class XMLParser {
public static String getBasePackage(String xmlFile){
SAXReader saxReader = new SAXReader();
//通过得到来的加载路径,来获取到myspringmvc配置文件
InputStream inputStream = XMLParser.class.getClassLoader().getResourceAsStream(xmlFile);
try {
//得到xmlFile文档
Document document = saxReader.read(inputStream);
Element rootElement = document.getRootElement();
Element componentScanElement = rootElement.element("component-scan");
Attribute attribute = componentScanElement.attribute("base-package");
String basePackage = attribute.getText();
return basePackage;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
开发HspWebApplicationContext,充当Spring容器,得到扫描类的全路径列表
/**
* HspWebApplicationContext 表示我们自己的 spring容器
*/
public class HspWebApplicationContext {
//定义属性classFullPathList, 保存扫描包/ 子包的类的全路径
private List<String> classFullPathList = new ArrayList<>();
//用于存放反射后的bean对象
public ConcurrentHashMap<String,Object> ioc = new ConcurrentHashMap<>();
public void init(){
String basePackage = XMLParser.getBasePackage("myspringmvc.xml");
System.out.println("basePackage= " + basePackage);
String[] basePackages = basePackage.split(",");
//遍历basePackages, 进行扫描
if (basePackages.length > 0){
for (String pack : basePackages) {
scanPackage(pack);
}
}
System.out.println("扫描后的classFullPathList= " + classFullPathList);
executeInstance();
System.out.println("扫描后的= " + ioc);
}
//创建方法,完后对包的扫描
public void scanPackage(String pack){
//得到包的工作路径
URL url = this.getClass().getClassLoader()
.getResource("/" + pack.replaceAll("\\.", "/"));
System.out.println("url= " + url);
//根据得到的路径, 对其进行扫描,把类的全路径,保存到classFullPathList
String path = url.getFile();
System.out.println("path= " + path);
File dir = new File(path);
for (File f : dir.listFiles()) {
if (f.isDirectory()) {
//如果是一个目录,需要递归扫描
scanPackage(pack +"."+ f.getName());
}else{
//是一个文件的话,先把文件的全路径保存到集合,在注入对象到容器
String classFullPath =
pack +"."+ f.getName().replaceAll(".class","");
classFullPathList.add(classFullPath);
}
}
}
}
在HspDispatcherServlet.java 中增加如下方法
private HspWebApplicationContext hspWebApplicationContext;
@Override
public void init() throws ServletException {
hspWebApplicationContext = new HspWebApplicationContext();
hspWebApplicationContext.init();
}
=====================测试结果=======================
basePackage= com.zzti.myspringmvc.controller,com.zzti.myspringmvc.service
url= file:/D:/Exercise_items/hsp_springmvc/myspringmvc/target/classes/com/zzti/myspringmvc/controller/
path= /D:/Exercise_items/hsp_springmvc/myspringmvc/target/classes/com/zzti/myspringmvc/controller/
url= file:/D:/Exercise_items/hsp_springmvc/myspringmvc/target/classes/com/zzti/myspringmvc/controller/xx/
path= /D:/Exercise_items/hsp_springmvc/myspringmvc/target/classes/com/zzti/myspringmvc/controller/xx/
url= file:/D:/Exercise_items/hsp_springmvc/myspringmvc/target/classes/com/zzti/myspringmvc/service/
path= /D:/Exercise_items/hsp_springmvc/myspringmvc/target/classes/com/zzti/myspringmvc/service/
url= file:/D:/Exercise_items/hsp_springmvc/myspringmvc/target/classes/com/zzti/myspringmvc/service/impl/
path= /D:/Exercise_items/hsp_springmvc/myspringmvc/target/classes/com/zzti/myspringmvc/service/impl/
扫描后的classFullPathList= [com.zzti.myspringmvc.controller.GoodsController, com.zzti.myspringmvc.controller.MonsterController, com.zzti.myspringmvc.controller.xx.xxController, com.zzti.myspringmvc.service.impl.MonsterServiceImpl, com.zzti.myspringmvc.service.MonsterService]
完善HspWebApplicationContext,充当spring容器,实例化对象到容器中。将扫描到的类,在满足条件下(有相应的注解@Controller @Service),反射注入到ioc容器。
在HspWebApplicationContext.java中加入如下方法
//用于存放反射后的bean对象
public ConcurrentHashMap<String,Object> ioc = new ConcurrentHashMap<>();
//实例化扫描到的类->创建对象->放入到ioc容器
public void executeInstance(){
if (classFullPathList.size() == 0){
return;
}
try {
for (String classFullPath : classFullPathList) {
Class<?> clazz = Class.forName(classFullPath);
if (clazz.isAnnotationPresent(Controller.class)) {
//得到该类的类名
String beanName = clazz.getSimpleName().substring(0,1).toLowerCase() + clazz.getSimpleName().substring(1);
ioc.put(beanName,clazz.newInstance());
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
===========测试结果=============
扫描后的ioc= {monsterController=com.zzti.myspringmvc.controller.MonsterController@22ba6643}
完成请求URL和控制器方法的映射关系
将配置的@RequestMapping的url和对应的 控制器方法 映射关系保存到集合中
//将配置的@RequestMapping的url和对应的 控制器方法 映射关系保存到集合中
public class HspHandler {
private String url;
private Object controller;
private Method method;
public HspHandler() {
}
public HspHandler(String url, Object controller, Method method) {
this.url = url;
this.controller = controller;
this.method = method;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Object getController() {
return controller;
}
public void setController(Object controller) {
this.controller = controller;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
@Override
public String toString() {
return "HspHandler{" +
"url='" + url + '\'' +
", controller=" + controller +
", method=" + method +
'}';
}
}
在 HspDispatcherServlet.java中加入如下方法
//存放url和控制器-方法映射关系
private List<HspHandler> handlerList = new ArrayList<>();
@Override
public void init() throws ServletException {
hspWebApplicationContext = new HspWebApplicationContext();
hspWebApplicationContext.init();
initHandlerMapping();
System.out.println("handlerList= " + handlerList);
}
/**
* 1. 完成控制层url-->Controller-->方法的映射关系
* 2. 并放入到 handlerList集合中
* 3. 通过 handlerList集合 找到某个url请求对应的控制器方法
*/
private void initHandlerMapping(){
if (hspWebApplicationContext.ioc.isEmpty()) {
throw new RuntimeException("spring ioc 容器为空");
}
for (Map.Entry<String, Object> entry : hspWebApplicationContext.ioc.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
if (clazz.isAnnotationPresent(Controller.class)) {
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {
RequestMapping requestMapping = declaredMethod.getAnnotation(RequestMapping.class);
String url = requestMapping.value();
handlerList.add(new HspHandler(url,entry.getValue(),declaredMethod));
}
}
}
}
}
==============测试结果=======================
handlerList= [HspHandler{url='/list', controller=com.zzti.myspringmvc.controller.MonsterController@22ba6643, method=public void com.zzti.myspringmvc.controller.MonsterController.getListMonster(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)}]
完成HspDispatcherServlet分发请求到对应控制器方法
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//System.out.println("HspDispatcherServlet doPost()执行~~~");
executeDispather(request,response);
}
//根据request请求,返回对应的HspHandler对象
private HspHandler getHspHandler(HttpServletRequest request){
String requestURI = request.getRequestURI();
for (HspHandler hspHandler : handlerList) {
if (requestURI.equals(hspHandler.getUrl())) {
//匹配
return hspHandler;
}
}
//不匹配,就返回null
return null;
}
private void executeDispather(HttpServletRequest request,HttpServletResponse response){
HspHandler hspHandler = getHspHandler(request);
try {
if (hspHandler == null) {
response.getWriter().print("<h1>404 NOT FOUND</h1>");
}else {
hspHandler.getMethod().invoke(hspHandler.getController(),request,response);
}
} catch (Exception e) {
e.printStackTrace();
}
}
注意:
- 因为getRequestURI() 返回的是 主机后所有内容
- 为了和handler mapping中存放的url一致,
我们需要把Tomcat配置application context 修改成 /
- 否则匹配不上,按照原来得到的是 /myspringmvc/list
HspHandler找的是MonsterController中方法上的@RequestMappingurl路径,不包括类上面的@RequestMappingurl
三. 任务3-从web.xml动态获取myspringmvc.xml
因为之前是在HspWebApplicationContext中的init(),直接写的String basePackage = XMLParser.getBasePackage("myspringmvc.xml")
,不是动态获取的,现在改成从web.xml文件中动态获取自定义的myspringmvc.xml配置文件。
修改HspWebApplicationContext.java文件
//这个contextConfigLocation形式为 classpath:xx.xml
private String contextConfigLocation = "";
public HspWebApplicationContext(String contextConfigLocation){
this.contextConfigLocation = contextConfigLocation;
}
public void init(){
//String basePackage = XMLParser.getBasePackage("myspringmvc.xml");
String basePackage = XMLParser.getBasePackage(contextConfigLocation.split(":")[1]);
System.out.println("basePackage= " + basePackage);
String[] basePackages = basePackage.split(",");
//遍历basePackages, 进行扫描
if (basePackages.length > 0){
for (String pack : basePackages) {
scanPackage(pack);
}
}
System.out.println("扫描后的classFullPathList= " + classFullPathList);
executeInstance();
System.out.println("扫描后的ioc= " + ioc);
}
修改HspDispatcherServlet.java中的init(),增加一个参数servletConfig,用来获取web.xml配置文件中的contextConfigLocation
@Override
public void init(ServletConfig servletConfig) throws ServletException {
String contextConfigLocation = servletConfig.getInitParameter("contextConfigLocation");
hspWebApplicationContext = new HspWebApplicationContext(contextConfigLocation);
hspWebApplicationContext.init();
initHandlerMapping();
System.out.println("handlerList= " + handlerList);
}
同时在添加一个GoodsController.java
@Controller
public class GoodsController {
@RequestMapping("/goods/buy")
public void buy(HttpServletRequest request, HttpServletResponse response){
response.setContentType("text/html;charset=utf-8");
try {
PrintWriter writer = response.getWriter();
writer.write("<h1>购买商品~~</h1>");
} catch (IOException e) {
e.printStackTrace();
}
}
@RequestMapping("/goods/add")
public void add(HttpServletRequest request, HttpServletResponse response){
response.setContentType("text/html;charset=utf-8");
try {
PrintWriter writer = response.getWriter();
writer.write("<h1>添加商品~~</h1>");
} catch (IOException e) {
e.printStackTrace();
}
}
}
四. 任务4-完成自定义@Service注解功能
若给某个类加上@Service,则可以将其注入到我们的spring容器
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
String value() default "";
}
@Service
public class MonsterServiceImpl implements MonsterService {
@Override
public List<Monster> listMonster() {
List<Monster> list = new ArrayList<>();
list.add(new Monster(100,"牛魔王","芭蕉扇"));
list.add(new Monster(200,"蜘蛛精","吐丝啦"));
return list;
}
}
public class Monster {
private Integer id;
private String name;
private String skill;
。。。。。。。
}
//实例化扫描到的类->创建对象->放入到ioc容器
public void executeInstance(){
if (classFullPathList.size() == 0){
return;
}
try {
for (String classFullPath : classFullPathList) {
Class<?> clazz = Class.forName(classFullPath);
if (clazz.isAnnotationPresent(Controller.class)) {
//得到该类的类名
String beanName = clazz.getSimpleName().substring(0,1).toLowerCase() + clazz.getSimpleName().substring(1);
ioc.put(beanName,clazz.newInstance());
}else if (clazz.isAnnotationPresent(Service.class)) {
Service serviceAnnotation = clazz.getAnnotation(Service.class);
String beanName = serviceAnnotation.value();
if ("".equals(beanName)) {
//如果@Service没有指定value,得到该Service的所有接口名(首字母小写)
//相当于可以通过该类的多个接口名来注入该Service实例
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> anInterface : interfaces) {
String beanName2 = anInterface.getSimpleName().
substring(0,1).toLowerCase() + anInterface.getSimpleName().substring(1);
ioc.put(beanName2,clazz.newInstance());
}
}else {
//若指定了value, 就直接放入spring容器中
ioc.put(beanName,clazz.newInstance());
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
================测试结果==========================
扫描后的ioc= {goodsController=com.zzti.myspringmvc.controller.GoodsController@358b3b84, monsterService=com.zzti.myspringmvc.service.impl.MonsterServiceImpl@4f410d6b, monsterController=com.zzti.myspringmvc.controller.MonsterController@6fb35c58}
五. 任务5-完成Spring容器对象的自动装配-@Autowired
完成Spring容器中的对象的注入/自动装配,加入@Autowried注解,进行对象属性的装配
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
String value() default "";
}
@Controller
@RequestMapping(value = "/monster")
public class MonsterController {
@Autowired
private MonsterService monsterService;
@RequestMapping(value = "/list")
public void getListMonster(HttpServletRequest request, HttpServletResponse response){
response.setContentType("text/html;charset=utf-8");
try {
List<Monster> monsters = monsterService.listMonster();
StringBuilder content = new StringBuilder("<h1>妖怪列表</h1>");
content.append("<table width='500px' style='border-collapse:collapse' border='1px'>");
for (Monster monster : monsters) {
content.append("<tr><td>" + monster.getId() + "</td><td>" + monster.getName()
+ "</td><td>" + monster.getSkill() + "</td></tr>");
}
content.append("</table>");
PrintWriter writer = response.getWriter();
writer.write(content.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
在HspWebApplicationContext.java中添加executeAutoWired();并在iniit()中加入测试
public void executeAutoWired(){
if (ioc.isEmpty()) {
throw new RuntimeException("容器中,没有可以装配的bean");
}
//遍历ioc容器中所有,已经实例化的bean
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
String key = entry.getKey();
Object bean = entry.getValue();
Field[] declaredFields = bean.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
if (declaredField.isAnnotationPresent(Autowired.class)) {
Autowired annotation = declaredField.getAnnotation(Autowired.class);
String beanName = annotation.value();
//没有设置要注入的bean名字,则按照默认规则
if ("".equals(beanName)) {
Class<?> type = declaredField.getType();
//获取被标注@Autowired字段的类型名字
beanName = type.getSimpleName().substring(0,1).toLowerCase() + type.getSimpleName().substring(1);
}
declaredField.setAccessible(true);
//属性注入
try {
if (ioc.get(beanName) == null) {
throw new RuntimeException("容器中,没有可以装配的bean");
}
declaredField.set(bean,ioc.get(beanName));
}catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
public void init(){
//String basePackage = XMLParser.getBasePackage("myspringmvc.xml");
String basePackage = XMLParser.getBasePackage(contextConfigLocation.split(":")[1]);
System.out.println("basePackage= " + basePackage);
String[] basePackages = basePackage.split(",");
//遍历basePackages, 进行扫描
if (basePackages.length > 0){
for (String pack : basePackages) {
scanPackage(pack);
}
}
System.out.println("扫描后的classFullPathList= " + classFullPathList);
executeInstance();
System.out.println("扫描后的ioc= " + ioc);
//完成Spring容器中对象的自动装配/注入
executeAutoWired();
System.out.println("装配后,ioc容器= " + ioc);
}
六. 任务6-完成控制器方法获取参数-@RequestParam
@RequestMapping(value = "/find")
public void findMonster(HttpServletRequest request,
HttpServletResponse response,
@RequestParam(value = "name") String name){
response.setContentType("text/html;charset=utf-8");
try {
System.out.println("接收到的name= " + name);
List<Monster> monsters = monsterService.findMonster(name);
StringBuilder content = new StringBuilder("<h1>妖怪列表</h1>");
content.append("<table width='500px' style='border-collapse:collapse' border='1px'>");
for (Monster monster : monsters) {
content.append("<tr><td>" + monster.getId() + "</td><td>" + monster.getName()
+ "</td><td>" + monster.getSkill() + "</td></tr>");
}
content.append("</table>");
PrintWriter writer = response.getWriter();
writer.write(content.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
修改HspDispatcherServlet.java
private void executeDispather(HttpServletRequest request,HttpServletResponse response){
HspHandler hspHandler = getHspHandler(request);
try {
if (hspHandler == null) {
response.getWriter().print("<h1>404 NOT FOUND</h1>");
}else {
//将HttpServletRequest HttpServletResponse封装到参数数组
//1.得到目标方法的所有参数(形参)信息
Class<?>[] parameterTypes = hspHandler.getMethod().getParameterTypes();
//2.创建一个参数(实参)数组
Object[] params = new Object[parameterTypes.length];
//3.遍历parameterTypes形参数组,根据形参数组信息,将实参填充到实参数组
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
//将形参填充到实参数组中
if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
params[i] = request;
}else if("HttpServletResponse".equals(parameterType.getSimpleName())){
params[i] = response;
}
}
//匹配成功,反射调用控制器的方法
/**
* 1.下面这样写法,只适用于目标方法参数是HttpServletRequest request, HttpServletResponse response
* 2.将需要传递给目标方法的实参=>封装到参数数组中=>然后以反射调用的方式传递给目标方法
*/
//hspHandler.getMethod().invoke(hspHandler.getController(),request,response);
hspHandler.getMethod().invoke(hspHandler.getController(),params);
}
} catch (Exception e) {
e.printStackTrace();
}
}
在方法参数指定@RequestParam的参数封装到参数数组,进行反射调用
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
String value() default "";
}
public interface MonsterService {
public List<Monster> listMonster();
public List<Monster> findMonster(String name);
}
@Override
public List<Monster> findMonster(String name) {
List<Monster> list = new ArrayList<>();
list.add(new Monster(100,"牛魔王","芭蕉扇"));
list.add(new Monster(200,"蜘蛛精","吐丝啦"));
list.add(new Monster(300,"蜘蛛精2","吐丝啦"));
list.add(new Monster(400,"蜘蛛精3","吐丝啦"));
list.add(new Monster(500,"蜘蛛精4","吐丝啦"));
List<Monster> findMonsters = new ArrayList<>();
for (Monster monster : list) {
if (monster.getName().contains(name)) {
findMonsters.add(monster);
}
}
return findMonsters;
}
修改HspDispatcherServlet.java
//编写方法,返回请求参数是目标方法的第几个形参
public int getIndexRequestParameterIndex(Method method,String name){
//得到method的所有形参参数
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
//取出当前的形参参数
Parameter parameter = parameters[i];
//判断parameter是不是有@RequestParam注解
boolean annotationPresent = parameter.isAnnotationPresent(RequestParam.class);
if (annotationPresent) {
//有@RequestParam注解,就取出当前这个参数
RequestParam requestParameterAnnotation = parameter.getAnnotation(RequestParam.class);
String value = requestParameterAnnotation.value();
if (name.equals(value)) {
//找到请求的参数,对应的目标方法的形参的位置
return i;
}
}
}
return -1;
}
//编写方法,得到目标方法的所有形参的名称,并放入到集合中
public List<String> getParameterNames(Method method){
List<String> parameterList = new ArrayList<>();
Parameter[] parameters = method.getParameters();
for (Parameter parameter : parameters) {
parameterList.add(parameter.getName());
}
System.out.println("目标方法的形参列表= " + parameterList);
return parameterList;
}
private void executeDispather(HttpServletRequest request,HttpServletResponse response){
HspHandler hspHandler = getHspHandler(request);
try {
if (hspHandler == null) {
response.getWriter().print("<h1>404 NOT FOUND</h1>");
}else {
//将HttpServletRequest HttpServletResponse封装到参数数组
//1.得到目标方法的所有参数(形参)信息
Class<?>[] parameterTypes = hspHandler.getMethod().getParameterTypes();
//2.创建一个参数(实参)数组
Object[] params = new Object[parameterTypes.length];
//3.遍历parameterTypes形参数组,根据形参数组信息,将实参填充到实参数组
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
//将形参填充到实参数组中
if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
params[i] = request;
}else if("HttpServletResponse".equals(parameterType.getSimpleName())){
params[i] = response;
}
}
//将http请求参数封装到params数组中(要注意参数顺序)
Map<String, String[]> parameterMap = request.getParameterMap();
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
String name = entry.getKey();
String value = entry.getValue()[0];
int indexRequestParameterIndex = getIndexRequestParameterIndex(hspHandler.getMethod(), name);
if (indexRequestParameterIndex != -1) {
params[indexRequestParameterIndex] = value;
}else {
//没有找到@RequestParam注解,就使用默认的机制进行匹配
//1.得到目标方法的所有形参名称
List<String> parameterNames = getParameterNames(hspHandler.getMethod());
//2.对得到的目标方法的所有形参进行遍历
for (int i = 0; i < parameterNames.size(); i++) {
if (name.equals(parameterNames.get(i))) {
params[i] = value;
break;
}
}
}
}
//匹配成功,反射调用控制器的方法
/**
* 1.下面这样写法,只适用于目标方法参数是HttpServletRequest request, HttpServletResponse response
* 2.将需要传递给目标方法的实参=>封装到参数数组中=>然后以反射调用的方式传递给目标方法
*/
//hspHandler.getMethod().invoke(hspHandler.getController(),request,response);
hspHandler.getMethod().invoke(hspHandler.getController(),params);
}
} catch (Exception e) {
e.printStackTrace();
}
}
七. 任务7-完成简单视图解析
通过方法返回的String转发或者重定向到指定页面
@RequestMapping("/login")
public String login(HttpServletRequest request,
HttpServletResponse response,
String loginName){
boolean login = monsterService.login(loginName);
request.setAttribute("loginName",loginName);
if (login){
//重定向
//return "redirect:/loginOk.jsp";
//默认是转发
//return "liginOk.jsp";
//转发
return "forward:/login_ok.jsp";
}else {
return "forward:/login_error.jsp";
}
}
<body>
<h1>妖怪登录页面</h1>
<form action="/monster/login" method="post">
妖怪名: <input type="text" name="loginName"/><br/>
<input type="button" value="登录"/>
</form>
</body>
在HspDispatcherServlet.java中的executeDispather()最后面加上如下代码
Object result = hspHandler.getMethod().invoke(hspHandler.getController(), params);
if (result instanceof String) {
String viewName = (String) result;
if (viewName.contains(":")) {
//说明是forward:/xx 或者是 redirect:/xxx
String viewType = viewName.split(":")[0];
String viewPage = viewName.split(":")[1];
if ("forward".equals(viewType)) {
request.getRequestDispatcher(viewPage).forward(request,response);
}else if ("redirect".equals(viewType)){
response.sendRedirect(viewPage);
}else {
//默认是转发
request.getRequestDispatcher(viewName).forward(request,response);
}
}
}
八. 任务8-完成返回json格式数据-@ResponseBody
在pom.xml文件中引入jackson jar 包
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}
//返回json格式数据
@RequestMapping("/json")
@ResponseBody
public List<Monster> listMonsterByJson(HttpServletRequest request,
HttpServletResponse response){
List<Monster> monsters = monsterService.listMonster();
return monsters;
}
json底层是一个List数组,所以要用数组来判断result的类型
Object result = hspHandler.getMethod().invoke(hspHandler.getController(), params);
if (result instanceof String) {
String viewName = (String) result;
if (viewName.contains(":")) {
//说明是forward:/xx 或者是 redirect:/xxx
String viewType = viewName.split(":")[0];
String viewPage = viewName.split(":")[1];
if ("forward".equals(viewType)) {
request.getRequestDispatcher(viewPage).forward(request,response);
}else if ("redirect".equals(viewType)){
response.sendRedirect(viewPage);
}else {
//默认是转发
request.getRequestDispatcher(viewName).forward(request,response);
}
}
}else if (result instanceof ArrayList){
//返回json格式数据
Method method = hspHandler.getMethod();
if (method.isAnnotationPresent(ResponseBody.class)) {
//把返回值转成json字符串(使用Jackson工具类)
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(result);
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print(json);
writer.flush();
writer.close();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
9. 数据格式化
在我们提交数据(表单)Spring MVC怎样对提交的数据进行转换和处理的
1.基本数据类型可以和字符串之间完成转换
<h1>Spring MVC[数据格式/验证等]</h1>
<hr/>
<a href="addMonsterUI">添加妖怪</a>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<title>Spring MVC[数据格式/验证等]</title>
</head>
<body>
<form:form action="save" method="post" modelAttribute="monster">
妖怪名字:<form:input path="name"/><br/><br/>
妖怪年龄:<form:input path="age"/><br/><br/>
电子邮件:<form:input path="email"/><br/><br/>
<input type="submit" value="添加妖怪"/>
</form:form>
</body>
</html>
@Controller
public class MonsterController {
@RequestMapping("/addMonsterUI")
public String addMonsterUI(Map<String,Object> map){
map.put("monster",new Monster());
return "datavalid/monster_addUI";
}
@RequestMapping("/save")
public String save(Monster monster){
System.out.println("--save--monster= " + monster);
return "datavalid/success";
}
}
2.特殊数据类型可以和字符串之间完成转换,使用注解
在Monster.java中加入以下属性
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@NumberFormat(pattern = "###,###,###")
private float salary;
若birthday和salary是按照格式输入,则通过,否则就报错400
10. 中文乱码处理
1.自定义中文乱码处理
在web.xml文件中配置
<!--配置自定义中文乱码处理器-->
<filter>
<filter-name>myCharacterFilter</filter-name>
<filter-class>com.zzti.springmvc.filter.MyCharacterFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myCharacterFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
package com.zzti.springmvc.filter;
import javax.servlet.*;
import java.io.IOException;
public class MyCharacterFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("utf-8");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
========================================================
--save--monster= Monster{id=null, email='123@163.com', age=21, name='张三', birthday=Sun Dec 12 00:00:00 CST 1999, salary=123.12}
2.Spring提供的处理中文乱码过滤器
修改web.xml,换成Spring提供的过滤器,处理中文乱码
<!--使用Spring提供的过滤器,处理中文乱码-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>