概念
三层架构 将整个业务应用划分为三层
表现层:用来和客户端进行数据交互,一般采用MVC设计模式
业务层:处理公司具体业务逻辑
持久层:用来操作数据库
MVC模型 Model View Controller模型视图控制器
Model:数据模型,JavaBean的类,用来封装数据
View:通过jsp, html等展示数据
Controller:接收用户请求,整个流程的控制器
Spring MVC spring提供的mvc框架
与struts2的区别:前者入口为servlet,后者用filter接收请求;前者基于方法,后者基于类(每次执行都会新建一个对象,效率低);前者使用更方便;前者使用JSTL表达式执行效率高,后者使用OGNL表达式开发效率高
应用
环境搭建
新建maven项目(不使用骨架)
完善目录结构
在main目录下新建webapp/WEB-INF/web.xml文件并导入约束和新建jsp.index文件
Web Application main / | \ java resources webapp / \ WEB-INF index.jsp | web.xml
添加web moudules
打开project structure的Modules添加Web
设置Deployment Descriptors的Path为web.xml文件的路径
设置Web Resource Directories的为webapp目录的路径
配置pom.xml
5.0.2.RELEASE org.springframework spring-context ${spring.version} org.springframework spring-web ${spring.version} org.springframework spring-webmvc ${spring.version} javax.servlet servlet-api 2.5 provided javax.servlet.jsp jsp-api 2.0 provided 配置Tomcat
- 打开Add Configuration添加Tomcat local
- 随意取Name,在Deployment中添加当前Artifact并设置访问路径
- 在Server的On'update'action中可以选择Redeploy
创建springmvc.xml(resources目录下,ioc下的bean.xml)
配置web.xml
dispatcherServlet org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:springmvc.xml 1 dispatcherServlet /
入门程序
编写View
在WEB-INF下编写index.jsp(用a或form发送请求)和返回页面pages/success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Start Program Start Program配置springmvc.xml
编写控制器类
@Controller //放入IOC容器 public class HelloController{ @RequestMapping("/hello") //处理路径为hello的请求 public String sayHello(){ //TODO return "success"; //根据视图解析器找到对应success的页面并返回 } }
@RequestMapping
可以放在类或方法上,放在类上时可以定义路径公共前缀(分模块)
属性path:用于指定请求的URL
属性value(常用):与path一致,单用value的时候可以省略掉value=
属性method(常用):定义能接收的请求方法,method={RequestMethod.POST}
属性params:用于指定限制请求参数的条件
如@RequestMapping(value="/hello", params={"username"})则请求的参数必须有username
如params={"username=abc"}则传过来的username必须为abc,还可以用!表示不等于
属性headers:用于指定限制请求消息头的条件
如headers={"Accept"}则请求中必须包含有Accept请求头
多个属性同时出现,关系为与
请求参数绑定
绑定机制(名字相同,自动注入)
- 表单提交的数据都是键值对形式的k=v
- SpringMVC把表单提交的参数作为控制器中方法的参数
- 要求表单参数的name和方法参数的的名称相同
支持的数据类型
基本数据类型和String:区分大小写
实体类型(JavaBean)
表单数据的name要与JavaBean的属性名相同,方法参数中加一个JavaBean类型的参数,自动封装名称随意
若JavaBean类中包含其他的引用类型,则表单name为对象.属性,如address.name
集合数据类型
- 设JavaBean中存在myList或myMap,则表彰数据的name=myList[0],myMap['one']
解决中文乱码(web.xml中配置)
characterEncodingFilter org.springframework.web.filter.CharacterEncodingFilter encoding UTF-8 characterEncodingFilter /* 自定义类型转换器
- 页面提交的参数都是字符串形式
- 正常情况下框架自动把传过来的字符串参数转换为对应类型,但有时可能会异常,如不同格式的日期
编写转换类实现Converter接口
import org.springframework.core.convert.converter.Converter; public class StringToDateConvert implements Converter
{ @Override public Date convert(String source){ if(source == null){ throw new RuntimeException("请传入数据"); } DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); try{ return df.parse(source); } catch(Exception e){ throw new RuntimeException("数据类型转换出现错误"); } } } 配置自定义类型转换器(springmvc.xml中)
获取servlet原生API
在方法参数中加对应类型的参数,如HttpServletRequest request, HttpServletResponse response
request.getSession()可以拿到HttpSession
Model(最常用)
方法参数接收一个Model model(map结构),向model中存值,值会自动存入request域中,可以代替获取原生request方式,存用set(),取用get(),取值时可以用实现类ModelMap
常用注解
@RequestParam
用在方法的参数前,把请求中指定名称的参数给控制器中的形参赋值,当页面参数与方法参数名不一致时可以用
属性value:请求参数中的名称
属性required:是否必须提供此参数,默认true
@RequestBody
- 用在方法的参数前,用于获取请求体的内容,直接使用得到的是k=v&k=v...结构的数据,get方式不适用
- 属性required:默认true,为true时用get方式会报错,false时get方式得到null
@PathVariable
- restful编程风格,简单来说就是通过不同请求方式访问同一请求路径的不同方法(用@RequestMapping的method属性指定),或路径上加参数来区分方法如 @RequestMapping(path="/user/{id}")
- 用于取请求路径上的参数 如 public String test(@PathVariable(name="id") String id){}
- 请求路径写 user/10
@RequestHeader(不常用)
- 用在方法的参数前,用于取请求头
@CookieValue(不常用)
- 用在方法的参数前,用于取指定名称Cookie的值,属性value
@ModelAttribute
在控制器方法执行前对参数进行预处理
- 用在方法上,使该方法会在别的控制器的方法执行之前执行,方法的返回值会作为之后实际控制器方法上的参数
用在参数上,获取指定的数据给参数赋值,当表单提交的数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据
@RequestMapping("/user") //此方法的参数user是findUser方法的返回值 public String test(User user){} @ModelAttribute public User findUser(String name){ //数据库中根据传入参数name查找user return user; } @ModelAttribute //无返回值写法,取参时参数上要用@ModelAttribute("u") public void findUser(String name, Map
map){ //数据库中根据传入参数name查找user map.put("u", user); } @SessionAttribute
- 用于多次制造控制器方法(多次请求)间的参数共享,代替request.getSession()...方式
- 用在类上,@SessionAttributes(value={"key"}) 用于将model中的键值对存入session域中
- 清空session用SessionStatus(参数接收)中setComplete()方法
响应方式
页面跳转及存值
字符串:根据前端控制器配置好的前缀和后缀找到要跳转的页面,存值用model,底层用ModelAndView实现
void
默认跳转以路径名为名的jsp文件
使用request.getRequestDispatcher("/WEB-INF开始路径全名").forward(request,response); return;进行请求转发
使用response.sendRedirect(request.getContextPath+"/index.jsp");进行重定向
直接进行响应
response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); response.getWriter().print("hello");
ModelAndView
- 方法内用new创建,最后返回
- ModelAndView可以当作Model存数据
- 使用setViewName("success")指定跳转页面,使用视图解析器
使用关键字进行转发和重定向(不能使用视图解析器)
- return "forward:/WEB-INF/pages/success.jsp";
- return "redirect:/index.jsp"; 框架自动添加项目名
响应json数据
springmvc.xml中配置前端控制器不拦截的静态资源
添加坐标,导入jackson包
com.fasterxml.jackson.core jackson-databind 2.9.0 com.fasterxml.jackson.core jackson-core 2.9.0 com.fasterxml.jackson.core jackson-annotations 2.9.0 接收json数据,用JavaBean作为参数接收,参数上用@RequestBody,后台自动将json按照名称封装进JavaBean
响应json数据,在返回值类型前加@ResponseBody,后台自动将JavaBean转换为json
@RequestMapping("/test") public @ResponseBody User test(@RequestBody User user){ //TODO return user; }
文件上传
导入坐标
commons-fileupload commons-fileupload 1.3.1 commons-io commons-io 1.3.1 编写前端代码
- form表单的enctype取值必须是multipart/form-data
- method必须是Post
- 需要提供一个文件选择域《input type="file"/》
配置文件解析器(beanId为固定写法,springmvc.xml)
编写控制器方法(参数中的upload必须对应表单中文件的name)
@RequestMapping("/upload") 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().replace("-", ""); filename = uuid + "_" + filename; //完成文件上传 upload.transferTo(new File(path, filename)); return "success"; }
传统文件上传的控制器方法的写法
@RequestMapping("/traUpload") public String traFileupload(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
items = upload.parseRequest(request); for (FileItem item : items) { //判断对象是否是上传文件项 if(item.isFormField()){ //为普通表单项 }else{ //为上传文件项 //获取文件名称 String filename = item.getName(); //完成文件上传 item.write(new File(path, filename)); //删除临时文件 item.delete(); } } 跨服务器上传文件
准备另一个文件上传专用项目,按照路径写好文件上传的控制器方法
@RequestMapping("/upload") public String fileupload(HttpServletRequest request, MultipartFile upload) throws Exception{ //定义上传文件服务器路径 String path = "http://localhost:9090/uploads/"; //说明上传文件项 //获取文件名称 String filename = upload.getOriginalFilename(); //把文件的名称设置唯一值 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"; }
异常处理
出现异常后跳转到自定义的友好页面,默认情况下各层出现异常后都向上一级抛出,最终会到视图层,可以在前端控制器处配置一个异常处理器,在异常处理器中处理异常
编写自定义异常类(做提示信息)
public class SysException extends Exception{ //存储提示信息的 private String message; public String getMessage(){ return message; } public String setMessage(String message){ this.message = message; } public SysException(String message){ this.message = message; } }
编写异常处理器
public class SysExceptionResolver implements HandlerExceptionResolver{ @Override public ModelAndView resolveException(...){ //获取异常对象 SysException e = null; if(e instanceof SysException) e = (SysException)e; else e = new SysException("系统正在维护..."); //创建ModelAndView对象 ModelAndView mv = new ModelAndView(); mv.addObject("errorMsg", e.getMessage()); mv.setViewName("error"); //提前写好error.jsp页面 return nmv; } }
配置异常处理器(跳转到提示页面,springmvc.xml)
在可能出现异常的地方catch到异常后new一个新的自定义异常对象存入提示信息并throw
try{ int a = 10/0; } catch(Exception e){ e.printStackTrace(); throw new SysException("赋值失败..."); }
拦截器
SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理,也是AOP思想的具体应用
拦截器与过滤器的区别
过滤器是servlet规范中的一部分,任何java web项目都能用
拦截器是SpringMVC自己的,只有使用了SpringMVC框架的工程才能用
过滤器在url-pattern中配置了/*后可以对所有要访问的资源 拦截
拦截器只会拦截访问的控制器方法,不能拦截jsp,html,css,image,js等静态资源
创建自定义拦截器类(接口方法有默认实现,可以不重写)
public class MyInterceptor implements HandlerInterceptor{ @Override public boolean preHandle(...){ //TODO before controller, 返回true表示放行,不放行可以request转发 return true; } @Override public boolean postHandle(...){ //TODO after controller return true; } @Override public void afterCompletion(...){ //TODO after showing page } }
配置拦截器(springmvc.xml)
原理
- Tomcat 启动,对 DispatcherServlet 进行实例化,然后调用它的 init() 方法进行初始化,在这个初始化过程中完成了:
- 对 web.xml 中初始化参数的加载;建立 WebApplicationContext (SpringMVC的IOC容器);进行组件的初始化;
- 客户端发出请求,由 Tomcat 接收到这个请求,如果匹配 DispatcherServlet 在 web.xml 中配置的映射路径,Tomcat 就将请求转交给 DispatcherServlet 处理;
- DispatcherServlet 从容器中取出所有 HandlerMapping 实例(每个实例对应一个 HandlerMapping 接口的实现类)并遍历,每个 HandlerMapping 会根据请求信息,通过自己实现类中的方式去找到处理该请求的 Handler (执行程序,如Controller中的方法),并且将这个 Handler 与一堆 HandlerInterceptor (拦截器) 封装成一个 HandlerExecutionChain 对象,一旦有一个 HandlerMapping 可以找到 Handler 则退出循环;
- DispatcherServlet 取出 HandlerAdapter 组件,根据已经找到的 Handler,再从所有 HandlerAdapter 中找到可以处理该 Handler 的 HandlerAdapter 对象;
- 执行 HandlerExecutionChain 中所有拦截器的 preHandler() 方法,然后再利用 HandlerAdapter 执行 Handler ,执行完成得到 ModelAndView,再依次调用拦截器的 postHandler() 方法;
- 利用 ViewResolver 将 ModelAndView 或是 Exception(可解析成 ModelAndView)解析成 View,然后 View 会调用 render() 方法再根据 ModelAndView 中的数据渲染出页面;
- 最后再依次调用拦截器的 afterCompletion() 方法,一次请求就结束了。