文章目录
SpringMVC
MVC设计模式
- MVC里面的M指的是:
- Model(通常包含bean、dao(mapper)、service);
- V指的是
- View:视图层,视图层主要的技术(JSP、HTML、FreeMaker、Themeleaf);
- C指的是
- Controller,控制层。控制层不负责具体数据、逻辑的处理和运算,它只负责将Model层的结果返回给对应的视图层去展示。
在JavaWeb阶段, Controller层指的就是Servlet; View层指的就是JSP或者HTML; Model层指的就是bean、dao、service。
在J2EE阶段,Controller层指的就是SpringMVC、Structs1\2; View层不变还是主流的页面展示技术; Model层包括bean、mybatis、service。
SpringMVC的优势
- SpringMVC是一款很轻量级的框架,要使用它的组件我们往往只需要定义一些最简单的Java类,然后添加某些注解就可以了
- SpringMVC的参数注入只直接注入到方法中,可以很好的做到不同请求间数据的隔离,而Struts2是注入到类实例变量上,不同的请求可能会覆盖参数。
- SpringMVC可以很轻易的和Spring整合,而Struts需要做比较复杂的配置。
SpringMVC简单使用
-
添加依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.0.RELEASE</version> </dependency>
-
在web.xml中配置DispatcherServlet
<servlet> <servlet-name>aa</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 指定SpringMVC 配置文件位置,DispatcherServlet初始化时会初始化Spring上下文(WebApplicationContext) --> <!-- 默认配置文件寻找位置:/WEB-INF/{servlet-name}-servlet.xml,如果名字符合默认寻找规则,可以不指定配置文件路径 --> <!--<init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/aa-servlet.xml</param-value> </init-param>--> <!-- 配置容器启动时初始化DispatcherServlet --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>aa</servlet-name> <!-- 映射路径配置成/,代表当前Servlet是一个默认Servlet,就是当其他Servlet都无法处理请求时,由默认Servlet出马 --> <url-pattern>/</url-pattern> <!-- <url-pattern>/*</url-pattern>--> </servlet-mapping>
-
配置SpringMVC dispatcher-servlet.xml
SpringMVC大部分组件都有默认配置,我们一般简单应用只需要指定视图解析器就行了
dispatcher-servlet.xml
<!-- 配置视图解析器,用于将Handler方法中返回的视图名解析成真正可展示的页面 --> <mvc:view-resolvers> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/" /> <property name="suffix" value=".jsp" /> </bean> </mvc:view-resolvers>
-
定义Controller
新建一个普通类,然后添加
@Controller
注解,就可以了 -
定义请求处理方法(Handler)
在Controller类中定义一个普通的方法,添加
@RequestMapping
注解就可以了
SpringMVC主要组件
1. 视图解析器
视图解析器的作用是将请求处理方法中的返回值解析成一个真正可以渲染的页面。
常用的视图解析器
-
InternalResourceViewResolver
内部资源解析器: 用于将返回值对应到项目路径下的某个可显示的页面。比如方法返回值是index字符串,那么
InternalResourceViewResolver
解析器会在index前加上指定的前缀,在index后加上指定的后缀来拼接成指向某个视图的路径。<!-- 配置视图解析器,用于将Handler方法中返回的视图名解析成真正可展示的页面 --> <mvc:view-resolvers> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/" /> <property name="suffix" value=".jsp" /> </bean> </mvc:view-resolvers>
2. 参数传递
页面参数传递到Controller
获取请求参数,包括通过GET请求中的查询参数、POST请求中的参数等
@RequestMapping("/hello")
public String toHelloPage(@RequestParam String message, Model model) {
model.addAttribute("msg", message);
return "hello_springmvc";
}
// 当方法参数名和请求参数不一样时,可以使用注解的参数对应起来
@RequestMapping("/hello")
public String toHelloPage(@RequestParam("message") String msg, Model model) {
model.addAttribute("msg", message);
return "hello_springmvc";
}
访问: http://localhost:8080/hello?message=zhangsan 时,message参数会自动注入到方法的message参数中。
获取请求头中的参数,如浏览器默认自带的userAgent就是请求头参数
@RequestMapping("/hello")
public String toHelloPage(@RequestHeader String userAgent, Model model) {
model.addAttribute("msg", userAgent);
return "hello_springmvc";
}
// 当方法参数名和请求头参数不一样时,可以使用注解的参数对应起来
@RequestMapping("/hello")
public String toHelloPage(@RequestHeader("userAgent") String ua, Model model) {
model.addAttribute("msg", ua);
return "hello_springmvc";
}
会自动将请求头中的userAgent参数注入到方法中
获取请求地址中的参数,注入到方法参数中
@RequestMapping("/hello/{sname}")
public String toHelloPage(@PathVariable String sname, Model model) {
model.addAttribute("msg", sname);
return "hello_springmvc";
}
// 当方法参数名和请求地址中参数不一样时,可以使用注解的参数对应起来
@RequestMapping("/hello/{sname}")
public String toHelloPage(@PathVariable("sname") String sn, Model model) {
model.addAttribute("msg", sn);
return "hello_springmvc";
}
访问:http://localhost:8080/hello/zhangsan 会自动将zhangsan注入到方法的参数中
jsp中
<%
request.setAttribute("sname", "虞姬req");
request.getRequestDispatcher("/param/req_scope").forward(request, response);
%>
Controller中
/**
* 请求作用域中的参数的传递
* @return
*/
@RequestMapping("/req_scope")
public String requestScopeParamShow(@RequestAttribute String sname, Model model) {
model.addAttribute("sname", sname);
model.addAttribute("type", "请求作用域参数");
return "param_show";
}
获取请求作用域中的参数
获取Session作用域中的参数,用法和上面请求作用域中使用类似
jsp中代码
cookie参数传递:<br />
<%
response.addCookie(new Cookie("sname", "yuji_cookie"));
%>
<a href="/param/cookie">发起请求</a>
Controller中
/**
* Cookie中的参数的传递
* @return
*/
@RequestMapping("/cookie")
public String cookieParamShow(@CookieValue String sname, Model model) {
model.addAttribute("sname", sname);
model.addAttribute("type", "cookie参数");
return "param_show";
}
获取Cookie中的指定数据
上面这几个注解都是用于在方法的参数上标注,用于获取不同种类的参数
Controller中的数据传递到页面
handler方法定义:
@RequestMapping("/show_msg")
public String showMessage(Model model) {
model.addAttribute("msg", "我是通过model传过来的参数");
return "message_page";
}
message_page.jsp
<h1>
获取后台参数: ${msg}
</h1>
/**
* ModelAndView其实是将数据Model和视图View做了一个封装,底层实现原理一样
*/
@RequestMapping("/show_msg")
public ModelAndView showMessage(ModelAndView mv) {
mv.addObject("msg", "我是通过model传过来的参数");
mv.setViewName("message_page");
return mv;
}
Model和ModelAndView传递的参数本质上也是用请求作用域来实现的,所以仅对请求转发有效。
3. 视图控制器
用于通过配置的方式简化我们项目中不含业务逻辑的页面跳转,省去了我们写一个空方法的步骤。
视图控制器的作用和用法
试想一下,如果我们的工程中有这样的一个需求,比如当用户点了某个链接和按钮的时候,我们需要做一个页面跳转,这个跳转的目标可能是一个公开目录的页面也可能是私有目录(/WEB-INF/下)的页面、还可能是跳转到后台Controller的某个方法中,我们如何实现?
示例:比如我们想将登陆页设置为一个项目默认欢迎页面
方案一:在Controller中添加一个拦截/的handler方法
@RequestMapping("/")
public String toWelcomePage() {
return "login";
}
方法二:使用视图控制器
在dispatcher-servlet.xml(SpringMVC配置文件)配置中添加
<mvc:view-controller path="/" view-name="login" />
- path: 指定要匹配的访问路径
- view-name: 返回的逻辑视图名
- status-code: 设置响应状态码。(注:不能通过只设置状态码,不设置view-name实现只返回code,没有页面的处理)
view-name不仅可以是逻辑视图名,还可以添加forward、redirect前缀,通过转发或重定向的方式跳转到一个具体的路径,如:<mvc:view-controller path="/" view-anme=“forward:/WEB-INF/login.jsp” />
4. 设置Web项目的默认欢迎页
-
在web.xml中使用标签指定
-
使用上面提到的视图控制器
<mvc:view-controller path="/" view-name="逻辑视图名" /> <!-- 当工程中存在Tomcat默认欢迎页同名文件时此种方式可能失效,比如index.html、index.jsp、index.htm -->
静态资源处理
<!-- 针对静态资源的处理 -->
<!-- http://localhost:8080/static/css/index.css -->
<mvc:resources mapping="/static/**" location="/static/" />
5. 重定向和转发
在Servlet实现重定向和转发:
重定向:response.sendRedirect(“xxx.jsp”);
转发: request.getRequestDispatcher(“xxx.jsp”).forward(request, response);
在SpringMVC中请求转发好和重定向
其中第二种和第三种作用一样, 都是将路径用请求转发的方式跳转,返回的路径时不经过视图解析器处理的;而第一种虽然也是通过请求转发方式跳转,但是返回值会作为逻辑视图名称,经过视图解析器处理后,才渲染。
这两种方法效果一样。 注意使用重定向的方式跳转页面,Model中的数据就无法带到页面了(因为Model底层也是用请求作用域来传递参数的)。
6. RESTFull服务接口开发
SpringMVC给我们提供了一系列的注解用于支持RESTFull风格的服务开发
常用RESTFull注解
-
@RestController
该注解本身是一个组合注解,由
@Controller
和@ResponseBody
两个注解组成,所以拥有这两个注解的作用。 在类上声明后,该类会变成一个Controller类,同时,方法的返回值会作为响应体经过消息转换器直接响应给客户端,而不会将其作为视图渲染。@RestController // 这个注解其实是组合了下面两个注解的作用 //@Controller //@ResponseBody public class UserRestController { }
-
@GetMapping
:作用: 申明当前handler方法只匹配GET请求
使用示例
@GetMapping("/{id}") public User getUserById(@PathVariable Integer id) { return userService.findById(id); }
-
@PostMapping
: 申明当前handler方法只匹配POST请求 -
@DeleteMapping
: 申明当前handler方法只匹配DELETE请求 -
@PutMapping
: 申明当前handler方法只匹配PUT请求
上面四个注解分别对应HTTP的一种请求方法,使用方法类似。
上面四个注解其实是一种快捷注解, 等效于使用下面代码:
@RequestMapping(value = "/user", method = RequestMethod.GET)
@RequestMapping(value = "/user", method = RequestMethod.POST)
@RequestMapping(value = "/user", method = RequestMethod.PUT)
@RequestMapping(value = "/user", method = RequestMethod.DELETE)
- @PathVariable: 获取请求地址中的变量,注入到方法参数中
7. 返回JSON格式的数据–消息转换器
消息转换器是什么
之前我们在写普通Controller时,handler方法的返回值要是就是一个String(逻辑视图名),要么就是一个ModelAndView、View对象。
而我们使用了RestController之后,方法的返回值已经不会被作为视图去渲染了,这时候我们的方法其实可以返回任意类型的数据。这些数据会直接通过响应体以流的方式返回给客户端。
我们知道Java中的对象是不能直接用流的方式读取的,需要序列化。比如我们的handler方法返回了一个User类型,SpringMVC就不知道如果将这个类型返回给客户端了。这时就需要我们通过配置消息转换器来完成这种类型对象的返回处理。 而在RESTFull服务中,对象绝大部分传递方式就是通过JSON格式。
如何配置一个转换JSON格式的消息转换器
-
使用SpringMVC默认的Jackson库
-
引入依赖
<!-- jackson依赖,用于将handler方法返回的对象直接转换成JSON数据 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.7</version> </dependency>
-
开启mvc注解支持
<mvc:annotation-driven />
-
定义Controller类
@RestController @RequestMapping("/user") public class UserRestController { @GetMapping("/{id}") public User getUserById(Integer id) { return userService.findById(id); } }
Jackson是SpringMVC默认的JSON格式消息转换器, 所以在不配置额外转json参数时,我们可以直接只引入jackson依赖,再开启mvc直接支持就可以了。而第三方的转换库如FastJSON就必须显示配置MessageConverter
-
-
使用阿里巴巴的FastJSON库
-
引入依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.57</version> </dependency>
-
配置MessageConverter
<mvc:annotation-driven> <mvc:message-converters> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"> <property name="defaultCharset" value="UTF-8" /> <property name="fastJsonConfig"> <!-- 设置转换JSON的参数 --> <bean class="com.alibaba.fastjson.support.config.FastJsonConfig"> <property name="dateFormat" value="yyyy-MM-dd HH:mm:ss" /> </bean> </property> <property name="supportedMediaTypes"> <!-- 指定转换完JSON后返回的响应头和编码,添加text/html是为了处理在IE下application/json会弹出下载框问题 --> <list> <!--<value>text/html;charset=UTF-8</value>--> <value>application/json;charset=UTF-8</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>
-
定义Controller类
@RestController @RequestMapping("/user") public class UserRestController { @GetMapping("/{id}") public User getUserById(Integer id) { return userService.findById(id); } }
-
8. 通过网页模拟RESTFull请求
通过SpringMVC给我们提供的一个过滤器,我们可以用表单模拟各种RESTFull的请求,使用方法如下:
-
在web.xml中添加过滤器
<filter> <filter-name>httpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>httpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
在表单中添加name为_method的hidden域,并将表单的method设置为post
<!-- 使用表单模拟发起一个DELETE请求,删除user表中id为57的数据 --> <form action="/user/57" method="post"> <input type="hidden" name="_method" value="delete" /> <input type="number" name="id" /> <input type="text" name="username" /> <input type="submit" /> </form>
由于RESTFull请求通常发生在服务于服务之间的调用,所以有些请求用浏览器不太好模拟,建议使用专业的网络测试工具postman来进行测试。
RestTemplate工具类
SpringMVC帮我们封装的一个Rest请求工具,可以使用一个URI地址发起网络请求,并且将结果封装成一个指定的对象。
9. 处理请求乱码
SpringMVC给我们提供了一个专门用来解决post请求乱码的过滤器,我们只需将其配置到web.xml中,就可以避免post请求乱码,免去了我们自己写过滤器的麻烦
在web.xml中
<!-- 解决POST请求乱码 -->
<filter>
<filter-name>encodingFilter</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>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意: 如果项目中配置了多个过滤器,建议将此过滤器放到所有过滤器的最上面。
10. 在SpringMVC方法中支持的参数
SpringMVC的handler方法中支持诸如很多特殊类型的参数,常用的有
-
注入原生Servlet对象
-
javax.servlet.ServletRequest
-
javax.servlet.ServletResponse
-
javax.serlvet.http.HttpSession
-
以流的方式读取请求体、以流的方式响应到客户端
-
java.io.InputStream、java.io.Reader -
java.io.OutputStream、java.io.Writer -
读取请求体和请求头数据
-
HttpEntity
-
向页面传参
-
java.util.Map,org.springframework.ui.Model、org.springframework.ui.ModelMap
11. 文件上传
在Java中,主流的文件上传方式有两种,分别是通用文件上传(commons-fileupload)和Servlet3.0方式的文件上传。SpringMVC分别对着两种方式都做了支持。
- CommonsMultipartResolver
- StandardServletMultipartResolver
除此之外SpringMVC对文件上传的接口做了统一的封装,使用
MultipartFile
接口代替了通用上传中的FileItem
和Servlet3.0中的Part
,使得开发人员在业务代码中可以使用统一的接口处理,而不用管底层用的是哪种文件上传实现方式。
-
添加依赖
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency>
-
在SpringMVC配置文件中添加MultipartResolver文件上传解析器
<!-- 配置基于apache fileupload的通用文件上传器(注意:id属性不可省略) --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 指定文件上传编码 --> <property name="defaultEncoding" value="UTF-8" /> </bean>
-
在Controller中编写文件上传逻辑代码
@RequestMapping("/upload") public String upload(@RequestParam("myFile") MultipartFile file, HttpServletRequest req, Model model) { try { // 使用SpringMVC给我们提供的工具类获取项目中upload文件夹在硬盘上的绝对路径 String uploadPath = WebUtils.getRealPath(req.getServletContext(), "/upload/"); // 将上传的文件写到上传目录 file.transferTo(new File(uploadPath+file.getOriginalFilename())); } catch (IOException e) { e.printStackTrace(); } }
-
开启Servlet文件上传支持
web.xml
<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 开启Servlet文件上传支持 --> <multipart-config /> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
-
在SpringMVC中配置MultipartResolver
<!-- 配置基于Servlet3.0文件上传器(注意:id属性不可省略) --> <bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver" />
-
在Controller中编写文件上传逻辑代码
实现方式同通用上传代码一样。
-
文件名上传乱码问题
-
使用通用上传方式的解决方法
在配置的CommonsMultipartResolver bean中注入属性
<!-- 配置基于apache fileupload的通用文件上传器(注意:id属性不可省略) --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 指定文件上传编码 --> <property name="defaultEncoding" value="UTF-8" /> </bean>
-
使用Servlet3.0标准上传方式的解决方法
通过配置SpringMVC提供的编码过滤器解决
web.xml
<filter> <filter-name>characterFilter</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> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
本质上还是设置请求的编码 request.setCharacterEncoding(“UTF-8”);
12. 拦截器
过滤器是Servlet规范中提供的一项技术。不依赖于任何第三方框架。它的作用主要用来做两件事: 加工请求、过滤请求。
拦截器是SpringMVC自己封装的一项基于Handler拦截的结束。也就是说只有在SpringMVC框架里面才有拦截器的概念。拦截器拦截的对象是Handler。它的全称是 HandlerInterceptor。
加上拦截器以后,请求处理流程: request -> DispatcherServlet -> HandlerMapping -> HandlerExecutionChain(包含了目标handler和若干个拦截器) -> HandlerAdapter -> 循环调用HandlerExecutionChain对象中所有拦截器的prehandler方法 -> 如果preHandler返回false,那么handler将不会被调用。反之,才会正常调用。
新建一个类实现HandlerInterceptor接口
/**
* 定义拦截器步骤:
* 1. 定义一个普通类,实现HandlerInterceptor接口
* 2. 按需实现接口中的方法
* 3. 在SpringMVC配置文件中通过<mvc:interceptors></mvc:interceptors>配置拦截器
*/
public class NotAllowedInterceptor implements HandlerInterceptor {
/**
* 此回调方法在执行Handler之前被调用
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("进入NotAllowedInterceptor, 凡是我能拦截到的请求,一律不准通过!");
return false;
}
/**
* 此回调方法在执行Handler之后被调用
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/**
* 此回调方法在DispatcherServlet最终响应之前被调用
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
配置拦截器
<mvc:interceptors>
<!-- 这种方式配置的拦截器会拦截所有请求 -->
<bean class="com.lanou3g.springmvc.interceptor.NotAllowedInterceptor" />
</mvc:interceptors>
<mvc:interceptors>
<mvc:interceptor>
<!-- 拦截的请求 -->
<mvc:mapping path="/admin/**" />
<!-- 不拦截的请求 -->
<mvc:exclude-mapping path="/intercepor/**"/>
<!-- 配置我们定义的实现了HandlerInterceptor接口的类 -->
<bean class="com.lanou3g.springmvc.interceptor.NotAllowedInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
SpringMVC出错点:
展示数据库数据:
<!--Spring-MVC 查询数据库要在web.xml加的代码-->
<!--父上下文的配置-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:applicationContext.xml</param-value>
</context-param>
<!-- 监听器配置 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Controller负责具体的业务模块流程的控制;Service层负责业务模块的逻辑应用设计
总结:具体的一个项目中有:controller层调用了Service层的方法,Service层调用Dao层的方法,其中调用的参数是使用Entity(实体类)层进行传递的。
SpringMVC静态资源放行
这种放行方式只能在页面展示不能下载
<mvc:default-servlet-handler />
还有一种办法
<!--放行静态资源!--><mvc:resources mapping="/static/**" location="/static/"/>
Spring MVC的controller层获取jsp页面参数
- 方式有两种,一种为使用@RequestParam,一种为使用request.getParameter(“param”)来获取
。。。。。。好长。。。。。。