12.spring-mvc
M:model模型,封装和映射数据
V:view视图,页面显示工作(.jsp)
C:Controller控制器:显示某个网站的跳转逻辑
1.web.xml文件的写法:按照上图可以方面理解前端控制器
<!--
springmvc思想中有个前端控制器拦截所有请求,并智能派发
这个前端控制器其实是个servlet:DispatcherServlet,应该在web.xml中配置这个servlet拦截所有请求
-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--contextConfigLocation:指定spring配置文件路径-->
<param-name>contextConfigLocation</param-name>
<!--
servlet启动加载,servlet原本是第一次访问时创建对象
load-on-startup:服务器启动时创建对象,并且值越小,创建的优先级越高
-->
<param-value>classpath:ioc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--
/*和/都是拦截所有请求
/:会拦截所有请求,但是不会拦截jsp页面,能保证jsp页面正常访问
/*:范围更大,还会拦截jsp页面,jsp页面拦截后就不会再显示了
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
2.spring的配置文件:
<context:component-scan base-package="cn.com"></context:component-scan>
<!--配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--前缀-->
<property name="prefix" value="/WEB-INF/page/"></property>
<!--后缀-->
<property name="suffix" value=".jsp"></property>
</bean>
配置视图解析器的作用是在controller返回时是:前缀+返回字符串+后缀
3.登录页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
</head>
<body>
<a href="/hello">登录</a>
</body>
</html>
4.对应的controller代码:
@Controller
public class MyFirstController {
@RequestMapping("/hello")
public String myfirstCon() {
System.out.println("hello请求传到后台了,正在处理!");
return "success";
}
}
这里用到了视图解析器:
返回的是:前缀+success+后缀
即:/WEB-INF/page/success.jsp
5.对应的success.jsp代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>成功</title>
</head>
<body>
<div>成功!div</div>
成功
</body>
</html>
1.当浏览器输入地址:http://localhost:8080/spring_mvc/login.jsp,点击登录后
2.发现其跳转到success.jsp页面
结论:发现其地址栏没变:是http://localhost:8080/spring_mvc/hello,但是展示的却是success.jsp,其过程发生了转发
1.转发:
1.浏览器地址栏没有发生变化
2.他们是一次请求
3.他们共享Request域中的对象
4.可以转发到WEB-INF目录下(解释:WEB-INF目录是对外不开放的,不能直接访问:即http://localhost:8080/spring_mvc/WEB-INF/page/success.jsp,不能直接访问)
5.不可以访问工程外的资源
2.重定向:
1.浏览器地址栏会发生变化
2.两次请求
3.两个不能共享Request域中的对象
4.不能访问WEB-INF下的资源
5.可以访问工程外的资源
页面显示成功的细节:l
1.运行流程:
1.1:当客户端点击页面上的登录按钮时,会发送请求:http://localhost:8080/spring_mvc/hello
1.2:来到tomcat服务器
1.3:spring前端控制器收到所有请求
1.4:来看请求地址和RequestMapping标注的哪个匹配,来找到到底使用哪个类的哪个方法
1.5:前端控制器找到目标处理类和目标方法,直接利用反射执行方法
1.6:放啊执行完成后会有一个返回值;springmvc认为这个放回值就是要去的页面地址
1.7:拿到方法返回值后,用视图解析器进行字符串拼接得到完整的页面地址
1.8:拿到页面地址,前端控制器帮我们转发到页面;
2.@RequestMapping("hello")
2.1就是告诉springmvc:这个方法处理什么请求
2.2这个/可以省略,即使省略了,也是默认从当前项目开始的,不过还是加上比较好
3.配置springmvc前端控制器时,也可以不指定spring的配置文件位置:
web.xml中:
1.通常做法:配置springmvc的前端控制器
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:ioc.xml</param-value>----------------------------------->指定了spring的配置文件位置:因为spring启动时必须有对应的spring配置文件
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
2.也可以不指定配置文件位置:
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>----------------->不指定spring排位置文件位置,此时启动肯定会报错!
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
报错如下:
nested exception is java.io.FileNotFoundException: Could not open ServletContext resource [/WEB-INF/dispatcherServlet-servlet.xml]
发现如果不指定spring配置文件,会默认去/WEB-INF/目录下找xxx-servlet.xml
这个xxx是在web.xml中配置的前端控制器的<servlet-name>dispatcherServlet</servlet-name>的值
/:拦截所有请求,不拦截jsp页面,*.jsp页面
/*:拦截所有请求,拦截jsp页面,*.jsp页面
处理*.jsp是tomcat做的事:所有项目的小web.xml都是继承于大的web.xml
DefaultServlet是Tomcat中处理静态资源的?
除过jsp和servlet外剩下的都是静态资源
index.html:静态资源,tomcat都会在服务器下找到这个资源并返回
我们的前端控制器的/尽用了tomcat服务器中的DefaultServlet
1.服务器的大web.xml中有一个DefaultServlet是url-pattern=/
2.我们的配置中前端控制器url-pattern=/
静态资源会来到DispatcherServlet(前端控制器)看哪个方法的RequestMapping是这个index.html,没有,故无法访问到静态资源
3.为什么jsp又能访问,因为我们没有覆盖服务器中的JspServlet中的配置
4./* 直接就是拦截所有请求;我们写/;也是为了迎合后面Rest风格的URL路径
RequestMapping
1.写在类上:为该类上的所有方法定一个基准:
示例:
@Controller
@RequestMapping("/myFirstController")
public class MyFirstController {
@RequestMapping("/hello")
public String myfirstCon() {
System.out.println("hello请求传到后台了,正在处理!");
return "success";
}
}
该类的所有访问方法都是:/myFirstController+对应的方法名, @RequestMapping("/hello")的/可以省略,因为这里并不是简单的进行字符串拼接
2.RequestMapping的几个属性:
2.1value:路径
2.2method:处理对应的请求方式:默认是全部接收,是个数组
HTTP协议中所有的请求方式:
GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS,TRACE;
@RequestMapping(value = "/hello",method = {RequestMethod.GET,RequestMethod.POST})
只接收处理get和post请求
2.3params:规定请求参数
2.3.1:params :params = {"username"}必须带上固定的请求参数,要不找不到对应处理报404
@RequestMapping(value = "/hello",params = {"username"})
也可以带上具体值:
@RequestMapping(value = "/hello",params = {"username=123"})
2.3.2:!params :params = {"!username"},必须不带固定请求参数,要不找不到对应处理报404
@RequestMapping(value = "/hello",params = {"!username"})
也可以写具体值:
@RequestMapping(value = "/hello",params = {"username!=123"})
此时不带username:http://localhost:8080/spring_mvc/myFirstController/hello可以正常访问
或:http://localhost:8080/spring_mvc/myFirstController/hello?username=456带的值不是123也可以正常访问!
2.3.3:多条规则:
@RequestMapping(value = "/hello",params = {"username!=123","pwd","!age"})
必须同时满足上述的多条规则:username不等于123,且有pwd的参数,且没有age参数
2.4headers:固定请求头,可以写简单的表达式
例如简单需求:控制只能谷歌浏览器访问,请他浏览器访问不了,使用User-Agent进行控制,因为不同浏览器的User-Agent是不同的
火狐的User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
用法如下:
@RequestMapping(value = "/hello",headers = {"User-Agent=Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36"})
因为header是个数组,故也可以传入多个浏览器的User-Agent来进行控制
2.5consumes:只接受内容类型是哪种的请求,规定请求头中的Context-Type
2.6produces:告诉浏览器返回的内容类型是什么,给响应头头中加上Context-type:text/html;charset=UTF-8
RequestMapping的模糊匹配
1. ?:能替代任意一个字符
@RequestMapping(value = "/hello?")匹配一个字符:/hello一个字符都可以访问
模糊和精确多个匹配情况下,精确优先
比如:
@RequestMapping(value = "/hello?")
public string test1(){}
@RequestMapping(value = "/hello1")
public string test2(){}
当输入:/hello1时这两个都可以匹配,用的时精确匹配的方法,因为精确匹配优先
2. *:能替代任意多个字符和一层路径
@RequestMapping(value = "/hello*")
匹配到多个字符,比如/hello多个字符都可以访问
@RequestMapping(value = "/aa/*/hello")
3. **:能替代多层路径
@RequestMapping(value = "/aa/**/hello")
@PathVariable获取URL绑定的占位符
路径上可以有占位符:占位符的语法就是可以在任意路径的地方写一个{变量名}
/user/admin /user/wmd
路径上的占位符只能占用一层路径
具体示例:使用@PathVariable注解获取路径上占位符的值
@RequestMapping(value = "/hello/{path}")
public String myfirstCon(@PathVariable("path") String path) {
System.out.println("path:"+path);
return "success";
}
若用多层的,可以这么写:
@RequestMapping(value = "/{id}/{path}")
public String myfirstCon(@PathVariable("path") String path,@PathVariable("id")String id) {
System.out.println("path:"+path+"id:"+id);
return "success";
}
若有两个,一个是精确路径,一个是占位符
@Controller
@RequestMapping("/myFirstController")
public class MyFirstController {
@RequestMapping(value = "/hello/{path}")
public String myfirstCon(@PathVariable("path") String path) {
System.out.println("path:"+path);
return "success";
}
@RequestMapping(value = "/hello/aa")
public String myfirstCon1() {
System.out.println("精确匹配");
return "success";
}
}
此时输入;http://localhost:8080/spring_mvc/myFirstController/hello/aa
会走精确匹配的方法,因为精确匹配优先!
Rest请求
系统希望以非常简介的url地址来发送请求;
怎样表示对对一个资源的增删改查呢:以请求的方式来区分
那rest风格是什么样子的呢:
之前我们若要做增删改查URL如下:
/getBook?id=1 :查询1号图书
/deleteBook?id=1 :删除1号图书
/updateBook?id=1 :更新1号图书
/addBook :添加图书
而rest风格:
rest推荐url地址这么起名:/资源名/资源标识符
/book/1 :get请求--查询1号图书
/book/1 :put请求--更新1号图书
/book/1 :delete请求--删除1号图书
/book :post请求---添加图书
系统的URL地址就是这么设计即可,简洁的URL提交请求,以请求的方式区分对资源的操作
问题:
从页面上只能发送get/post请求,其他方式无法发送,那应该如何应用呢
springmvc如何获取请求带来的参数
springmvc获取请求带来的更重信息
1.默认方式获取请求参数:
直接给方法入参上写一个和请求参数名相同的变量,这个参数就来接收请求参数的值
例子如下:
@Controller
public class MyFirstController {
@RequestMapping("/hello")
public String hello(String username){
System.out.println("username:"+username);
return "success";
}
}
当路径输入:http://localhost:8080/spring_mvc/hello?username=wmd,输出username:wmd,可以获取到传来的值
但当输入:http://localhost:8080/spring_mvc/hello,输出username:null
结论:带-->有值 没带--->null
2.@RequestParam:获取请求参数,参数默认是必须带的:
value :指定要获取的参数的key
required:指定这个参数是否必须要带,布尔值
defaultValue:默认值。没带就是null
示例如下:
@RequestMapping("/hello")
public String hello(@RequestParam(value = "name") String username){
System.out.println("username:"+username);
return "success";
}
请求路径是:http://localhost:8080/spring_mvc/hello?name=wmd
输出:username=mwd
若没有该请求参数:http://localhost:8080/spring_mvc/hello
会直接报错:HTTP Status 400 - Required String parameter 'name' is not present
2.2也可以设置该参数不是必须(默认是必须)
2.3也可以设置没带的默值:
public String hello(@RequestParam(value = "name",required = false,defaultValue = "你没带") String username){}
请求地址是:http://localhost:8080/spring_mvc/hello,也不会报错,输出:username=你没带
注意点:
@RequestParam和@PathVariable的区别
如下实例
@RequestMapping("/hello/{name}")
public String hello(@RequestParam(value = "name") String username,@PathVariable("name")String name){
System.out.println("username:"+username);
return "success";
}
@RequestParam获取的是请求参数name的值,@PathVariable获取的是请求路径name的值,两者互不影响!
3.@RequestHeader获取请求头的信息
例如:获取浏览器请求头中User-Agent的值,不同浏览器的User-Agent值不同
@Controller
public class MyFirstController {
@RequestMapping("/hello")
public String hello(@RequestHeader("User-Agent") String userAgent){
//原生的servlet获取请求头做法;request.getHeader("User-Agent")
System.out.println("User-Agent:"+userAgent);
return "success";
}
}
若获取的是请求头中没有的信息: public String hello(@RequestHeader("wmd") String wmd){}因为请求头中没有该wmd信息,所以请求会报错和@RequestParam一样
@RequestHeader也有三个相同属性:用法和@RequestParam一致
value :指定要获取的参数的key
required:指定这个参数是否必须要带,布尔值
defaultValue:默认值。没带就是null
4.@CookieValue获取cookie中的某个值
4.1原生的获取方法:
cookies[] cks=request.getCookies();
for(Cookie ck:cks){
if(ck.getName().equals("JSESSIONID")){
String cv=ck.getValue();
}
}
4.2现在的做法
@RequestMapping("/hello")
public String hello(@CookieValue("JSESSIONID")String jession){
System.out.println("JSESSIONID:"+jession);
return "success";
}
若获取cookie没有的值:@CookieValue("wmd")String wmd cookie中没有这个key,所以会报错,@CookieValue也有三个对应的属性,用法和上述一致
value :指定要获取的参数的key
required:指定这个参数是否必须要带,布尔值
defaultValue:默认值。没带就是null
可以使用pojo(对象)来接收请求参数:
代码如下:如果页面传来的参数较多
实体类代码:
public class Person {
private String name;
private Integer age;
private Book book;
...
}
public class Book {
private String bookName;
private Double price;
...
}
页面代码:注意是name的值必须和实体类的属性值一致,且此处可以使用级联属性,即对象中如果包含对象,
<form action="hello">
姓名:<input name="name"></br>
年龄:<input name="age"></br>
//注意:必须使用book.bookName,级联属性来获取,若直接写成:bookName,会获取不到对应的参数值
书名:<input name="book.bookName"></br>
价格:<input name="book.price"></br>
<input type="submit" value="提交">
</form>
处理器代码:
@RequestMapping("/hello")
//在此处传入对象即可
public String hello(Person person){
System.out.println("person:"+person);
return "success";
}
springmvc可以直接在参数上写原生的API:
例子:
@RequestMapping("/hello")---->直接在方法上引入对应的原生对象即可
public String hello(HttpSession httpSession,
HttpServletRequest request,
HttpServletResponse response){
可以利用原生对象做原生的操作!
return "success";
}
注意;并不是支持所有的原生对象,支持以下对象:
1.HttpServletRequest
2.HttpServletResponse
3.HttpSession
4.Principal
5.locale:国际化的区域信息对象
6.InputStream
ServletInputStream inputStream = request.getInputStream();
7.OutPutStream
ServletOutputStream outputStream = response.getOutputStream();
8.Reader
BufferedReader reader = request.getReader();
9.Writer
PrintWriter writer = response.getWriter();
这里有个小问题是,idea创建的spring web maven项目,可能不能直接在类中使用原生对象,这是因为没有导入tomcat的servlet-api.jar,需要手动导下!
后台乱码的解决办法:
输出:
发现后台乱码:
1.请求乱码:即上述的java后台乱码
1.1GET请求:改tomcat的server.xml,位置:配置tomcat端口8080处,增加:URIEncoding="UTF-8"
1.2post请求:
在第一次请求参数前设置:
request.setCharacterEncoding("UTF-8")
2.响应乱码:
response.setContextType("text/html;charset=utf-8")
3.最好的解决办法:使用spring的filter
在web.xml中配置:
<!--配置spring过滤器解决请求乱码问题-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--encoding:指定请求POST请求乱码-->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<!--
forceEncoding:顺手解决响应乱码问题,response.setContextType("text/html;charset=utf-8")
值为布尔值:默认是false,当是true时,使用和上面配置的encoding一样的编码!
-->
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
乱码问题得以解决:
person:Person{name='吴孟达', age=18, book=Book{bookName='java开发', price=12.0}}
得出结论:
spring-web的开发步骤:
开始时,不管什么,先在web.xml
1.配置spring的前端控制器,并设置spring容器在tomat启动时就创建
<!--
springmvc思想中有个前端控制器拦截所有请求,并只能派发
这个前端控制器其实是个servlet:DispatcherServlet,应该在web.xml中配置这个servlet拦截所有请求
-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--
/*和/都是拦截所有请求
/:会拦截所有请求,但是不会拦截jsp页面,能保证jsp页面正常访问
/*:范围更大,还会拦截jsp页面,jsp页面拦截后就不会再显示了
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
2.配置spring的filter拦截器:解决请求的post乱码和响应乱码:
<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>
<!--
forceEncoding:顺手解决响应乱码问题,response.setContextType("text/html;charset=utf-8")
值为布尔值:默认是false,当是true时,使用和上面配置的encoding一样的编码!
-->
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3.解决get请求的乱码问题:
在tomcat的servlet.xml,8080端口处,加上:URIEncoding="UTF-8"
注意:
字符编码的filter一般都在其他的filter之前,因为是按照顺序执行的,若放在最后,之前的其他filter可能已经获取到编码,而后字符编码的filter设置就已经失效了