1. SpringMVC 基础
-
SpringMVC 在 在 三层架构的位置
-
SpringMVC 的优势
- 清晰的角色划分:
- 前端控制器(DispatcherServlet)
- 请求到处理器映射(HandlerMapping)
- 处理器适配器(HandlerAdapter)
- 视图解析器(ViewResolver)
- 处理器或页面控制器(Controller)
- 验证器( Validator)
- 命令对象(Command 请求参数绑定到的对象就叫命令对象)
- 表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)。
- 分工明确,而且扩展点相当灵活,可以很容易扩展,虽然几乎不需要。
- 由于命令对象就是一个 POJO,无需继承框架特定 API,可以使用命令对象直接作为业务对象。
- 和 Spring 其他框架无缝集成,是其它 Web 框架所不具备的。
- 可适配,通过 HandlerAdapter 可以支持任意的类作为处理器。
- 可定制性,HandlerMapping、ViewResolver 等能够非常简单的定制。
- 功能强大的数据验证、格式化、绑定机制。
- 利用 Spring 提供的 Mock 对象能够非常简单的进行 Web 层单元测试。
- 本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换。
- 强大的 JSP 标签库,使 JSP 编写更容易。
- 清晰的角色划分:
-
SpringMVC 和 和 Struts2
- 共同点:
- 它们都是表现层框架,都是基于 MVC 模型编写的。
- 它们的底层都离不开原始 ServletAPI。
- 它们处理请求的机制都是一个核心控制器。
- 区别:
- Spring MVC 的入口是 Servlet, 而 Struts2 是 Filter
- Spring MVC 是基于方法设计的,而 Struts2 是基于类,Struts2 每次执行都会创建一个动作类。所以 Spring MVC 会稍微比 Struts2 快些。
- Spring MVC 使用更加简洁,同时还支持 JSR303, 处理 AJAX 的请求更方便 (JSR303 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们 JavaBean 的属性上面,就可以在需要校验的时候进行校验了)
- Struts2 的 OGNL 表达式使页面的开发效率相比 Spring MVC 更高些,但执行效率并没有比 JSTL 提升,尤其是 struts2 的表单标签,远没有 HTML 执行效率高。
- 共同点:
-
入门程序:
index 页面:
<body> <h3>入门程序</h3> <a href="hello">入门程序</a> </body>
web.xml
<web-app> <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:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
- DispatcherServlet 为前端控制器,所有请求通向前端控制器
- init-param 为初始化参数,用于加载 Spring 配置文件
- load-on-startup 代表程序启动就加载,创建 Servlet 对象
- 路径使用
/
而不能使用/*
,/
会匹配到/springmvc
这样的路径型 url,而不会匹配到像.jsp
这样的后缀型的 url。
springmvc.xml
<!-- 开启注解扫描 --> <context:component-scan base-package="pers.ylq"/> <!-- 视图解析器对象 --> <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"/> <property name="suffix" value=".jsp"/> </bean> <!-- 开启SpringMVC框架注解的支持 --> <mvc:annotation-driven conversion-service="conversionService"/>
- InternalResourceViewResolver 为视图解析器
- prefix 为前缀,suffix 为后缀
- 经视图解析器跳转至请求页面
HelloController.java
@Controller public class HelloController { @RequestMapping(path = "/hello") public String sayHello(){ System.out.println("Hello SpringMVC"); return "success"; } }
- @Controller 用 IoC 创建对象
- RequestMapping 声明请求路径
- 当 dispatcherServlet 配置路径为 *.do 的时候,后台可以写 /hello.do 或者 /hello
- 返回 success.jsp
-
入门程序简单分析
-
详细分析
-
前端控制器:DispatcherServlet
用户请求到达前端控制器,它就相当于 mvc 模式中的 c,dispatcherServlet 是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet 的存在降低了组件之间的耦合性。 -
处理器映射器:HandlerMapping
HandlerMapping 负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。 -
处理器:Handler
它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler。由 Handler 对具体的用户请求进行处理。 -
处理器适配器:HandlAdapter
通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。 -
视图解析器:View Resolver View Resolver
负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。 -
处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。使 用
<mvc:annotation-driven>
自动加载 RequestMappingHandlerMapping (处理映射器) 和 RequestMappingHandlerAdapter(处理适配器),可 用 在 SpringMVC.xml 配 置 文 件 中 使 用<mvc:annotation-driven>
替代注解处理器和适配器的配置。 -
RequestMapping 注解:用于建立请求 URL 和处理请求方法之间的对应关系。
- 位置:
- 类上:一级访问目录
- 方法上:二级访问目录
- 属性:
- value:用于指定请求的 URL。它和 path 属性的作用是一样的。
- method:用于指定请求的方式。
- params:用于指定限制请求参数的条件。
- headers:用于指定限制请求消息头的条件。
- 位置:
2. 请求参数的绑定
-
可以封装的类型
- 基本类型和 String 类型
- 实体类,以及关联的实体类
- 数组和集合类型
-
绑定的方法:通过把表单提交请求参数,作为控制器中方法参数进行绑定。
-
基本类型和 String 类型的绑定:参数名称必须和控制器中方法的形参名称保持一致。(严格区分大小写)
-
实体类类型的绑定:参数名和方法的形参中的实体类的属性名一致即可。
-
实体类中有实体类:比如方法的形参为 Account,有一个属性为 User user,User 中有个成员变量 name,则需要前台参数的 name 为 user.name。
-
参数的乱码问题:
- 配置 Spring 官方提供的过滤器 CharacterEncodingFilter 传递参数即可。
- 可以解决 post 请求体乱码问题,不能解决 get 乱码问题,因为 get 的请求参数在请求行中(tomcat8 已经解决)。
-
<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> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
集合的封装:
public class Account implements Serializable{ private List<User> list; private Map<String,User> map; } @RequestMapping("/saveAccount") public String saveAccount(Account account){ return "success"; } <body> <input name="list[0].username" /> <input name="list[1].username" /> <input name="map['key'].username" /> </body>
-
自定义类型转换器:表单提交的任何数据类型全部都是字符串类型,Spring 框架内部会默认进行数据类型转换,来帮我们封装数据。但有时候可能因为格式问题封装失败,比如 String 转 Date,这时,我们需要自定义类型转换器。
-
自定义类型转换类,实现 Converter 接口。
-
注册自定义类型转换器,在 springmvc.xml 配置文件中编写配置
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="自定义类的全类名"/> </set> </property> </bean> <!-- 注册类型转换器 --> <mvc:annotation-driven conversion-service="conversionService"/>
- 注册类型转换器可以添加自定义的类型转换器,原有的不会失效。
-
-
在控制器中获取原生 ServletAPI 对象(request、response 等)
- 只需要在控制器的方法参数定义 HttpServletRequest 和 HttpServletResponse 类型的形参。
3. 常用注解
-
@RequestParam
- 作用:把请求中的指定名称的参数传递给控制器中的形参赋值
- 位置:参数上
- 属性:
- value:请求参数中的名称
- required:请求参数中是否必须提供此参数,默认值是 true,必须提供
-
@RequestBody
- 作用:用于获取请求体的内容(注意:get 方法不可以)
- 位置:参数上
- 属性 required:是否必须有请求体,默认值是 true
- 备注:可以用来获取前台异步传输的 JSON 数据
-
@RestController
- 作用:直接返回 json 数据,是 @Crotroller 和 @ResponseBody 的组合
- 位置:类上
- 备注:适用于前后端分离的项目,不再返回视图
-
@PathVariable
- 作用:拥有绑定 url 中的占位符的。例如:url 中有/delete/,就是占位符
- 属性 value:指定 url 中的占位符名称
- RESTFul 风格的 URL:请求路径一样,可以根据不同的请求方式去执行后台的不同方法
-
@RequestMapping(path="/hello/{id}") public String sayHello(@PathVariable("id") String id) { System.out.println(id); return "success"; }
-
@RequestHeader
- 作用:获取指定请求头的值
- 位置:参数上
- 属性 value:请求头的名称
-
@CookieValue
- 作用:用于获取指定 cookie 的名称的值
- 属性 value:cookie 的名称
-
@ModelAttribute
- 作用:
- 方法上:表示当前方法会在控制器方法执行前先执行。
- 参数上:获取指定的数据给参数赋值。
- 修饰的方法有返回值
@ModelAttribute public User showUser(String name) { User user = new User(); return user; } @RequestMapping("/updateUser") public String updateUser(User user) { //可以获取到showUser方法返回的User System.out.println(user); return "success"; }
- 修饰的方法没有返回值
@ModelAttribute public void showUser(String name,Map<String, User> map) { User user = new User(); map.put("abc", user); } @RequestMapping("/updateUser") public String updateUser(@ModelAttribute(value="abc") User user) { System.out.println(user); return "success"; }
- 作用:
-
@SessionAttributes
- 作用:用于多次执行控制器方法间的参数共享
- 属性 value:指定存入属性的名称
-
@Controller @RequestMapping(path="/user") @SessionAttributes(value= {"username","password","age"}) // 把数据存入到session域对象中 public class HelloController { //向session中存入值 @RequestMapping(path="/save") public String save(Model model) { model.addAttribute("username", "root"); model.addAttribute("password", "123"); model.addAttribute("age", 20); return "success"; } //从session中获取值 @RequestMapping(path="/find") public String find(ModelMap modelMap) { String username = (String) modelMap.get("username"); String password = (String) modelMap.get("password"); Integer age = (Integer) modelMap.get("age"); System.out.println(username + " : "+password +" : "+age); return "success"; } //清除值 @RequestMapping(path="/delete") public String delete(SessionStatus status) { status.setComplete(); return "success"; } }
- 向 Model 中存值,会把值存入 request 域中,通过注解 @SessionAttributes,会将值同时存入 session 域。
4. 响应数据和结果视图
-
转发可以访问 WEB-INF 目录,重定向不行。
-
当方法的返回值是字符串的时候,字符串即为逻辑视图的名称,根据视图解析器解析为物理视图的地址,例如返回 "success"
<!-- 视图解析器对象 --> <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"/> <property name="suffix" value=".jsp"/> </bean>
经视图解析器,转发至
/WEB-INF/pages/success.jsp
-
当返回值为 void,程序会将请求路径作为返回值,经视图解析器寻找对应路径。比如请求路径为
/user/delete
,则会经视图解析器,寻找WEB-INF/pages/user/delete.jsp
,可以由参数获取 request 或 response 自己手动转发或重定向。 -
返回值是 ModelAndView 对象,该对象是 Spring 提供的一个对象,可以用来存储值和要跳转的视图。当返回值为 String,参数为 Model 的时候,底层调用的也是此对象。
@RequestMapping("/testModelAndView") public ModelAndView testModelAndView(){ // 创建ModelAndView对象 ModelAndView mv = new ModelAndView(); User user = new User(); // 把user对象存储到mv对象中,也会把user对象存入到request对象 mv.addObject("user",user); // 跳转到哪个页面 mv.setViewName("success"); return mv; }
-
SpringMVC 框架提供的转发和重定向
- forward 关键字进行转发,redirect 关键字进行重定向
- 重定向不能访问 WEB-INF 目录,使用 SpringMVC 重定向的时候,路径不需要再加项目名,底层已经帮我们加上了。
-
public String testForwardOrRedirect(){ return "forward:/WEB-INF/pages/success.jsp"; return "redirect:/index.jsp"; }
-
SpringMVC 前端控制器也会拦截静态资源,通过 mvc:resources 标签配置不过滤
<mvc:resources location="/css/" mapping="/css/**"/> <mvc:resources location="/images/" mapping="/images/**"/> <mvc:resources location="/js/" mapping="/js/**"/>
- location 表示 webapp 目录下的包下的所有文件
- mapping 表示以
/xxx
开头的所有请求路径
-
接收与响应 JSON 数据:
@RequestMapping("/testJson") public @ResponseBody Address testJson(@RequestBody Address address) { System.out.println(address); address.setAddressName("上海"); return address; }
- @RequestBody 注解会将前台传进来的 JSON 自动封装为对应对象。
- @ResponseBody 会将返回的对象解析为 JSON 然后响应给前台。
- @ResponseBody 可以作用于类和方法上面。
- 使用前提:pom 坐标中加入 jackson 2.7.0 以上版本
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.0</version> </dependency>
5. 文件上传
- 传统文件上传
-
jar 包
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency>
-
前台代码
<form action="user/fileupload1" method="post" enctype="multipart/form-data"> 选择文件:<input type="file" name="upload"><br> <input type="submit" value="上传"> </form>
- enctype:是表单请求正文的类型,取值必须是 multipart/form-data 才能上传文件,表示把表单分为多个部分。
- 必须为 post,get 的 url 有长度限制。
-
后台代码:
@RequestMapping("/fileupload1") public String fileuoload1(HttpServletRequest request) throws Exception { // 使用fileupload组件完成文件上传 // 上传的位置 String path = request.getSession().getServletContext().getRealPath("/uploads/"); // 判断,该路径是否存在 File file = new File(path); if(!file.exists()){ file.mkdirs(); } // 解析request对象,获取上传文件项 DiskFileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload upload = new ServletFileUpload(factory); // 解析request List<FileItem> items = upload.parseRequest(request); // 遍历 for(FileItem item:items){ // 进行判断,当前item对象是否是上传文件项 if(item.isFormField()){ // 说明普通表单项 }else{ // 说明上传文件项 // 获取上传文件的名称 String filename = item.getName(); // 把文件的名称设置唯一值,uuid String uuid = UUID.randomUUID().toString().replace("-", ""); filename = uuid+"_"+filename; // 完成文件上传 item.write(new File(path,filename)); // 删除临时文件,大于10k会产生临时文件 item.delete(); } } return "success"; }
-
- SpringMVC 文件上传原理
- 我们只需要配置文件解析器,SpringMVC 自动帮我们解析,我们只需要参数获取文件就行。
- 解析器的 id 必须为 multipartResolver
- 使用 MultipartFile 获取文件。
- 参数名必须和前台 name 属性名一致。
- 代码实现
-
配置文件解析器
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxInMemorySize" value="10485760"/> </bean>
- maxInMemorySize 为配置上传文件最大大小,单位字节
-
后台实现
@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/fileupload") public String fileupload(HttpServletRequest request, MultipartFile upload) throws Exception { String path = request.getSession().getServletContext().getRealPath("/uploads/"); File file = new File(path); if (!file.exists()) file.mkdirs(); String filename = upload.getOriginalFilename(); String uuid = UUID.randomUUID().toString(); filename = uuid + "_" + filename; //将文件写入指定目录 upload.transferTo(new File(path, filename)); return "success"; } }
-
- 跨服务器上传
- jar 包
<dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-core</artifactId> <version>1.18.1</version> </dependency> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-client</artifactId> <version>1.18.1</version> </dependency>
- 后台实现
@RequestMapping("/fileupload3") public String fileuoload3(MultipartFile upload) throws Exception { // 定义上传文件服务器路径 String path = "http://localhost:9090/uploads/"; // 获取上传文件的名称 String filename = upload.getOriginalFilename(); // 把文件的名称设置唯一值,uuid String uuid = UUID.randomUUID().toString().replace("-", ""); filename = uuid+"_"+filename; // 创建客户端的对象 Client client = Client.create(); // 和图片服务器进行连接 WebResource webResource = client.resource(path + filename); // 上传文件 webResource.put(upload.getBytes()); return "success"; }
- jar 包
6. SpringMVC 异常处理
- 自定义异常类
public class SysException extends Exception{ // 存储提示信息的 private String message; public SysException(String message) { this.message = message; } }
- 自定义异常处理器:实现 HandlerExceptionResolver 接口
public class SysExceptionResolver implements HandlerExceptionResolver{ public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 获取到异常对象 SysException e = null; if(ex instanceof SysException){ e = (SysException)ex; }else{ e = new SysException("系统正在维护...."); } // 创建ModelAndView对象 ModelAndView mv = new ModelAndView(); mv.addObject("errorMsg",e.getMessage()); mv.setViewName("error"); return mv; } }
- 配置异常处理器
<bean id="sysExceptionResolver" class="cn.itcast.exception.SysExceptionResolver"/>
7. SpringMVC 拦截器
- SpringMVC 框架中的拦截器用于对处理器进行预处理和后处理的技术。
- 拦截器和过滤器的功能比较类似,有区别
- 过滤器是 Servlet 规范的一部分,任何框架都可以使用过滤器技术。
- 拦截器是 SpringMVC 框架独有的。
- 过滤器配置了
/*
,可以拦截任何资源。 - 拦截器只会对控制器中的方法进行拦截。
- 想要自定义拦截器,需要实现 HandlerInterceptor 接口。
- 拦截器的执行流程
- 自定义拦截器
public class MyInterceptor1 implements HandlerInterceptor{ /** * return true放行 * return false拦截 * 可以使用转发或者重定向直接跳转到指定的页面。 */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } }
- springmvc.xml 中配置拦截器
<!--配置拦截器--> <mvc:interceptors> <mvc:interceptor> <!--要拦截的具体的方法--> <mvc:mapping path="/user/*"/> <!--不要拦截的方法 <mvc:exclude-mapping path=""/> --> <!--配置拦截器对象--> <bean class="cn.itcast.controller.cn.itcast.interceptor.MyInterceptor1" /> </mvc:interceptor> <!--配置第二个拦截器--> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="cn.itcast.controller.cn.itcast.interceptor.MyInterceptor2" /> </mvc:interceptor> </mvc:interceptors>
- HandlerInterceptor 接口中的方法
- preHandle 方法是 controller 方法执行前拦截的方法,可以进行跳转页面。
- postHandle 方法是 controller 方法执行后执行的方法,在 JSP 视图执行前拦截,可以进行跳转页面。
- afterCompletion 方法是在 JSP 执行后执行,不能进行页面跳转。