当前位置 博文首页 > m0_53222768的博客:52.SpringMVC
@一贤爱吃土豆
SpringMVC引言、开发流程、收参传参跳值、静态资源Json拦截器、跨域Rest、项目整合运行原理
名称 | 职责 |
---|---|
Model | 模型:即业务模型,负责完成业务中的数据通信处理,对应项目中的 service和dao |
View | 视图:渲染数据,生成页面。对应项目中的Jsp |
Controller | 控制器:直接对接请求,控制MVC流程,调度模型,选择视图。对应项目中的Servlet |
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 局部参数:声明配置文件位置 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc.xml</param-value>
</init-param>
<!-- Servlet启动时刻:可选 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
@Controller //声明这是一个控制器
@RequestMapping("/hello") //访问路径 ,等价于url-pattern
public class HelloController {
@RequestMapping("/test1") //访问路径
public String hello1(){
System.out.println("hello world");
return "index"; // 跳转:/index.jsp
}
@RequestMapping("/test2") //访问路径
public String hello2(){
System.out.println("hello c9");
return "views/users";// 跳转:/views/user.jsp
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 告知springmvc 哪些包中 存在 被注解的类 -->
<context:component-scan base-package="com.qf.controller"></context:component-scan>
<!-- 注册注解开发驱动 -->
<mvc:annotation-driven></mvc:annotation-driven>
<!-- 视图解析器
作用:1.捕获后端控制器的返回值="index"
2.解析: 在返回值的前后 拼接 ==> "/index.jsp"
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/"></property>
<!-- 后缀 -->
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
// id name gender
// http://localhost:8989/xxx/../test1?id=1&name=zzz&gender=false&birth=2018-12-12 12:20:30
@RequestMapping("/test1")
public String testParam1(
Integer id,
String name,
Boolean gender,
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")Date birth){
System.out.println("test param1");
return "index";
}
public class User {
private Integer id;
private String name;
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birth;
private Boolean gender;
//set/get ...
}
//http://localhost:8989/.../test2?id=1&name=zzz&gender=false&birth=2018-12-12 12:20:30
@RequestMapping("/test2")
public String testParam2(User user){
System.out.println("test param2");
System.out.println("user:"+user);
return "index";
}
<form>
......
<input type="checkbox" name="hobby" value="fb"/>足球
<input type="checkbox" name="hobby" value="bb"/>篮球
<input type="checkbox" name="hobby" value="vb"/>排球
</form>
//http://localhost:8989/.../test3?hobby=football&hobby=basketball
@RequestMapping("/test3")
public String testParam3(String[] hobby){
for(String h:hobby){
System.out.print(h+" ");
}
return "index";
}
public class UserList {
//private User[] users;
private List<User> users;
//set/get..
}
// <input type="text" name="users[0].id"/>
// post请求:http://...?users[0].id=1&users[0].name=zhangsan&users[0].birth=2018-12-12&users[1].id=2&....
@RequestMapping("/test4")
public String testParam4(UserList userList){
for(User user:userList.getUsers()){
System.out.println(user);
}
return "index";
}
<form action="/user/hello5" method="post">
用户名:<input type="text" name="users[0].name" value="zs1" /><br>
密码:<input type="text" name="users[0].password" value="1" /><br>
用户名:<input type="text" name="users[1].name" value="zs2" /><br>
密码:<input type="text" name="users[1].password" value="2" /><br>
<input type="submit" value="提交">
</form>
// {id} 定义名为id的路径;【/hello/{id}】的匹配能力和【/hello/*】等价
// http://localhost:8989/.../hello/10 {id}匹配到10
@RequestMapping("/hello/{id}")
// @PathVariable将{id}路径匹配到值赋给id参数
// 路径名和参数名相同则@PathVariable("id")可简写为 @PathVariable
public String testParam5(@PathVariable("id") Integer id){
System.out.println("id:"+id);
return "index";
}
// http://localhost:8989/.../hello/tom {username}匹配到tom
@RequestMapping("/hello/{username}")
public String testParam6(@PathVariable("username") String name){//将{username}路径匹配到的值赋给name参数
System.out.println("username:"+name);
return "index";
}
JSP : <%@page pageEncoding="utf-8" %>
HTML : <meta charset="UTF-8">
其次,tomcat中字符集设置,对get请求中,中文参数乱码有效.
Tomcat配置:URIEncoding=utf-8
最后,设置此filter,对post请求中,中文参数乱码有效.
<!-- 此过滤器会进行:request.setCharactorEncoding("utf-8"); -->
<filter>
<filter-name>encoding</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>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
@RequestMapping("/forw")
class ForwardController{
@RequestMapping("/test1")
public String testForward(){
System.out.println("test forward1");
// 转发跳转 /views/users.jsp
// return "views/users";//和下一行等价
return "forward:/views/users.jsp";
}
@RequestMapping("/test2")
public String testForward2(){
System.out.println("test forward2");
//转发到 /forw/test1
//return "forward:test1";//相对路径(转发到本类中的test1)
//转发到 /forw/test1
return "forward:/forw/test1"; //绝对路径
}
}
@RequestMapping("/redir")
class RedirectController{
@RequestMapping("/test1")
public String testRedirect1(){
System.out.println("test redirect1");
//重定向到 /redir/test1
//return "redirect:test1"; //相对路径(转发到本类中的test1)
return "redirect:/redir/test1";//绝对路径
}
@RequestMapping("/test2")
public String testRedirect2(){
System.out.println("test redirect2");
//重定向到 /views/users.jsp
return "redirect:/view/user.jsp";
}
}
//形参中 即可获得 request 和 session对象
@RequestMapping("/test1")
public String testData(HttpSession session,HttpServletRequest req,Integer id){
session.setAttribute("user",new User());
req.setAttribute("age", 18);
req.setAttribute("users",Arrays.asList(new User(),new User()));
//return "test2";
return "forward:/WEB-INF/test2.jsp";
}
//jsp中用EL表达式 取值即可
<fmt:formatDate value="${sessionScope.user.birth}" pattern="yyyy-MM-dd"/> <br/>
${sessionScope.user.birth} <br>
${requestScope.age}
//model中的数据,会在V渲染之前,将数据复制一份给request
@RequestMapping("/test")
public String testData(Model model){
model.addAttribute("name", "张三");
return "index";
}
//jsp中用EL表达式 取值即可
${requestScope.name}
//modelandview 可以集中管理 跳转和数据
@RequestMapping("/test")
public ModelAndView testData(){//返回值类型为ModelAndView
//新建ModelAndView对象
ModelAndView mv = new ModelAndView();
// 设置视图名,即如何跳转
mv.setViewName("forward:/index.jsp");
// 增加数据
mv.addObject("age",18);
return mv;
}
//jsp中用EL表达式 取值即可
${requestScope.age}
@Controller
@SessionAttributes({"gender","name"}) // model中的 name和gender 会存入session中
public class UserController {
@RequestMapping("/hello")
public String hello(Model m){
m.addAttribute("gender",true); // 会存入session
mv.addObject("name","zhj"); // 会存入session
return "index";
}
@RequestMapping("/hello2")
public String hello(SessionStatus status){
// 移除通过SessionAttributes存入的session
status.setComplete();
return "index";
}
}
<servlet>
<servlet-name>mvc9</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>mvc9</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<!--
额外的增加一个handler,且其requestMapping: "/**" 可以匹配所有请求,但是优先级最低
所以如果其他所有的handler都匹配不上,请求会转向 "/**" ,恰好,这个handler就是处理静态资源的
处理方式:将请求转会到tomcat中名为default的Servlet
-->
<mvc:default-servlet-handler/>
将/html/** 中 /**匹配到的内容,拼接到 /hhh/后
http://..../html/a.html访问/hhh/a.html。
<mvc:resources mapping="/html/**" location="/hhh/"/>
<!-- Jackson springMVC默认的Json解决方案选择是 Jackson,所以只需要导入jackson的jar,即可使用。-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
@Controller
public class JsonController{
@RequestMapping("/test1")
@ResponseBody //将handler的返回值,转换成json(jackson),并将json响应给客户端。
public User hello1(){
System.out.println("hello world");
User user = new User();
return user;
}
// @ResponseBody还可以用在handler的返回值上
@RequestMapping("/test2")
public @ResponseBody List<User> hello2(){
System.out.println("hello world");
List<User> users = Arrays.asList(new User(),new User());
return users;
}
// 如果返回值已经是字符串,则不需要转json,直接将字符串响应给客户端
@RequestMapping(value="/test3",produces = "text/html;charset=utf-8") //produces 防止中文乱码
@ResponseBody
public String hello2(){
System.out.println("hello world");
return "你好";
}
}
@Controller
@RestController
public class JsonController{
@RequestMapping("/test1")
public User hello1(){
System.out.println("hello world");
User user = new User();
return user;
}
//@ResponseBody还可以用在handler的返回值上
@RequestMapping("/test2")
public List<User> hello2(){
System.out.println("hello world");
List<User> users = Arrays.asList(new User(),new User());
return users;
}
}
class User{
private Integer id;
private String name;
private Boolean gender;
//set get
}
@RequestMapping("/users")
public String addUser(@RequestBody User user){//@RequestBody将请求体中的json数据转换为java对象
System.out.println("cap2");
System.out.println("Post user :"+user);
return "index";
}
var xhr = new XMLHttpRequest();
xhr.open("post","${pageContext.request.contextPath}/users?"+new Date().getTime());
xhr.setRequestHeader("content-type","application/json");//设置请求头
xhr.send('{"id":1,"name":"shine","gender":"true"}');//传递json串
//ajax
var user = {id:1,name:"shine"};
$.ajax({
url:'${pageContext.request.contextPath}/json2/test4',
type:'post',
contentType:"application/json",//声明请求参数类型为 json
data:JSON.stringify(user),// 转换js对象成json
success:function(ret){
console.log(ret);
}
});
public class User{
private Integer id;
private String name;
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date birth;
....
get/set
}
public class User{
@JsonProperty("new_id") //不再使用原属性名,而是 "new_id"
private Integer id;
private String name;
....
get/set
}
输出的json:{“new_id”:xx,"name":"xx"}
public class User{
private Integer id;
@JsonIgnore // 生成json时,忽略此属性
private String name;
....
get/set
}
输出json时: {"id":xx}
public class User{
private Integer id;
@JsonInclude(JsonInclude.Include.NON_NULL) // 若"name==null" 忽略此属性
private String name;
@JsonInclude(value= JsonInclude.Include.NON_EMPTY) // 若hobby长度为0或==null 忽略此属性
private List<String> hobby;
....
get/set
}
如果name=null,且 hobby长度为0,则输出json时:{"id":xx}
public class User {
private Integer id;
private String name;
@JsonSerialize(using = MySerializer.class)
private Double salary = 10000.126;//在输出此属性时,使用MySerializer输出
....
get/set
}
public class MySerializer extends JsonSerializer<Double> {
// value即 Double salary的值
@Override
public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// 将Double salary的值 四舍五入
String number = BigDecimal.valueOf(value).setScale(2, BigDecimal.ROUND_HALF_UP).toString();
// 输出 四舍五入后的值
gen.writeNumber(number);
}
}
<!-- FastJson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
<mvc:annotation-driven>
<!-- 安装FastJson,转换器 -->
<mvc:message-converters>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<!-- 声明转换类型:json -->
<property name="supportedMediaTypes">
<list>
<value>application/json</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
public class User implements Serializable{
@JSONField(serialize = false)
private Integer id;
@JSONField(name="NAME",serialzeFeatures = SerializerFeature.WriteNullStringAsEmpty)
private String name;
@JSONField(serialzeFeatures = SerializerFeature.WriteMapNullValue)
private String city;
@JSONField(format="yyyy/MM/dd")
private Date birth;
@JSONField(serializeUsing = MySerializer2.class)
private Double salary;
...
}
public class MySerializer2 implements ObjectSerializer {
@Override
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType,
int features) throws IOException {
Double value = (Double) object; // salary属性值
String text = value + "元";// 在salary后拼接 “元”
serializer.write(text); // 输出拼接后的内容
}
}
new User(1,null,null,new Date(),100.5);
// 如上对象,转换json:
{NAME:"",city:null,"birth":"2020/12/12","salary":"100.5元"}
public String xxx(){
try{
...
}catch(Exception1 e){
e.printStackTrace();
return "redirect:/xx/error1";
}catch(Exception2 e){
e.printStackTrace();
return "redirect:/xx/error2";
}
}
public class MyExResolver implements HandlerExceptionResolver{
/**
* 异常解析器:主体逻辑
* 执行时刻:当handler中抛出异常时,会执行:捕获异常,并可以跳到错误页面
*/
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
ex.printStackTrace();//打印异常栈
//创建一个ModelAndView
ModelAndView mv = new ModelAndView();
//识别异常
if (ex instanceof Exception1) {
mv.setViewName("redirect:/xxx/error1");
}else if(ex instanceof Exception2){
mv.setViewName("redirect:/xxx/error2");
}else{
mv.setViewName("redirect:/xxx/error");
}
return mv;
}
}
<!-- 声明异常解析器 -->
<bean class="com.baizhi.exception.resolver.MyExResolver"></bean>
public class MyInter1 implements HandlerInterceptor{
//主要逻辑:在handler之前执行:抽取handler中的冗余代码
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("pre~~~");
/*
response.sendRedirect("/springMVC_day2/index.jsp");//响应
return false;//中断请求
*/
return true;//放行,后续的拦截器或handler就会执行
}
//在handler之后执行:进一步的响应定制
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("post~~");
}
//在页面渲染完毕之后,执行:资源回收
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("after~~");
}
}
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/inter/test1"/>
<mvc:mapping path="/inter/test2"/>
<mvc:mapping path="/inter/test*"/> <!-- test开头 -->
<mvc:mapping path="/inter/**"/> <!-- /** 任意多级任意路径 -->
<mvc:exclude-mapping path="/inter/a/**"/> <!--不拦截此路径-->
<bean class="com.baizhi.interceptor.MyInter1"></bean> <!--拦截器类-->
</mvc:interceptor>
</mvc:interceptors>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<form action="${pageContext.request.contextPath }/upload/test1" method="post"
enctype="multipart/form-data">
file: <input type="file" name="source"/> <br>
<input type="submit" value="提交"/>
</form>
<!-- 上传解析器
id必须是:“multipartResolver”
-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 最大可上传的文件大小 单位:byte 超出后会抛出MaxUploadSizeExceededException异常,可以异常解析器捕获 -->
<property name="maxUploadSize" value="1048576"></property>
</bean>
@RequestMapping("/test1")
public String hello1(String username,MultipartFile source,HttpSession session) {
//文件的原始名称
String filename = source.getOriginalFilename();
//定制全局唯一的命名
String unique = UUID.randomUUID().toString();
//获得文件的后缀
String ext = FilenameUtils.getExtension(filename);//abc.txt txt hello.html html
//定制全局唯一的文件名
String uniqueFileName = unique+"."+ext;
System.out.println("唯一的文件名:"+uniqueFileName);
//文件的类型
String type = source.getContentType();
System.out.println("filename:"+filename+" type:"+type);
//获得 upload_file的磁盘路径 ==> 在webapp目录下创建一个目录"upload_file",且此目录初始不要为空,否则编译时被忽略
String real_path = session.getServletContext().getRealPath("/upload_file");
System.out.println("real_path:"+real_path);
//将上传的文件,存入磁盘路径中
//source.transferTo(new File("d:/xxxx/xxxx/xx.jpg"))
source.transferTo(new File(real_path+"\\"+uniqueFileName));
return "index";
}
<a href="${pageContext.request.contextPath}/download/test1?name=Koala.jpg">下载</a>
@RequestMapping("/test1")
public void hello1(String name,HttpSession session,HttpServletResponse response){
System.out.println("name:"+name);
//获得要下载文件的绝对路径
String path = session.getServletContext().getRealPath("/upload_file");
//文件的完整路径
String real_path = path+"\\"+name;
//设置响应头 告知浏览器,要以附件的形式保存内容 filename=浏览器显示的下载文件名
response.setHeader("content-disposition","attachment;filename="+name);
//读取目标文件,写出给客户端
IOUtils.copy(new FileInputStream(real_path), response.getOutputStream());
//上一步,已经是响应了,所以此handler直接是void
}