基础知识简介
POJO:Plain Old Java Object
<!-- /和/*都是拦截所有请求
不同点:/不拦截.jsp请求,而/*会-->
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
前端控制器 DispatcherServlet
tomcat有编译功能,处理*.jsp是tomcat的事
所有项目的小web.xml 继承自服务器(tomcat)的大web.xml
大web.xml的DefaultServlet的<url-pattern>是"/"
大web.xml中还有一个JspServlet,用来拦截jsp文件
静态资源:除jsp和Servlet之外的都是静态资源
基础注解
@RequestMapping 标注位置
可以标注在类或者方法上面
@RequestMapping("/sdfsd/sdfgr")标注在类上面时就是为该类的所有方法加一层路径/sdfsd/sdfgr
@RequestMapping的各个属性:
- method:限定请求方式:GET、POST等等请求方式,默认值是全部接受
- params:规定请求参数
- params:eg:params={“username”}:表示发送请求时必须带上名为username的参数,没带则404,{“username=123”}或者{“username!=123”}都是可以使用的(规定请求信息中必须有username且值为123或不为123)
- !params:eg:params={"!username"}:必须不带username参数
- headers:规定请求头
- User-Agent:描述浏览器信息,比如说可以区分火狐和Chrome
- 等等的header参数都可以进行设置
- headers={“User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36”}意为只允许Chrome访问
- 支持Ant风格,即支持基本的正则匹配
- ?:代表一个字符
- *:代表多个字符或者一层路径
- **:代表任意多层路径
- 例如:@RequestMapping("/**/hello")表示接收任意路径的hello请求
@PathVariable 映射URL绑定的占位符
可以有RequestMapping("/hello/{id}") 这种形式,其中{}起占位符的作用,id是该占位符的名字,占位符和Ant风格的"*"有相似点,都可以代表不同名字。不同之处是,占位符所代表的值到后面还可以拿到。方式为:
@RequestMapping("/hello/{id}")
public void hi(@PathVariable("id")String id){
System.out.println(路径上的占位符是:""+id);
}
REST风格
概念解释
-
REST:Representational State Transfer (资源表现层转化)是目前互联网最流行的一种互联网架构。
-
资源(Resources):网络上的一个实体,或者说是网络上的一个具体信息。URI(统一资源定位符)是每一个资源的独一无二的识别符
-
表现层(Representation):把资源具体呈现出来的形式。
-
状态转化(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。HTTP协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器发生"状态转化"(State Transfer)。而这种转化时建立在表现层之上的,所以就是"表现层状态转化"。具体说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。他们分别对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。
-
对比
- 一般方式:
- /getBook?id=1:查询1号图书
- REST风格:
- /book/1:使用GET方式发送,查询1号图书
- 简洁的URI提交请求,以请求方式区分对资源的操作
- 一般方式:
问题:页面上只能发起GET、POST两种请求
-
解决办法:使用Rest来构建增删改查系统
-
如何从页面发出DELETE和PUT请求
-
SpringMVC中有一个Filter,他可以把普通的请求转化为规定形式的请求;配置(web.xml):
<filter> <filter-name>smart</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>smart</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
发出请求:
- 创建POST类型的表单
- 表单中携带_method的参数(<input name="_method" value=“delete”>)
- 这个_method的值就是DELETE和PUT
-
Tomcat8.0及以上的版本不允许使用DELETE、PUT这些请求
解决办法:
在jsp文件的头上加 isErrorPage=“true”
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib url="http://java.sun.com/jsp/jstl/core" prefix="c" isErrorPage="true" %>
获取页面发送请求时所带的参数
@RequestParam
页面向后端发送请求时,一般会带有一些参数,怎么获取这些参数?
-
默认方式:直接给方法入参上写一个和请求所带参数名字相同的变量,这个变量就来接收请求参数的值
-
使用@RequestParam(value=“username”,required=false,defaultValue=“默认值”)来获取请求中的指定参数
@RequestMapping("\hello") public void hello(@RequestParam(value="username",required=false,defaultValue="你是猪")String userName){ System.out.println(userName); } /*@RequestParam中 value的值必须设置的,用来指定请求中的参数名; requied用来规定请求是否必须携带value指定的参数,非必须; defaultValue用来设置目标参数的默认值 */
和@PathVariable()的区别:
对请求url:/book/{user}?user=admin
@PathVariable(“user”)获取的是url路径上占位符中的值,
@RequestParam(“user”)获取的是url中参数的值,即?之后的值
@RequestHeader
获取请求头中的某个key的值
使用方式与@PathVariable一样,都是在方法的参数前面加,注解的参数也都一致
@RequestHeader(“User-Agent”,required=false,defaultValue=“默认值”)
等同于HttpServletRequest request:request.getHeader(“User-Agent”)
@RequestMapping("\hello")
public void hello(@RequestHeader("User-Agent")String User-Agent){
System.out.println(User-Agent);
}
// 这二者是等价的
@RequestMapping("\hello")
public void hello(HttpServletRequest request){
String userAgent=request.getheader("User-Agent");
System.out.println(User-Agent);
}
@CookieValue
获取某个cookie的值
使用方式都与上面两个都一致,
@CookieValue(“JSESSIONID”,required=false,defaultValue=“默认值”)
第一次访问某个页面时,Headers中没有JSESSIONID
传入POJO
- 如果我们的请求参数是一个POJO
- 这句话是指,我们在请求中出=传入的一系列数据恰好可以构成一个对象
- 对下面这个例子来说就是请求中同时传入了name、author、price、country、
province
SpringMVC会自动为这个POJO进行赋值
- 将POJO中的每一个属性,从request中获取出来,并封装即可
- 还可以级联封装
// book.java
class Book{
private String name;
private String author;
private double price;
private Address address;
//....省略setter和getter方法
}
// Address.java
class Address{
private String country;
private String province;
//...省略setter和getter方法
}
// Conroller.java
class Controller{
@RequestMapping("/Book")
public void addBook(Book book){
System.out.println("图书:"+book);
}
}
// 就是这样,直接把对象放到参数里面就可以
// Book类中有Address类型的成员变量address,也可以对address中的属性进行赋值,是所谓级联封装
// 注意:Book类中的成员变量的名字要和页面请求中的参数名一致
对原生API的支持
SpringMVC可以直接在参数上写原生API
-
HttpServletRequest
-
HttpServletResponse
-
HttpSession
下面几个不常用
-
java.security.Principal:和HTTP安全协议有关
-
Locale:国际化有关的区域信息
-
InputStream:request.getInputStream
-
OutputStream: response.getOutputStream
-
Reader:request.getReader
-
Writer:response.getWriter
乱码处理
提交的数据可能有乱码,首先要分清是请求乱码还是响应乱码
请求乱码:
GET请求:改Tomcat中的server.xml在8080端口处添加URIEncoding=“UTF-8”(Eclipse中是这样,Intellij中需要到tomcat的根目录下修改,具体百度)
POST请求:在web.xml中配置filter
<!--web.xml-->
<filter>
<filter-name>filter</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:解决响应乱码,但是这里只设置了字符类型,如果想要设置内容类型还得在发送响应时设置,下面有介绍-->
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
响应乱码
在发送响应时,设置内容类型和编码类型:
response.setContentType(“text/html;charset=utf-8”)
@RequestMapping(value = "/hello")
public String hello(HttpServletResponse response){
response.setContentType("text/html;charset=utf-8");
return "hello";
}
配置多个filter的情况下,按照filter的配置顺序进行处理.
处理编码问题的filter放在其他filter的前面才能有效的处理乱码问题
发送数据到页面
如何将数据输出到页面
- 使用原生API:Request和Session
- 在方法处传入Map,Model或者ModelMap类型的参数,给这些参数里面保存的所有数据都会放在域中.可以在页面获取(其实三者都放在了request域中)
// Conroller.java
class Controller{
@RequestMapping("/Book01")
public String addBook(Map map){
map.put("hello","你好");
System.out.println("图书:"+book);
return "success";
}
@RequestMapping("/Book03")
public String addBook(Model model){
model.addAttribute("hello","你好");
System.out.println("图书:"+book);
return "success";
}
@RequestMapping("/Book04")
public String addBook(ModelMap modelMap){
modelMap.addAttribute("hello","你好");
System.out.println("图书:"+book);
return "success";
}
}
三者之间的关系:对三者的类型(Map.getClass)进行输出后可以发现,三者的类型都是BindingAwareModelMap
BindingAwareModelMap中保存的信息都会放在请求域中
Map(jdk的interface) Model(spring的interface
ModelMap是一个类,继承了LinkedHashMap,明显是和Map是一脉 ,
-
设置方法的返回值为ModelAndView(信息放在了请求域中,将想要发送的信息放入ModelAndView中
@RequestMapping("/Book02") public ModelAndView addBook(ModelMap modelMap){ // 视图解析器会帮我们拼串最终得到页面的真实地址 ModelAndView model = new ModelAndView("success"); // 或者ModelAndView model=new ModelAndView(); // model.setViewName("success"); model.addObject("hello","你好坏"); return model; }
数据一般可以放在request,session,application中,session中的数据多了之后容易爆掉,application中的数据对所有人可见,所以一般都是把数据放在request中
-
SpringMVC中提供了一种可以临时给Session域中保存数据的方式:@SessionAttributes (只能标在类上)
@SessionAttributes(value=“hello”)
加上这个注解后,在给BindingAwareModelMap(即Map,Model,ModelMap),或者ModelAndView中保存key为"hello"的数据时,同时(就是必须先在某个方法中给BindAwareModelMap中添加一个值)会在Session中放一份
想指定多个value怎么办: @SessionAttributes(value={“hello”,“msg”})
@SessionAttributes中还有一个参数是type,用来指定数据的类型
不推荐使用,可能会引发异常,给Session中放数据时最好使用原生API
如果一定要使用:
- 保证隐含模型中有@SessionAttributes标注的key
- 如果隐含模型中没有,session还说有就一定要有(各种方法给他加进去),否则抛异常
-
@ModelAttribute
很复杂,现在已经没人用了
应用场景:数据库进行更新时,是全字段更新,如果我们只从网页拿到部分数据,直接用这些数据对数据库进行更新时,该条记录的其他值就会被设置为null,这显然是不允许的.
解决办法就是,提前将被更新的记录从数据库中拿出保存到一个对象中,然后对对象进行操作,更新需要更新的属性,最后再将该对象保存到数据库中.
- SpringMVC要封装请求参数的Book对象不应该是自己new出来的,而应该是从数据库中拿到的准备好的对象
- 再使用这个对象封装请求参数
@ModelAttribute标注位置:
- 参数:取出隐含模型(后面会讲)中保存的数据
- 方法:这个方法就会提前于类中其他方法运行(随便调用一个目标方法,该方法都会在目标方法之前运行),我们就可以这样提前查出数据库中的记录
使用方法:
- 首先要有一个从数据库中拿数据,并保存到隐含模型中的方法
- 之后要有从隐含模型中拿出数据对象,对对象进行更新,并保存到数据库.如果隐含模型中没有,Spring会自己new一个对象进行封装.如果在拿数据时没有指定key,那么Spring会以对应参数的返回值类型的首字母小写为key拿数据
@ModelAttribute public void getData(Model model){ // 从数据库中拿数据,保存到隐含模型中 // 通常的做法是将拿出的数据保存到对应的对象中 // 在将对象保存到Model(Map,ModelMap)中 // 此处就不详细写了,假装book就存储着拿出的数据 model.add("book1",book); // 以 book1 为key将数据对象book放入隐含模型中 } @RequestMapping("hello") // 在参数中,用key拿出数据对象 public void hello(@ModelAttribute("book1")Book book){ }
隐含模型
- Map,Model或者ModelMap三种类型都是对三者的类型(都是BindingAwareModelMap.
- 更加有意思的一点是,不论你这个类中有多少个方法,设置了多少个Map,Model或者ModelMap(一个方法中只能有一个),其实使用的都是同一个BindingAwareModelMap对象.
- BindingAwareModelMap在Spring运行过程中全程存在,被称为隐含模型.
视图解析
有前缀的返回值独立解析,不使用视图解析器进行拼串
forward转发
forward:转发到一个页面
@RequestMapping("/hello1")
public String hello1(){
return "forward:/success1.jsp";
}
// "forward:/success1"表示取当前项目路径(一般路径最后的部分是WEB-INF)下的success1,
// 一定加上"/"如果不加/,就表明使用相对路径,容易出错
@RequestMapping("/hello2")
public String hello2(){
return "forward:/hello1"
}
// 可以通过forward将请求从hello2转发到hello1
redirect重定向
原生的Servlet重定向需要加上项目名:response.sendRedirect("/项目名/hello1.jsp")
redirect:重定向的路径(不需要前面的项目名)SpringMVC会帮我们自动拼接项目名(一般路径最后的部分是WEB-INF).
@RequestMapping("/hello1")
public String hello1(){
return "redirect:/success.jsp";
}
@RequestMapping("/hello2")
public String hello2(){
return "redirect:/hello1";
}
// 可以通过redirect将请求从hello2重定向到hello1
返回其他路径下的jsp
我们配置好视图解析器之后,视图解析器会帮我们自动进行拼串,但是视图解析器中的前缀prefix路径是固定的,那我们想返回其他路径下的jsp文件该怎么办呢?
-
使用相对路径:"…"表示返回上一层路径
@RequestMapping("/hello1") public String hello1(){ //比如前缀是 WEB-INF/pages // 我们想返回WEB-INF/success.jsp // 那么"../success.jsp"就等同于WEB-INF/success.jsp return "../success.jsp"; }
-
返回值中使用前缀"forward",“redirect”
@RequestMapping("/hello1") public String hello1(){ // 我们想返回WEB-INF/success.jsp return "forward:/success.jsp"; }
一些原理
-
请求处理方法执行完成后,最终返回一个 ModelAndView 对象。对于那些返回 String,View 或 ModeMap 等类型的处理方法,SpringMVC 也会在内部将它们装配成一个 ModelAndView 对象,它包含了逻辑名和模型对象的视图
-
Spring MVC 借助视图解析器(ViewResolver)得到最终的视图对象(View),最终的视图可以是 JSP ,也可能是 Excel、JFreeChart 等各种表现形式的视图
-
对于最终究竟采取何种视图对象对模型数据进行渲染,处理器并不关心,处理器工作重点聚焦在生产模型数据的工作上,从而实现 MVC 的充分解耦
-
视图:视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户
国际化
使用国际化Jstl包
导入Jstl包后,在创建View时SpringMVC会自动改为创建JstlView
JavaWeb的国际化步骤:
得到一个Locale对象
使用ResourceBundle绑定国际化资源文件
使用ResourceBundle.getString(“key”);获取到国际化配置文件中的值
web页面的国际化,fmt标签库来做
<fmt:setLocale>
<fmt:setBundle>
<fmt:message>
JstlView 进行国际化的步骤:
-
写好国际化资源文件
-
让Spring管理国际化资源
<!-- 配置文件--> <!--让SpringMVC管理国际化资源文件:配置资源文件管理器--> <!--id 必须命名为"messageSource"--> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <!--basename 用来指定基础名,比如说国际化文件的全名是:i18n_zh_CN.properties,基础名就是:i18n--> <property name="basename" value="i18n"></property> </bean>
国际化资源文件命名规范:https://blog.youkuaiyun.com/qq_34419607/article/details/100114102
-
直接去页面使用<fmt:message>拿资源就可以
在页面中(jsp)使用fmt标签库
<%@ page language="java" contentType="text/html; charset=UTF-8"%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%--添加fmt标签库--%> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <!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> <h4>Sucess Page</h4> <br><br> <%--从properties文件中拿属性--%> <fmt:message key="i18n.username"/> <br><br> <fmt:message key="i18n.password"/> </body> </html>
注意:
-
一定要经过SpringMVC的视图解析流程(不能在地址栏中直接使用url的方式进行访问),SpringMVC会创建jstlView进行快速国际化
-
在返回值中,不能使用forward,redirect前缀,我们之前说过有前缀的返回值独立解析,不使用视图解析器进行拼串,使用这两个前缀,SpringMVC会创建另外两种View,而不是jstlView
在配置中进行页面转发
场景:因为jstl的使用必须要经过视图解析流程,那么就会有很多方法只是为了转发一下页面而存在,这没什么意义,解决办法就是在xml中进行配置.
// 就比如这种方法,没有意义嘛,纯粹为了进行转发页面而存在
@RequestMapping("/toLoginPage")
public String toLogin(){
return "login";
}
<!--path 指定哪个请求
view-name:指定映射给哪个视图-->
<mvc:view-controller path="/toLoginPage" view-name="login"></mvc:view-controller>
<!--当使用配置view-controller后,其他基于@RequestMapping注解的方法会失效,所以要在这里开始注解模式-->
<!--开启MVC注解驱动-->
<mvc:annotation-driven></mvc:annotation-driven>
现象:国际化信息的显示是按照浏览器带来的语言信息决定的(得到源码中看):
Locale locale = request.getLocale();// 获取到浏览器的区域信息
SpringMVC中区域信息是由区域信息解析器得到的:private Localeresolver localeresolver
默认会用一个AcceptHeaderLocaleResolver,所有用到区域信息的地方都是用AcceptHeaderLocaleResolver 获取的
实现点击链接切换国际化(只有大概描述)
1. 实现自己的区域解析器(实现AcceptHeaderLocaleResolver)
可以模仿默认的LocaleResolver去写
一些介绍:
-
AcceptHeaderLocaleResolver使用请求头的区域信息
-
FixedLocaleResolver 使用系统默认的区域信息
-
SessionLocaleResolver 从Session中拿区域信息,可以根据请求参数创建locale对象,把他放在Session中,页面根据Session中的信息进行相应的国际化改变,需要对Session中的属性进行设置,具体怎么设置可以看Session的源代码.
-
CookieLocaleResolver
2. 使用系统默认的拦截器(配合SessionLocaleResolver )
比如:LocaleChangeInterceptor ,先在配置文件中进行注册,LocaleChangeInterceptor 会帮我们将国际化信息从请求中拿出,放入Session中
什么时候用Filter,拦截器
- 如果某些功能需要其他组件配合完成,就使用拦截器
- Filter无法加入ioc容器中
- 其他情况可以写Filter(JavaWeb的三大组件,没有SpringMVC也能用)
补充
-
扩展
视图解析器根据方法的返回值得到视图对象
SpringMVC中有多个视图解析器,他们都会尝试能否得到视图对象
视图对象不同就可以具有不同功能
-
一定要学会看源代码,很多问题只要看过源代码之后就都不存在了.
-
看源代码时,要学会分辨哪些代码是有用的,哪些是默认的不能改变的,还有哪些是我们需要根据需求进行设置的
自定义视图
自定义视图和视图解析器的步骤:
-
编写自定义的视图解析器
// 实现自己的视图解析器,需要实现ViewResolver和Ordered 接口 public class MyViewResolver implements ViewResolver, Ordered { private int order=0; // 需要实现的方法只有一个,根据视图名返回相应的视图对象 @Override public View resolveViewName(String s, Locale locale) throws Exception { // 如果我们只想解析meinv 前缀的请求 if(s.startsWith("meinv")){ return new MyView(); }else{ return null; } } // 在配置文件中设置属性需要有setter方法,是为了后面在配置文件中对order进行设置 public void setOrder(int order) { this.order = order; } @Override public int getOrder() { return order; } }
-
编写视图实现类
// 自己的视图类,实现View 接口 public class MyView implements View { // 内容类型 @Override public String getContentType() { return "text/html"; } @Override public void render(Map<String, ?> map, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { System.out.println("之前保存的数据:" + map); // 需要设置内容类型,不然会乱码 httpServletResponse.setContentType("text/html"); // 一个简单的渲染逻辑 httpServletResponse.getWriter().write("哈哈<h1>你想看的,这里都没有!!<h1>"); } }
-
视图解析器必须放在ioc容器中
<!--servlet.xml中添加--> <bean class="com.spring.controller.MyViewResolver"> <!--设置MyViewResolver的优先级--> <property name="order" value="1"/> </bean>
InternalResourceViewResolver的优先级是最低的(order的值越大,优先级越低)