M:model,指javabean,分为
处理业务数据的实体类和
处理业务逻辑、数据访问的类(dao和service层对象)
V:视图,html、jsp页面
C:控制器,以前是servlet,是DispatcherServlet,是一个功能强大的封装servlet,又叫前端控制器
配置servlet:
<servlet>
<servlet-name>SpringMVCservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:springMVC.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>SpringMVCservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
/*匹配所有请求,/匹配除jsp页面的其他请求
<load-on-startup>1load-on-startup>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<context:component-scan base-package="ind.deng.mvc.controller">context:component-scan>
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<property name="prefix" value="/WEB-INF/templates/" />
<property name="suffix" value=".html" />
<property name="templateMode" value="HTML5" />
<property name="cacheable" value="false" />
<property name="characterEncoding" value="UTF-8" />
bean>
property>
bean>
property>
bean>
beans>
thymeleaf语法,使页面跳转(请求转发)时带有绝对路径,也就是所谓的“不写死”
<a th:href="@{/send}">跳转a>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sBFy6XUm-1639117403850)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211204232334103.png)]
请求映射路径必须唯一,不然根本起不来
@RequestMapping可以用在类上也可以用在方法上,最终跳转的路径是二者拼接起来的路径
用在类上常表示不同的模块
属性越多,匹配越精准。常用value和method
value必须设置。仅有value属性时可以省略value=而直接写值;value值可以是字符串数组,即多个地址都能映射到该控制器类/方法上
@RequestMapping(
value={"/a","/b"}
)
常用的值是get和post,偶尔put和delete,如果不设置则任意请求都可。参数是数组,每个值为枚举值,如{RequestMethod.GET,RequestMethod.POST},即可以设置多种请求方式
后面的restful即是value和method属性一同进行请求映射
方法不匹配会报405
可以使用@RequestMapping的派生注解GetMapping、PostMapping(put、delete同理),就不用标注method属性了
在表单中写put、delete不会生效,还是按get请求
请求参数必须同时满足才算映射成功。缺少会报400
可以?和&传参,但编译器可能爆红,所以也可以如下在括号内键入值
请求头,如Host、Cookie、User-Agent等。也是必须全部匹配。和value一样,不匹配报404
废话不多说,见controller层代码
@RequestMapping("xxx/{id}/{name}")
public String test01(@PathVariable("id")Integer id,@PathVariable("name")String name){
```;return "xxx";
}
public String test02(HttpServletRequest request){
String name = request.getParameter("name");
System.out.prinln(name);
return "xxx";
}
一般情况下用的很少,有简便的方式(下面)为啥用原生的?你小子是在侮辱spring是吧。而且这种方法似乎不支持restful风格
控制器方法的形参和请求参数同名即可获取请求参数,拿到直接使用。
即使是复选框这种有多个name的也能String或String[]获取
前端:
用“,”连接数组
也可以用String[] hobbies接收参数,输出时要用Arrays.toString(hobbies)
若控制器方法的形参和请求参数不同名,可以用@RequestParam(“请求参数”) 控制器方法的形参 //有点鸡肋
这个注解还有别的值,name是value的别名,用法一样;
required代表这个参数是否必需,默认为true。
如果缺少required参数会报400;defaultValue="",如果不传参数或者传的空字符串则用默认参数
注:这三个参数下边两个注解也有,用法一样
用来获取请求头信息(Host、User-Agent)
session依赖于cookie,cookie是浏览器间的会话技术,session是服务器间的会话技术。session的生命周期为浏览器开启到浏览器关闭
小插曲:服务器可以在控制器方法中编写HttpSession session = request.getSession();浏览器请求服务器时,服务器会检查请求报文中是否有JSESSIONID。如果没有,即浏览器第一次访问。服务器会创建一个HttpSession对象,键为JSESSIONID,值为随机序列,并保存在服务器的一个Map集合中,键为JSESSIONID的随机序列,值为对应的HttpSession对象。服务器会把JSESSIONID:随机序列键值对(即cookie)作为响应报文的一部分返回给浏览器,浏览器存入本地,且每次访问这个服务器都会带上cookie,服务器就可以根据cookie值(随机序列)找到对应的HttpSession对象。
POJO里的属性和请求参数一致即可。简单省事,比较常用。
get请求不会乱码是因为在tomcat服务器下conf里的server.xml 配置端口号后面也配置了pageEncoding=utf-8 一劳永逸
而post会乱码。在controller方法中设置编码也没用,因为请求参数早在servlet创建时就获取到了,所以必须在servlet之前更改编码。
三大web组件的执行顺序是监听器、过滤器、servlet,所以可以在过滤器中统一设置编码
<filter>
<filter-name>CharacterEncodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>utf-8param-value>
init-param>
<init-param>
<param-name>forceResponseEncodingparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>CharacterEncodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
forceResponseEncoding的取值是false或true,我们既然用了就得设置为true,而不是写utf-8(傻逼的我…)。一开始犯了这个错项目起不来,还说日志文件有问题,我寻思也没配日志的依赖,就导入了个slf4j,结果slf4j帮我找到了出错信息。也就是,原生idea找不到错误(或者找出来没有提示),把锅甩给日志,让日志提示。日志总有用的,所以不要忘了引入日志依赖
request:范围:一次请求
session:范围:一次会话,浏览器开启到浏览器关闭
servletContext(application)范围:服务器开启到服务器关闭
WEB-INF下的静态页面重定向访问不了,只能通过转发。转发可以通过request共享数据
@RequestMapping("test05")
public String test05(HttpServletRequest request){
request.setAttribute("hello","world!");//键名不能有?、$、()等特殊符号
return "send";
}
页面接收参数:
<p th:text="${hello}">p>
如果使用了th:text,则p标签内的文字不会被渲染到页面,取而代之的是从服务器得到的数据。
h e l l o 获 取 的 是 键 为 h e l l o 的 r e q u e s t 参 数 值 , 其 实 是 r e q u e s t . {hello}获取的是键为hello的request参数值,其实是request. hello获取的是键为hello的request参数值,其实是request.{hello}的简写。如果是session对象,则要写session.${hello}。application同理。
@RequestMapping("test06")
public ModelAndView test06(){//返回值必须为ModelAndView对象
ModelAndView mav = new ModelAndView();
mav.addObject("hello","砸瓦鲁多!");//等价于request.setAttribute();
mav.setViewName("send");//设置视图
return mav;
}
@RequestMapping("test07")
public String test07(Model model){
model.addAttribute("hello","世界!");
return "send";
}
往map中存值实际上就是往request域中存值
@RequestMapping("test08")
public String test08(Map<String,Object> map){
map.put("hello","Ckai!");
return "send";
}
和Model基本一致,除了把形参对象由Model换为ModelMap
获取对象类型,object.getClass().getName();
五种方法最终都会把数据封装到ModelAndView中,Model比较常用,当成map用就行还不用写泛型,简单省事
@RequestMapping("test09")
public String test09(HttpSession session){
session.setAttribute("hello","Ckaiichi!");
return "send";
}
控制器
@Controller
@SessionAttributes("person")//用在类上,和@ModelAttribute("person")引号中的值匹配
public class controller {
@RequestMapping("test10")
@ModelAttribute("person")
public ModelAndView test10(Person person){
ModelAndView mv = new ModelAndView();
mv.addObject("person",person);
mv.setViewName("jump");
return mv;
}
@RequestMapping("test11")
public String test11(@SessionAttribute("person") Person person){
System.out.println(person);
return "send";
}
}
前端页面
<form th:action="@{/test10}" method="post">
编号:<input type="text" name="id" placeholder="请输入编号" ><br/>
姓名:<input type="text" name="name" placeholder="请输入姓名" ><br/>
<input type="submit" name="提交">
form>
<a th:href="@{/test11}">点击跳转a>
这个例子好特么麻烦,自己琢磨的,还不怎么会用
application即ServletContext,有多种方法获取:
request.getServletContext();
session.getServletContext();
ServletConfig.getServletContext();
@RequestMapping("test12")
public String test12(HttpSession session){
ServletContext application = session.getServletContext();
application.setAttribute("hello","hello again!");
return "send";
}
常用session和request。session主要用于保存用户登录状态,但超过30min(默认,可更改)会失效。request包括存取参数、修改删除数据等。
ModelAndView既然有Model,当然也有单独的View。也是接口
常用的有转发视图InternalResourceView(前缀为forward:,原生jsp默认也是这个)、重定向视图RedirectView(前缀为redirect:).
没有前缀,如果是thymeleaf模板引擎则为ThymeleafView,通过转发方式跳转。
如果是jsp模板引擎则为InternalResourceView
既然默认转发方式,还用forward:干啥?
如果一个控制器方法处理完之后不是跳转到页面,而是要到另一个控制器方法,要匹配请求路径,而路径可没有前缀和.html之类的。
如果使用forward:,视图就会变为InternalResourceView,就不会使用ThymeleafView中视图解析器的规则,而是直接跳转到给定路径
转发: 一次请求;地址栏不改变;可以携带参数; 可以访问WEB-INF下的资源; 不能跨域;
重定向:两次请求;地址栏改变; 不可以携带参数;不可以访问WEB-INF下的资源;可以跨域;
ps:两次请求都是浏览器发出的
forward理论上可以访问WEB-INF下的文件,不需要跳转到请求方法(test05),但路径我不会写
@RequestMapping("test13")
public String test13(){
return "forward:/test05";
}
@RequestMapping("test14")
public String test14(){
return "redirect:/test05";
}
地址栏由test14变为test05[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DkuAkVk6-1639117403858)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211207020056515.png)]
如果一个请求方法只是用来转发到某一个页面,那么可以在mvc核心配置文件里配置试图控制器,省事
<mvc:view-controller path="/test15" view-name="/send">mvc:view-controller>
send会爆红,但不影响使用。
在url中输入xxxx/test15会跳转到send.html页面,但send页面如果有超链接之类点击全部404失效,所以还需要配置
<mvc:annotation-driven/>
功能超多
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/templates/"/>
<property name="suffix" value=".jsp"/>
<property name="order" value="1"/>
bean>
控制器方法没有变化
jsp可以通过el表达式获取上下文路径,比如在超链接中这样使用:
<a href="${pageContext.request.contextPath}/test15">xxxa>
hum,挺长的。pageContext是四个内置对象中范围最小的一个,也算派上用场了
jsp页面更改之后不必重新部署,保存后前端就会变化。thymeleaf不能,因为需要服务器解析。html应该也不用重新部署
是一种软件架构风格
Representational State Transfer,表现层状态转移
服务器中万物皆资源。资源以名词组织。用户请求这一服务器资源,如book,不论什么操作,请求路径中/book不会变,代表这一资源,而不同的操作才会变化,/book/add等等
不使用问号键值对传参,而是将发送给服务器的数据作为URL的一部分,使得整体风格一致
get查询、post添加、put更新、delete删除
先后执行三种方式:
@RequestMapping(value="/user",method = RequestMethod.GET)
public String test16(){
System.out.println("查询所有用户信息");
return "send";
}
@GetMapping("/user/{id}")
public String test17(){
System.out.println("根据id查询用户信息");
return "send";
}
@PostMapping("/user")
public String test17(String id,String name){
System.out.println("新增用户信息:"+id+":"+name);
return "send";
}
可以在@RequestMapping的method属性设置不同操作,但使用派生注解(例如@GetMapping)更方便
前端代码:
<a th:href="@{/user}">查询所有用户a>
<a th:href="@{/user/1}">根据id查询用户a>
<form th:action="@{/user}" method="post">
编号:<input type="text" name="id" placeholder="请输入编号" ><br/>
姓名:<input type="text" name="name" placeholder="请输入姓名" ><br/>
<input type="submit" name="提交">
form>
表单方法设置成put和delete并不起作用,仍是当成get处理
先要模拟。需要发送ajax请求(部分浏览器支持)或使用postman(万能)
教程中使用的是mvc自带的过滤器
<filter>
<filter-name>HiddenHttpMethodFilterfilter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilterfilter-class>
filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
看清楚,全类名中没有reactive,那是响应式相关的。爆红时想想是不是同名类引错了
这样就能用表单模拟put和delete请求了:
<form th:action="@{/user}" method="post">
<input type="hidden" name="_method" value="put">
编号:<input type="text" name="id" placeholder="请输入编号" ><br/>
姓名:<input type="text" name="name" placeholder="请输入姓名" ><br/>
<input type="submit" name="提交">
form>
type="hidden"是不需要用户看到所以隐藏,name为固定值,method也必须为post才行,看源码就知道。value可以为put、delete、patch,大小写都可,最后都会被转换为大写
模拟put和delete控制台会有中文乱码问题,解决方法只要把设置字符集的过滤器放在HiddenHttpMethodFilter之前就好了。
以防万一以后把设置字符集的过滤器永远放最上面
delete与此同理。但删除一般是超链接,老师说用js或vue先取消超链接的默认行为再像表单一样提交就行。
thymeleaf和restful结合使用:
xxxa>
或者分别解析再拼接
<a th:href=@{/employee/}+${employee.id}}>xxxa>
项目中引入vue后需要重新打包(package)或直接重新启动项目以加载static静态资源。而且需要开启
<mvc:default-servlet-handler/>
以处理静态资源,不然交给mvc前端控制器处理不了
资源先由DispatcherServlet处理,处理不了再让DefaultServlet处理,所以两个注解
<mvc:annotation-driven/>
<mvc:default-servlet-handler/>
都得加。
报文信息转换器,用来将请求报文转成java对象,或由java对象转成响应报文
用在控制器方法的形参上,用于获取请求体,即接收post请求传过来的参数(?key=value&key=value)。实际很少使用
不是注解。可以指定泛型如,也用在控制器方法的形参上,获取整个请求体,即还包含了请求头
用.getHeaders() 和 .getBody()
以后在微服务阶段经常用到,每个控制器方法上都有
以往要返回字符串并直接显示在浏览器上需要
PrintWriter out = response.getWriter();
现在直接在请求方法上加上@ResponseBody,return的字符串直接显示在页面上
直接返回一个对象的话会报错,需要导入jackson依赖,把对象以json形式显示在页面上
步骤:
1 加入依赖
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.12.5version>
dependency>
2 又是你啊小老弟
<mvc:annotation-driven/>
3 加@ResponseBody注解
4 返回对象
作为控制器方法返回值,用于文件上传和下载
无需引入新的依赖
控制器方法:
@RequestMapping("test20")
public ResponseEntity<byte[]> test20(HttpSession session) throws IOException {
ServletContext servletContext = session.getServletContext();
String realPath = servletContext.getRealPath("/images/1.jpg");//session获取应用上下文再获取文件绝对路径
InputStream is = new FileInputStream(realPath);
byte[]bytes = new byte[is.available()];//字节数组的大小为文件的字节数
is.read(bytes);//抛出IO异常
MultiValueMap<String,String> headers = new HttpHeaders();
headers.add("Content-Disposition","attachment;filename=1.jpg");//新建header
HttpStatus ok = HttpStatus.OK;//新建HttpStatus
ResponseEntity<byte[]> responseEntity = new ResponseEntity<byte[]>(bytes,headers,ok);
is.close();
return responseEntity;
}
需要引入
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.3.1version>
dependency>
还需要配置文件上传解析器:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
前端代码:
<form th:action="@{/test19}" method="post" enctype="multipart/form-data">
图片<input type="file" name="pic" ><br/>
<input type="submit" name="上传">
form>
控制器方法:
@RequestMapping ("/test19")
public String test19(MultipartFile pic,HttpSession session) throws IOException {
String filename = pic.getOriginalFilename();
ServletContext servletContext = session.getServletContext();//session就是用来获取application的
String path = servletContext.getRealPath("images");//application用来获取指定文件夹的真实路径
File file = new File(path);//指定文件夹若不存在则新建
if(!(file.exists())){
file.mkdir();
}
String finalPath= path+File.separator+filename;//最终路径
pic.transferTo(new File(finalPath));//抛出异常
return "send";
}
为了避免文件重名(如果重名会把原来的文件字节流覆盖)问题,可以用UUID+suffix生成新的文件名。
常用的函数:replaceAll(“before”,“after”);
创建一个类实现HandlerInterceptor接口以作为拦截器
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle被执行了");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle被执行了");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion被执行了");
}
}
前两个方法分别是控制器方法执行前后,第三个是页面加载完成(处理完模型和视图,视图渲染完毕)后。
第一个方法返回true代表放行该请求
多个拦截器执行顺序:
按xml文件中拦截器配置的顺序,
preHandle正序执行(0------>length-1),且每执行完一次都会为一个计数器赋值,每次+1;
postHandle逆序执行(length-1------>0);
afterCompletion也是逆序执行,之前的那个计数器每次-1
如果多个拦截器中有false。
false之前(xml文件中的配置顺序)的拦截器(包括本身)的preHandle都会正序执行,postHandle一个都不执行。afterCompletion逆序执行,但false的那个不会执行,即执行afterCompletion的次数比preHandle少一次。比如,四个拦截器,第三个为false其他都为true,则三个preHandle正序执行,两个afterCompletion逆序执行
配置拦截器
<mvc:interceptors>
<bean class="ind.deng.interceptor.MyInterceptor"/>
mvc:interceptors>
也可以在要当做拦截器的类上加上@Component然后这样配置
<mvc:interceptors>
<ref bean="MyInterceptor"/>
mvc:interceptors>
别忘了还要组件扫描。不扫描怎么能识别注解呢
这两种方式默认对所有请求拦截,不能自己配置
第三种方式可以从拦截中剔除不拦截的(如下,过滤所有但剔除主页)//其实/*只拦截一部分,/**才是拦截所有
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/*"/>
<mvc:exclude-mapping path="/"/>
<ref bean="myInterceptor"/>
mvc:interceptor>
mvc:interceptors>
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArithmeticException">errorprop>
props>
property>
<property name="exceptionAttribute" value="exception"/>
bean>
如果出现了ArithmeticException就会跳转到error页面(已经被视图解析器处理过了)
@ControllerAdvice//Component的派生注解
public class ExceptionController {
@ExceptionHandler(value={ArithmeticException.class,NullPointerException.class})
public String test01(Exception ex,Model model){
model.addAttribute("exception",ex);
return "error";
}
}