springMVC
javaWeb—SSM中最后控制层MVC框架
处理器方法返回值、静态资源处理
MVC框架主要就是按照MVC的设计模式,处理的数据model和视图的数据view都是通过处理器方法的返回值来实现的;同时视图解析对象功能很强大:属性前缀和后缀直接就只用写逻辑名称了;将安全的界面和lib放在WEB-INF下就只能服务器调用了
使用@Controller注解的处理器的处理方法使用@RequestMapping修饰,value就是访问的资源路径;在类上面使用@RequestMapping就可以指定模块路径;减少处理器方法的value的长度。处理器方法的返回值代表的是处理的结果;【处理器方法加上method属性其实就可以直接类似servlet的doGet或者doPost方法】;处理器方法的返回值常用的有4种类型: ModelAndView、String、无返回值void、返回自定义类型对象
适用范围
: 如果处理器方法处理完毕之后、需要跳转到其他的资源、并且又要在跳转的资源间传递数据,此时返回ModelAndView较好
如果要返回ModelAndView,处理器方法中就要有ModelAndView对象;1). 通过addObject方法将数据以键值对的方法存储到mv对象中【其实最终是放到了requestScope中,就是一种请求转发forward】 ,2). 使用setViewName指定要跳转的资源的名称
可以看出来,这个返回值就是适用于之前普通的Servlet将数据放入,然后将数据交给下一个资源使用的场景,如果处理器方法只是跳转但是不需要数据【比如之前修改学生后跳转执行显示页面】或者只是传递参数但是不需要跳转【AJAX请求】,这个时候就会出现model或者view多余;不适合使用
适用范围
: 处理器方法返回的String可以指定逻辑视图名,然后视图解析的完整的资源名;适用于只是跳转页面但是不需要携带任何数据的情景
返回内部逻辑资源视图名和ModelAndView相同,在容器中使用bean注册一个InternalResourceViewResolver对象即可,逻辑资源名和prefix和suffix结合即可; 返回的字符串会直接当作逻辑视图的名称
返回值直接就是代表的逻辑视图的名称; 框架内部解析
@RequestMapping(value = "/other.do",method = RequestMethod.GET)
public String doOther(){
System.out.println("执行了other.do的方法");
return "result"; //这里的效果和modelAndView的mv.setViewName("result");类似
}
//测试了一下,如果返回空,服务器会寻找的是other.jsp,也就是访问的地址的逻辑名称加上prefix和suffix
访问的结果
<a href="test/other.do.do'">只是跳转结果a>
访问查看是否跳转
//后台
执行了other.do的方法
//前台
msg数据 :
fun数据 :
just for test, don't care
可以看到确实返回值就是直接代表的是view界面的逻辑名称
如果在使用String的情况下,也想传递数据; 那么就直接将HttpServletRequest对象,之后将数据放到请求作用域中,因为内部是使用的请求转发
@RequestMapping(value = "/other.do",method = RequestMethod.GET)
public String doOther(HttpServletRequest req){
System.out.println("执行了other.do的方法");
req.setAttribute("msg","你好,Jning");
req.setAttribute("fun","doOther方法");
return "result";
}
-------------------result---------------------
msg数据 :你好,Jning
fun数据 :doOther方法
just for test, don't care
同时需要注意,如果配置了视图解析器,返回的就必须是逻辑名称,如果是完整的路径,就会出现错误,这里可以看看错误
return "/WEB-INF/view/result.jsp";
项目直接报错
HTTP状态 404 - 未找到
类型 状态报告
消息 文.件[/WEB-INF/view/WEB-INF/view/result.jsp.jsp] 未找到 ---->视图解析还是会加上前后缀
所以在用视图解析器的时候,必须是逻辑名称; 在IDEA中,当正确书写的时候,旁边会有提示,可以直接跳转到related view
适用范围
:处理AJax请求的时候使用void,因为不需要跳转页面和将数据放到RequestScope中,数据直接放到response中返回
之前使用AJAX的时候就是通过response直接返回传递给ajax数据,和视图view无关,Servlet的doGet方法也是void的
这里可以来演示ajax的请求,害,还是不手写util来封装json对象了,因为手工封装需要在前台使用eval,并且MIME类型是text/html,不是applicaion/json;这里直接使用jackson工具,首先导入依赖jackson-core,和jackson-databind ; 两个jar一起构成jackson的功能,如果要使用需要一起导入,不然无法正常使用【bind 结】 — 需要bind联系各个部分
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-coreartifactId>
<version>2.13.1version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.13.1version>
dependency>
使用也很简单,主要依赖的就是ObjectMapper的实例方法writeValueAsString方法来讲对象转为json格式
这里使用Junit测试一下结果
public class TestJson {
//测试一下jackson
@Test
public void testJackson() throws JsonProcessingException {
List<Student> stulist = new ArrayList<>();
stulist.add(new Student(1001,"李四","HC2001")); //直接使用匿名对象了
stulist.add(new Student(1002,"王五","HC2003"));
stulist.add(new Student(1003,"Cfeng","HC2001"));
String json = "";
json = new ObjectMapper().writeValueAsString(stulist);
System.out.println(json);
}
}
[{"stuno":1001,"stuname":"李四","stuclass":"HC2001"},{"stuno":1002,"stuname":"王五","stuclass":"HC2003"},{"stuno":1003,"stuname":"Cfeng","stuclass":"HC2001"}]
就是转为一个json的数组,这样就可以直接通过数组的方式取出,和之前的util结果类似,当时其实也是不需要len的,因为可以通过数组直接获取len
所以这里的处理器方法
@RequestMapping(value = "/ajax.do",method = RequestMethod.GET)
public void doAjax(HttpServletResponse resp, Student student) throws IOException {
//student用来接收上传的数据,这样就不用req来getParameter了
System.out.println("上传的数据是" + student.getStuno() + " : " + student.getStuname() + ": " + student.getStuclass());
//调用service处理 ……
//返回处理结果,假设是一个student的list
String json = ""; //避免空报错
List<Student> stulist = new ArrayList<>();
stulist.add(new Student(1001,"李四","HC2001")); //直接使用匿名对象了
stulist.add(new Student(1002,"王五","HC2003"));
stulist.add(new Student(1003,"Cfeng","HC2001"));
//将结果转为json返回给ajax;使用jackson
if(stulist != null) {
json = new ObjectMapper().writeValueAsString(stulist);
}
//输出json数据给ajax异步对象
resp.setContentType("application/json;charset=utf-8");//设置MIME格式,这里使用jackson,可以直接返回json
PrintWriter out = resp.getWriter();
out.println(json);
out.flush();
out.close();
}
前端页面获取这个对象进行处理;dataType进行处理转为json;处理之后的状态为4,status为200;注意:script引入的时候也是有结束标签的,不然运行不了,我在这个问题上卡了会才发现
这样就可以成功的进行ajax的处理
可以看到响应的就是json的对象;JQuery会自动将字符串给转化为json对象
提示
: 这里遇到了一个让人很不舒服的乱码问题,就是设置MVC的过滤器之后会出现另外的乱码问题,可能是版本不兼容的关系,所以这里可以手工实现过滤,使用过滤器之后欢迎页面都乱码了;并且GET请求的参数也乱码了;这里仔细思考之后; 决定换拦截路径,这里的问题暂时保留,因为删除过滤器再加又恢复,所以目前不知道原因【评论区回答】
适用范围
这里不是针对的某种情景,而是作为void类型的ajax请求的辅助,分析上面的代码,会发现又很多重复的部分
if(stulist != null) {
json = new ObjectMapper().writeValueAsString(stulist);
}
//输出json数据给ajax异步对象
resp.setContentType("application/json;charset=utf-8");//设置MIME格式,这里使用jackson,可以直接返回json
PrintWriter out = resp.getWriter();
out.println(json);
out.flush();
out.close();
就是这一块----将对象转化为json格式然后放入到响应体,这里就可以通过Object来进行简化
处理器方法可以返回Object对象,Object可以是Integer、String等多种类型,返回的对象不是作为逻辑视图出现,而是作为直接在页面进行显示的数据出现; 返回对象,需要使用@ResponseBody注解; 实现的步骤:
<mvc:annotation-driven/>
实现的方式
: springmvc处理器方法返回Object,可以转为json输出到浏览器,响应ajax: 注解驱动完成java对象对象到json等数据格式的转化,使用的是HttpMessageConvert接口—消息转换器;各种实现类分别转化为不同格式的数据(strategy pattern)//可以看看这个接口 public interface HttpMessageConverter<T> { boolean canRead(Class<?> clazz, @Nullable MediaType mediaType); boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType); List<MediaType> getSupportedMediaTypes(); default List<MediaType> getSupportedMediaTypes(Class<?> clazz) { return !this.canRead(clazz, (MediaType)null) && !this.canWrite(clazz, (MediaType)null) ? Collections.emptyList() : this.getSupportedMediaTypes(); } T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException; }
除了之间的方法,其余的刚好成对,read和canread;write和canwrite ----> 控制器类将结果输出给浏览器使用
- canwirte : 检查处理器方法是否可以将返回值能否转化为mediaType表示的数据格式,比如json、xml等,如果可以,就返回true
- wirte : 把处理器方法的返回值对象,调用jackson的objectMapper转为json格式字符串
开发常用的实现类是:
public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> { public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter { ---->使用jackson工具库来完成类型的转换
加入之后会多创建几个对象,就上面提到的对象
这里可以使用方法来返回对象来修改一下上面的Ajax的方法;不再使用void返回值
@RequestMapping(value = "/ajax.do",method = RequestMethod.GET)
@ResponseBody
public List<Student> doAjax(HttpServletResponse resp, Student student) {
//执行相关操作后需要返回数据; 这里加入直接后就和上面的String相同,都是被赋予了特定的含义,
// 不加注解的String就是逻辑名称;这里加入注解后的对象就是json返回的对象
List<Student> stulist = new ArrayList<>();
stulist.add(new Student(1001,"李四","HC2001")); //直接使用匿名对象了
stulist.add(new Student(1002,"王五","HC2003"));
stulist.add(new Student(1003,"Cfeng","HC2001"));
return stulist;
}
这里就和上面的String一样,只要返回就好了,框架内部自动进行处理,可以看看响应
HTTP/1.1 200
Content-Type: application/json ----> 返回的类型自动就是json格式
Transfer-Encoding: chunked
Date: Sat, 15 Jan 2022 09:45:08 GMT
Keep-Alive: timeout=20
Connection: keep-alive
a2 ---> 传输的数据都是字节流 ISO-8859-1
[{"stuno":1001,"stuname":"鏉庡洓","stuclass":"HC2001"},{"stuno":1002,"stuname":"鐜嬩簲","stuclass":"HC2003"},{"stuno":1003,"stuname":"Cfeng","stuclass":"HC2001"}]
0
这里的效果和上面的void是相同的,所以之后就直接加入annotation-driven再加入注解就可以自动转为json了,注意一定要加入jackson的依赖
框架内部的执行的过程:
1.框架会把返回的List类型,调用ArrayList中的每一个类的canWrite方法检查那个HttpMessageConvter接口的实现类能处理其中的数据,依赖的MappingJackson2HttpMessageConverter
2. 框架调用实现类的write方法,MappingJackson2HttpMessageConverter的write方法,将List对象转为json;嗲用jackson的ObjectMapper.writeValueAsString等方法实现
3. 框架会调用@ResponseBody将2中的数据输出到浏览器的响应体中,完成ajax请求
其实这里的注解加上之后就相当于是直接将返回值输出到响应体,类似于一个out.println(returnResult); 所以如果是对象,配合annotation-driven就会输出对象到响应体,如果是其他的数据,就直接输出到响应体【也即是数据】
如果返回非中文字符串,将前面返回数值型数据直接修改为字符串即可,但如果其中包含中文,接收的时候就会出现乱码,这个时候使用数学produces来指定字符串;produce:产品、结果,这个用于设置结果的类型【之前的value指代的路径,method指代访问的方式】
处理器方法的返回值为String类型,String表示的是数据还是视图;就看是否有注解@ResponseBody
比如这里直接输出字符串数据到响应体中
@RequestMapping(value = "/data.do",method = RequestMethod.GET)
@ResponseBody
public String doData(){
System.out.println("执行了data.do的方法");
return "你好,我是MVC,欢迎使用";
}
浏览器输出的结果是
???MVC??? ----出现了中文乱码,这个时候就要使用produces的属性了
HTTP/1.1 200
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 13
Date: Sat, 15 Jan 2022 10:30:04 GMT
Keep-Alive: timeout=20
Connection: keep-alive
?????MVC?????
可以看到错误的原因就是因为采用的编码方式为ISO-8859-1
这里修改的范式就是通过属性produce,可以重新设置MIME类型
@RequestMapping(value = "/data.do",method = RequestMethod.GET,produces = "text/plain;charset=utf-8")
这里就是设置了contentType,plain是无装饰的,表示的就是普通的text;框架内部完成这个也是通过的HttpMessageConverter接口的实现类完成转化,json是jackson的; 这里的String是StringHttpMessageConverter这个实现类来完成的【canwrite和write】 将字符串按照指定的编码来处理
设置了MIME类型之后就可以看到响应的contentType变化了
HTTP/1.1 200
Content-Type: text/plain;charset=utf-8
Content-Length: 33
Date: Sat, 15 Jan 2022 10:43:02 GMT
Keep-Alive: timeout=20
Connection: keep-alive
浣犲ソ锛屾垜鏄疢VC锛屾杩庝娇鐢?
其实这个属性的作用就和之前的使用response对象的setContentType是相同的
之前设置的url设置的是*.do后缀名的方式,可是实际开发中不一定有后缀,所以要采取另外的方式,除了这种扩展名的方式通配,还有就是/的方式
之前分析servlet的时候提过/*会匹配所有的请求,会覆盖所有的后缀,只用在filter中,包括动态和静态的资源;默认的default匹配静态资源请求和所有的未映射的请求;项目的/会覆盖default,导致静态资源访问出现问题,jsp还是正常的;/的优先级最低
Tomcat会处理静态资源【html、图片 — defalutservlet处理、jsp---- jspservlet处理】,之前动态资源是servlet处理,而现在使用MVC就是中央处理器处理
为社么Tomcat也可以处理这些静态的资源呢?这是因为Tomcat中有默认的servlet【这里只是截取小部分】
<servlet>
<servlet-name>defaultservlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServletservlet-class>
<load-on-startup>1load-on-startup>
处理静态资源以及未映射的其他servlet的请求
<servlet-mapping>
<servlet-name>defaultservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
<servlet>
<servlet-name>jspservlet-name>
<servlet-class>org.apache.jasper.servlet.JspServletservlet-class>
<load-on-startup>3load-on-startup>
<servlet-mapping>
<servlet-name>jspservlet-name>
<url-pattern>*.jspurl-pattern>
<url-pattern>*.jspxurl-pattern>
servlet-mapping>
可以看到有两个servlet,default匹配的就是/,就是所有的静态资源,服务器启动的时候就会创建;还有一个是jsp处理servlet,匹配所有的jsp请求,所有的jsp都是这个servlet处理,也是启动的时候创建对象
所以,说Tomcat可以处理请求不准确,因为其实真正处理请求的都是servlet;不管是静态还是动态;没有匹配上的servlet请求都是defaultservlet进行处理,比如访问项目中不存在的路径就是default处理的,返回的404
如果项目中的中央处理器的路径设置成 / , 那么就会自动替代defalut,静态资源本来应该是Tomcat的default处理,现在覆盖了,所以静态资源的访问就出现问题了;会出现404的错误
默认情况下Dispatcher是没有处理静态资源的能力的,没有控制器对象能处理静态资源的访问,所以html、js、图片等都是404; 但是动态资源是可以正常访问的
/ 和 /* 一般都可以认为是匹配所有 【都是全路径】
/ 优先级最低,只是比默认的default高,filter过滤器无用,只能用在servlet中;会覆盖default;default的路径就是/,其会处理所有的静态资源页面和未映射请求 【/是匹配所有,覆盖了default需要解决】
/* 优先级很高,比后缀方式高,会覆盖所有的后缀方式,所以伤害性高,如果中央处理器使用了,就真的会拦截所有的请求,包括tomcat中处理jsp的*.jsp也会覆盖,无法处理jsp的请求,一般只用在过滤器中
解决上面的/无法访问静态资源,其实就是让Tomcat的default这个servlet发挥作用,不被覆盖
再springmvc中加上这个声明之后,springmvc就会再容器中创建DefaultServletHttpRequestHandler处理器对象,也就是一个检查的对象,会检查所有的url,识别出所有的静态资源,然后转交给Web默认的default对象进行处理;
<!-- 解决静态资源覆盖问题,注册defaultServletHandler -->
<mvc:default-servlet-handler/>
可以看看这个类的内部的代码
public class DefaultServletHttpRequestHandler implements HttpRequestHandler, ServletContextAware {
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
rd.forward(request, response);
就是请求转发的过程
default-servlet-handler和@RequestMapping注解有冲突,需要加入之前的annotation-driven来解决问题
在spring中,spring专门定义了用来进行静态资源处理的ResourceHttpRequestHandler,添加了mvc:resource标签,专门解决静态资源无法访问的问题,也是需要在配置文件中配置【这种方式不依赖Tomcat服务器的default】
因为在项目开发的时候静态资源很多,所以不是直接就放在webapp下面,而是想js一样,都有一个一级目录;比如/images/…… /html/…… /js/……;同时也可以表示多级目录,**代表的是匹配任意字符
上面的第一种方式,如果不在配置文件中加入handler,就会发生报错
警告 [http-nio-8080-exec-1] org.springframework.web.servlet.DispatcherServlet.noHandlerFound No mapping for GET /MVCtest/
这里就是找不到handler,不能转发请求,不能正常访问静态资源。这里将html文件都放到html包下面,所以这里修改一下欢迎页面
<welcome-file-list>
<welcome-file>html/index.htmlwelcome-file>
welcome-file-list>
这里再配置文件中使用的resources标签用于处理静态资源的访问,location就是位置/XXX/即可,而mapping是映射就是访问的url的地址,其实就是前面的加上**就可以; /XXX/–
<!-- 这里是后台路径,所以加上/,会带上根路径 location表示的是目录位置; mapping代表的是访问的url地址 -->
<mvc:resources mapping="/html/**" location="/html/"/>
<mvc:resources mapping="/js/**" location="/js/"/>
这样就成功访问,需要设置welcome-list
resources和@RequestMapping注解有冲突,需要加入之前的annotation-driven来解决问题
这里其实还是有点繁琐,实际开发中,常常直接再webapp下面建一个static文件夹,然后将所有的静态资源放在static文件夹下面
static文件夹代表的就是静态资源文件,资源文件都是static开始的
这样就只用配置一个resources标签就可以了
<mvc:resources mapping="/static/**" location="/static/"/>
换一下欢迎页面的位置 : < welcome-file>static/html/index.html welcome-file>
这里之前分享过的,前台路径是以/开头的
没有/的就是直接资源开始的,是会发生变化的,与当前资源所在的位置有关系
这里就不再赘述