- MVC设计模式
- 什么是设计模式
设计模式是一套被反复使用,多是人知晓,经过分类,代码设计的经验总结使用设计模式的目的:为了代码可重用性,让代码更加容易被他人理解,保证代码可靠性设计模式就是一种模子,经过多年实践锤炼形成一套行之有效的完成某个特定任务的步骤和方式设计模式使代码编写真正工程化
- MVC设计模式
MVC设计模式是一种通用的软件编程思想在MVC设计模式中认为,任何软件都可以分为三部分组成1.控制程序流转的控制器 (Controller)2.负责数据处理数据的模型(Model)3.负责展示数据的视图(View)并且在MVC设计思想中要求一个符合MVC设计思想的软件应保证上面这三个部分相互独立,互不干扰,每一个部分只负责自己擅长的部分如果一个模块发生变化,应该尽量做到不影响其他两个模块,这样做的好处是软甲你的结构会变得更加的清晰,可读性强,有利于后去扩展和维护,并且代码可以实现复用,之前是 servlet,dao,jsp
- 什么是设计模式
- 初识SpringMVC
- servlet的缺点
通常情况下,一个servlet类只能负责处理一个请求,若项目中有成千上百个请求需要处理,就需要有成千上百个servlet,这样会使得项目中的servlet类的个数暴增当刻划断提交参数到服务器时,通过Servlet接受数据时,无论数据是什么格式,在Servlet中一律按照字符串进行接收,后期需要进行类型转化,复杂类型数据还需要特殊处理,特别麻烦servlet具有容器依赖性,必须放在服务器中运行,不利于单元测试
- SpringMVC简介
SpringMVC是Spring框架的一个模块,Spring和SpringMVC无需中间整合层整合SpringMVC是一个基于MVC设计模式的web框架
- SpringMVC五大组件
- DispatcherServlet:前端控制器,用于接收所有的请求,并将请求分发给控制器去处理
- HandlerServlet:映射处理器,用于记录请求路径与处理请求的控制器的对应关系
- Controller:控制器,用于处理实际请求,在同一个项目中可以有多个控制器
- ModelAndView:控制器的响应结果,该类型的名称中,Model表示控制器处理后得到的数据,View用于呈现数据的视图的名称
- ViewResolver:视图解析器,根据视图的名称,确定最终负责显示视图的组件
- SpringMVC运行流程
-
用户发送请求至前端控制器 前端控制器的作用:接收请求,调用其他组件处理请求,响应结果,相当于转发器,中央处理器,控制整个流程
- 前端控制器收到请求后调用映射处理器,映射处理器找到具体的Controller(可以根据xml配置,注解进行查找),并将Controller返回给前端控制器
- 前端控制器调用处理器适配器,处理器适配器经过适配调用具体的Controller,Controller执行完成后返回ModelAndView。Model(模型数据,Controller的处理结果),View(视图名,即负责展示结果的jsp的页面的名字)。处理器适配器将Controller的执行结果(ModelAndView)返回给前端控制器
- 前端控制器将执行的结果(ModelAndView)传给视图解析器(ViewReslover),视图解析器根据View(视图名)解析后返回具体的JSP页面
- 前端控制器根据Model对View进行渲染(将数据填充到页面中)
- 前端控制器将填充了数据的网页响应给用户整个过程中,需要开发人员编写的部分只有 Controller Service
Dao View
-
- servlet的缺点
- SpringMVC入门案例
- 创建maven-web项目
- 在pom.xml中引入springmvc依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.7.RELEASE</version> </dependency>
- 添加Tomcat环境
- 创建java文件夹
- 在web.xml中配置DispatcherServlet前端控制器
<web-app> <display-name>Archetype Created Web Application</display-name> <!--配置前端控制器,将所有请求交给SpringMVC处理--> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <!--配置SpringMVC核心配置文件的位置,默认SpringMVC配置文件是 在WEB-INF下,默认的 名字是springmvc-servlet,如果放在其他目录则需要指定如下配置 classpath代表根目录 --> <param-value>classpath:springmvc-config.xml</param-value> </init-param> <!--优先级最高--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <!--其中 / 表示拦截所有请求(除了jsp以外), 所有请求都需要经过前端 控制器--> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
在DispatcherServlet的父类FrameworkServlet中,定义了contextConfigLocation属性,用于表示Spring配置文件的路径,当配置了该属性后,初始化DispatcherServlet时,就会自动读取Spring的配置文件另外如果将DispatcherServlet配置为启动Tomcat时自动加载,可以实现当启动Tomcat时,就会加载Spring配置文件的效果所以我们可以为节点添加子级节点,以配置初始化参数,一般情况加,我们想让Tomcat启动时首先初始DispatcherServlet需要添加1 - 创建并配置springmvc-config.xml
<!--1.配置前端控制器放行静态资源(html/css/js)--> <mvc:default-servlet-handler></mvc:default-servlet-handler> <!--2.配置注解驱动,用于识别注解(比如@Controller)--> <mvc:annotation-driven></mvc:annotation-driven> <!--3.配置需要扫描的包,组件扫描--> <context:component-scan base-package="com.zb.controller"></context:component-scan> <!--4.配置视图解析器 prefix:配置路径前缀 suffix:配置路径后缀 /WEB-INF/pages/*.jsp --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages"></property> <property name="suffix" value=".jsp"></property> </bean>
- 编写HelloController控制器接收并处理请求
根据执行流程图,下一步需要处理的就是 HandlerMapping (映射处理器),这个部分的配置已经被注解方式简化,所以无需另外进行配置, 执行流程依然不变,知识开发者在编写代码时可以无视这个过程接下来,需要使用控制器 Controller 接收并处理请求,所以先创建控制器类
@Controller public class HelloController { }
在 SpringMVC 框架中,实际处理请求的是控制器类中的方法,所以,是一种请求对应方法的关系,并不是请求对应类接下来,需要在控制器类中添加处理请求的方法,关于方法的声明:1. 应使用 public 权限2. 暂时使用 String 作为返回值类型3. 方法名可以自定义4. 暂时不添加任何参数,参数列表为空 - 测试访问 http://localhost:8080/hello
- 显示页面
@Controller public class HelloController { @RequestMapping("/hello") public String hello(){ System.out.println("hello weiwei"); return "home";//返回后交给视图解析器 通过视图解析器进行匹配,返回页面 } }
- 创建maven-web项目
- SpringMVC的参数绑定
- 创建控制器,显示用户注册页面
@RequestMapping("regist") public String showRegist(){ return "regist"; }
- 创建注册页面 regist.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <form action="/doReg" method="post"> 用户名:<input type="text" name="username"/> <p></p> 密码:<input type="password" name="password"/> <p></p> 年龄:<input type="text" name="age"/> <p></p> 手机号码:<input type="text" name="phone"/> <p></p> 电子邮箱:<input type="text" name="email"/> <input type="submit" value="提交" /> </form> </body> </html>
- 创建控制器用来接收用户注册的参数
@RequestMapping("/doReg") public String doReg(HttpServletRequest request){ System.out.println("doReg"); String username=request.getParameter("username"); String password=request.getParameter("password"); String age=request.getParameter("age"); String phone=request.getParameter("phone"); String email=request.getParameter("email"); System.out.println(username+password+age+phone+email); return "login"; }
- 接收请求参数
- 不推荐通过HttpServletRequest获取请求参数
@RequestMapping("/doReg") public String doReg(HttpServletRequest request){ System.out.println("doReg"); String username=request.getParameter("username"); String password=request.getParameter("password"); String age=request.getParameter("age"); String phone=request.getParameter("phone"); String email=request.getParameter("email"); System.out.println(username+password+age+phone+email); return "login"; }
通常不推荐使用这种作法,主要原因有1. 使用方法比较麻烦2. 需要自行处理数据类型问题3. 不便于执行单元测试 - 推荐直接将请求参数设计为处理请求的方法的参数
@RequestMapping("/doReg") public String doReg(String username,String password,Integer age,Integer phone,String email){ System.out.println("doReg"); System.out.println(username+password+age+phone+email); return "login"; }
这种作法要求参数名称保持一致,即客户端提交的参数名为 username, 则方法的参数列表也叫 Username ,如果不一致,则服务器端将视为客户端没有提交某个参数,方法中对应的参数值将是 null这种方法不适用于,参数比较多的应用场景 - 推荐使用封装的类型接收请求参数
当请求参数的数量较多时,可以将这邪恶参数封装到一个自定义的数据类型中 (实体类)当然,依然需要保持名称一致:即请求参数的名称,与自定义数据类型中的属性名称保持一致
@RequestMapping("/doReg") public String doReg(User user){ System.out.println(user); return "login"; }
@Data //此注解的作用就是生成get set方法 @ToString //此注解的作用就是生成 toString方法 public class User { private String username; private String password; private Integer age; private String phone; private String email; }
- 小结
首先无论什么情况下,都不必通过 HttpServletRequest 获取参数如果参数数量固定且较少,使用第二种方式如果参数数量较多,而且可能进行调整,优先使用第三种方式
- 不推荐通过HttpServletRequest获取请求参数
- 补充
在 Controller 中方法的返回值类型可以是 String 也可以是ModelAndView最常用的就是 String ,简单明了@ResponseBody注解的作用:此注解用来指定给浏览器返回的是文本数据
@Controller public class PageController { @RequestMapping("login") public String showLogin(){ return "login"; } @RequestMapping("login2") public ModelAndView showLogin2(){ ModelAndView mv=new ModelAndView(); mv.addObject("这是我的数据");//设置数据 mv.setViewName("login"); return mv; } @RequestMapping("showString") @ResponseBody//此注解用来指定给浏览器返回的是文本数据 public String showString(){ return "showString"; } }
- 创建控制器,显示用户注册页面
- 解决中文乱码问题
在前面学servlet学习中,我们学习了get和post请求参数乱问如何解决,SpringMVC也提供了解决请求参数乱码的方案,在web.xml中配置过滤器,可以解决post提交参数乱码问题 在web.xml配置标签时要遵循,顺序==filter listener servlet
<filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
- springmvc响应数据
- 转发和重定向
在控制器中,不适合通过大量的输出语句向客户端输出HTML代码,而JSP页面中虽然可以编写Java代码,但是,由于其中本来就已经存在HTML,CSS,JS相关代码,就不适合再在其中混杂Java代码,也就是说JSP不适合处理数据,所以推荐的作法就是:控制器完成数据的处理,然后将数据交给JSP文件负责展示,这种作法称之为转发 例如 return "login";当用户请求已经处理完毕后们可以使用重定向的作法让客户端访问另一个页面,例如注册成功后重定向到登录页,在SpringMVC中,如果处理请求的方法的返回值是String,默认表示转发,如果需要重定向,返回的字符串需要使用,redirect: 作为前缀 例如:return "redirect:login";
- 控制器向jsp转发数据
需求:假设用户登陆时,提交的用户名为root且密码是1234时,允许登录,登录成功后我们暂时不考虑页面显示问题,如果用户名或密码错误,需要在专门的错误提示页面中进行显示可以先设计一个专门用于提示错误的error.jsp页面,并且,在处理登录的方法中,对用户名和密码进行判断
- 不推荐--使用HttpServletRequest封装转发的数据
在处理请求的方法中,添加HttpServletRequest类型的参数,当需要转发某数据到jsp页面时,调用HttpServletRequest参数对象的setAttribute(key ,value)封装转发的数据在 jsp 页面中,取值, EL 表达式如果不起作用,需要声明 <%@ page isELIgnored="false" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page isELIgnored="false" %> <html> <head> <title>Title</title> </head> <body> <h2>错误提示</h2> <%=request.getAttribute("error")%> </body> </html>
@RequestMapping("/doLogin") public String doLogin(String username,String password,HttpServletRequest request){ System.out.println("doLogin"); if("root".equals(username)&&"123".equals(password)){ return "home"; }else { request.setAttribute("error","用户名或密码错误"); return "error"; } }
- 推荐--使用ModelMap封装转发数据
使用方式与HttpServletRequest几乎完全相同modelMap.addAttribute(key,value)
@RequestMapping("/doLogin") public String doLogin(String username, String password, ModelMap modelMap){ System.out.println("doLogin"); if("root".equals(username)&&"123".equals(password)){ return "home"; }else { modelMap.addAttribute("error","用户名或密码错误"); return "error"; } }
- 转发和重定向
- 关于@RequestMapping
在处理请求的方法前添加@RequestMapping注解可以配置请求路径与处理请求的方法的映射关系其实该注解还可以添加到类的声明之前例如:
@Controller @RequestMapping("user") public class UserController {
添加了以上注解后,就会在请求路径中多产生一个层级,例如原本URL http://localhost:8080/doLogin 就会变成http://localhost:8080/user/doLogin 在类之前添加该注解,可以解决实际项目中设计各种相似的功能的URL名称的问题,只需要类之前配置的名称不同即可,各相似功能可以配置相同的名称,所以在实际应用中,通常建议为每一个控制类添加该注解<body> <form action="/user/doLogin" method="post"> 用户名:<input type="text" name="username" /> 密码:<input type="password" name="password" /> <input type="submit" value="提交"> </form> </body>
- 处理session 登录成功后跳转到首页,首页显示session中的用户名
@RequestMapping("/doLogin") public String doLogin(String username, String password, ModelMap modelMap, HttpSession session){ System.out.println("doLogin"); if("root".equals(username)&&"123".equals(password)){ session.setAttribute("username",username); return "home"; }else { modelMap.addAttribute("error","用户名或密码错误"); return "error"; } }
在方法的处理过程中,也可以调用 HttpSession 参数对象的 setAttribute() 方法向session 中封装数据,也可以调用 getAttribute() 方法获取 session 中的数据 - @RequestParam注解
通过该注解的配置,可以解决客户端提交的请求参数的名称,与控制器中处理请求的方法的名称不一致的问题,例如客户端提交的请求参数叫username,而控制器中的方法的参数叫name,就会不一致,使用该注解可以解决这个问题
@RequestMapping("/doLogin") public String doLogin(@RequestParam("username") String name, String password, ModelMap modelMap, HttpSession session){ System.out.println("doLogin"); if("root".equals(name)&&"123".equals(password)){ session.setAttribute("username",name); return "home"; }else { modelMap.addAttribute("error","用户名或密码错误"); return "error"; } }
- SpringMVC中的拦截器
拦截器可以设置在许多请求的访问过程中,拦截器内部可以对请求的各项参数进行判断,从而决定拦截下来,阻止程序继续执行,或决定放行,让程序执行后续的流程拦截器存在的价值并不一定是要将某些请求拦截下来,阻止运行,也可能是希望若干种请求都应执行相同的代码例如:实现登录拦截,即已登录时可以正常访问,未登录时不允许访问,将重定向到登录页
- 首先应创建一个拦截器类 LoginInterceptor,实现HandlerInterceptor接口,其中preHandle()方法,可以在该方法种执行逻辑判断
HandlerInterceptor 接口的源码种,可以看到高版本的spring-webmvcjar包中,HandlerInterceptor 接口定义的是默认方法,这是jdk1.8的新特性,也就是说接口中的方法你重写也可以,不重写也不会报错。
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //判断session中username是否为null HttpSession session=request.getSession(); if(session.getAttribute("username")==null){ //重定向到登录页 String path=request.getServletContext().getContextPath(); response.sendRedirect(path+"/login"); return false; } return true; } }
- 在配置文件中配置需要拦截的路径
springMVC 环境中,所有的拦截器都需要在配置文件中进行配置,拦截器可以有多个并且在匹配时,还可以使用*作为通配符。例如配置为
在spring-config.xml <!--5.配置拦截器--> <mvc:interceptors> <!--配置第一个拦截器--> <mvc:interceptor> <mvc:mapping path="/home"/> <mvc:mapping path="/index"/> <!--还可以使用通配符配置--> <mvc:mapping path="/order/*"/> <bean class="com.zb.common.LoginInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
<mvc:mapping path="/order/*"/>这种方法只能匹配一层路径 例如 /order/delete /order/update如果需要实现若干各层级的匹配,是要使用 **<mvc:mapping path="/order/**"/>则无视后续有多少层级,都可以匹配,例如 /order/delete/order/vip/update/ 除此之外,我们可以配置黑名单,我们还可以配置白名单<!--5.配置拦截器--> <mvc:interceptors> <!--配置第一个拦截器--> <mvc:interceptor> <!--配置黑名单--> <mvc:mapping path="/*"/> <!--配置白名单--> <mvc:exclude-mapping path="/regist"/> <mvc:exclude-mapping path="/login"/> <bean class="com.zb.common.LoginInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
- 首先应创建一个拦截器类 LoginInterceptor,实现HandlerInterceptor接口,其中preHandle()方法,可以在该方法种执行逻辑判断
- SpringMVC实现文件上传
- SpringMVC开启文件上传功能,注意此处ID 不能更改,因为文件上传类中已经定义好的规则
<bean id="MultipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="5000000" /> <property name="defaultEncoding" value="UTF-8" /> </bean>
- 准备JSP页面uploadFile.jsp 提交方式必须为post enctype="multipart/form-data" 设置此属性后浏览才才会将文件以二进制的数据发送给服务器
<form action="/uploadFile" method="post" enctype="multipart/form-data"> 头像:<input type="file" name="file" /> 个人简历:<input type="file" name="file" /> <input type="submit" value="提交" /> </form>
- 引入文件上传的Jar包
<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> </dependency>
- 编写Controller用来显示上传页面
@RequestMapping("/upload") public String uploadFile(){ return "uploadFile"; }
- SpringMVC开启文件上传功能,注意此处ID 不能更改,因为文件上传类中已经定义好的规则
JSR303
输入验证是Spring处理的重要web开发任务之一,在SpringMVC中,有两种方式可以验证输入,一是利用Spring自带的验证框架,二就是用JSR303实现。建议大家使用JSR303,因为JSR303是正式Java规范,使用相对简单
JSR只是一个规范文档,本身用处并不大,除非编写了他的实现,对于JSR目前有两个实现,第一个是Hibernate Validator(JSR303) ,第二个是 Apache BVal
1.JSR303
约束和
Hibernate Validator
约束
JSR303是标准的校验注解,Hibernate Validator也有自带的校验注解,这些注解约束,使用在实体类的成员变量中
Hibernate Validator是JSR303的一个实现
![](https://i-blog.csdnimg.cn/blog_migrate/f9852fe3f968af7316edf7cf01d43d24.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c0a3565eb94bf665516a65931acce8f2.png)
1.1导入依赖
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.1.Final</version>
</dependency>
1.2在实体类属性上添加校验注解
public class User {
@NotEmpty(message = "用户名不能为空")
private String username;
@Length(max=10,min=5,message = "密码长度不正确")
private String password;
private Integer age;
private Integer phone;
@Email(message = "邮箱格式不正确")
private String email;
}
1.3在控制器层,开启校验
@Valid
:当该对象的类中已经定义好了校验规则,利用
@Valid
注解在控制器的方法中对指定对象进行校验
BindingResult
用于获取校验失败后的反馈信息
@RequestMapping("/doReg")
public String doReg(@Valid User user, BindingResult result){
System.out.println("doReg");
if (result.hasErrors()){
List<ObjectError> errorList = result.getAllErrors();
for(ObjectError error : errorList){
System.out.println(error.getDefaultMessage());
}
return "regist";
}
System.out.println(user);
return "redirect:login";
}