MVC设计模式
MVC是三个单词的首字母缩写,它们是Model(模型)、View(视图)和Controller(控制)
1、视图
视图(View)代表用户交互界面,对于Web应用来说,可以概括为HTML界面,但有可能为XHTML、XML和Applet。随着应用的复杂性和规模性,界面的处理也变得具有挑战性。一个应用可能有很多不同的视图,MVC设计模式对于视图的处理仅限于视图上数据的采集和处理,以及用户的请求,而不包括在视图上的业务流程的处理。业务流程的处理交予模型(Model)处理。比如一个订单的视图只接受来自模型的数据并显示给用户,以及将用户界面的输入数据和请求传递给控制和模型。
2、 模型
模型(Model):就是业务流程/状态的处理以及业务规则的制定。业务流程的处理过程对其它层来说是黑箱操作,模型接受视图请求的数据,并返回最终的处理结果。**业务模型的设计可以说是MVC最主要的核心。**目前流行的EJB模型就是一个典型的应用例子,它从应用技术实现的角度对模型做了进一步的划分,以便充分利用现有的组件,但它不能作为应用设计模型的框架。它仅仅告诉你按这种模型设计就可以利用某些技术组件,从而减少了技术上的困难。对一个开发者来说,就可以专注于业务模型的设计。MVC设计模式告诉我们,把应用的模型按一定的规则抽取出来,抽取的层次很重要,这也是判断开发人员是否优秀的设计依据。抽象与具体不能隔得太远,也不能太近。MVC并没有提供模型的设计方法,而只告诉你应该组织管理这些模型,以便于模型的重构和提高重用性。我们可以用对象编程来做比喻,MVC定义了一个顶级类,告诉它的子类你只能做这些,但没法限制你能做这些。这点对编程的开发人员非常重要。
3、控制
控制(Controller)可以理解为从用户接收请求, 将模型与视图匹配在一起,共同完成用户的请求。划分控制层的作用也很明显,它清楚地告诉你,它就是一个分发器,选择什么样的模型,选择什么样的视图,可以完成什么样的用户请求。控制层并不做任何的数据处理。例如,用户点击一个连接,控制层接受请求后, 并不处理业务信息,它只把用户的信息传递给模型,告诉模型做什么,选择符合要求的视图返回给用户。因此,一个模型可能对应多个视图,一个视图可能对应多个模型。
SpringMVC
SpringMVC是Spring的一部分,如图:
SpringMVC的核心架构:
具体流程:
(1)首先浏览器发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
(2)DispatcherServlet——>HandlerMapping,处理器映射器将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器对象、多个HandlerInterceptor拦截器)对象;
(3)DispatcherServlet——>HandlerAdapter,处理器适配器将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
(4)HandlerAdapter——>调用处理器相应功能处理方法,并返回一个ModelAndView对象(包含模型数据、逻辑视图名);
(5)ModelAndView对象(Model部分是业务对象返回的模型数据,View部分为逻辑视图名)——> ViewResolver, 视图解析器将把逻辑视图名解析为具体的View;
(6)View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构;
(7)返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。
依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.10</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl-api</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
方式一(接口)
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--1.注册 DispatcherServlet 请求分发器 前端控制器-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--通过初始化参数指定SpringMVC配置文件的位置,进行关联-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<!--启动级别,数字越小,启动越早-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--所有请求都会被springmvc拦截-->
<!--/ 匹配所有请求 :(不包括.jsp)-->
<!--/* 匹配所有请求 :(包括.jsp)-->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
springmvc-servlet.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--处理器映射器-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!--处理器适配器-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<!--视图解析器-->
<!--
获取 ModelAndView中的数据
解析 ModelAndView的视图名字
拼接视图名字,找到对应的视图 /WEB-INF/jsp/hello.jsp
将数据渲染到视图上
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<!--前缀-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!--后缀-->
<property name="suffix" value=".jsp"/>
</bean>
<!--Handler-->
<bean id="/hello" class="com.jiutian.controller.HelloController"/>
</beans>
Controller:
public class HelloController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
// ModelAndView 模型和视图
ModelAndView mv = new ModelAndView();
// 业务代码
String result = "HelloSpringMVC";
// 封装对象
mv.addObject("msg",result);
//视图跳转
mv.setViewName("hello"); //: /WEB-INF/jsp/hello.jsp
return mv;
}
}
方式二(注解)
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
springmvc-servlet.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--自动扫描包-->
<context:component-scan base-package="com.jiutian.controller"/>
<!--让SpringMVC不处理静态资源 css,js-->
<mvc:default-servlet-handler/>
<mvc:annotation-driven/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
Controller:
@Controller
// @RequestMapping("/hello") // /hello/h1
public class HelloController {
@RequestMapping("/h1")
public String hello(Model model){
// 封装数据
model.addAttribute("msg","Hello,SpringMVCAnnotation!");
return "hello"; //会被视图解析器处理
}
}
注解@controller
@Componet //组件
@Controller //controller
@Service //service
@Repository //dao
RestFul风格
@Controller
public class RestFulController {
// 原来的: http://localhost:8080/add?a=1&b=2
// RestFul: http://localhost:8080/add/1/2
//请求方式不限,不指定请求方式时优先级最低
@RequestMapping("/add/{a}/{b}")
public String test1(@PathVariable int a, @PathVariable int b, Model model) {
int res = a + b;
model.addAttribute("msg", "结果1:" + res);
return "test";
}
//value 或 path
//@RequestMapping(value = "/add/{a}/{b}", method = RequestMethod.GET)
@GetMapping("/add/{a}/{b}")
public String test2(@PathVariable int a, @PathVariable int b, Model model) {
int res = a + b;
model.addAttribute("msg", "结果2:" + res);
return "test";
}
@PostMapping("/add/{a}/{b}")
public String test3(@PathVariable int a, @PathVariable int b, Model model) {
int res = a + b;
model.addAttribute("msg", "结果3:" + res);
return "test";
}
}
转发和重定向
@Controller
public class ModelTest1 {
@RequestMapping("/m1/t1")
public String test1(HttpServletRequest request, HttpServletResponse response){
System.out.println(request.getSession().getId());
return "test";
}
// 转发
@RequestMapping("/m1/t2")
public String test2(Model model){
model.addAttribute("msg","ModelTest1");
//无视图解析器 需要全限定名
//return "forward:/WEB-INF/jsp/test.jsp"; // return "/WEB-INF/jsp/test.jsp";
//有视图解析器直接写(会拼接) return "test";
return "test";
}
// 重定向
@RequestMapping("/m1/t3")
public String test3(Model model){
model.addAttribute("msg","ModelTest1");
//需要全限定名
return "redirect:/index.jsp";
//return "redirect:/WEB-INF/jsp/test.jsp"; 会 404
}
}
注意: 重定向不能跳到受保护的资源 [/WEB-INF] 目录下。
乱码解决
1.自定义过滤器 (高级):
public class GenericEncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//处理response的字符编码
HttpServletResponse myResponse = (HttpServletResponse) response;
myResponse.setContentType("text/html;charset=UTF-8");//转型为与协议相关对象
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
//对request包装增强
HttpServletRequest myrequest = new MyRequest(httpServletRequest);
chain.doFilter(myrequest, response);
}
@Override
public void destroy() {
}
//自定义request对象,HttpServletRequest的包装类
static class MyRequest extends HttpServletRequestWrapper {
private HttpServletRequest request;
//是否编码的标记
private boolean hasEncode;
//定义一个可以传入HttpServletRequest对象的构造函数,以便对其进行装饰
public MyRequest(HttpServletRequest request) {
super(request);//super必须写
this.request = request;
}
public Map getParameterMap() {
//先获得请求方式
String method = request.getMethod();
if (method.equalsIgnoreCase("post")) {
//post请求
try {
//处理post乱码
request.setCharacterEncoding("utf-8");
return request.getParameterMap();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else if (method.equalsIgnoreCase("get")) {
//get请求
Map<String, String[]> parameterMap = request.getParameterMap();
if (!hasEncode) { //确保get手动编码逻辑只运行一次
for (String parameterName : parameterMap.keySet()) {
String[] values = parameterMap.get(parameterName);
if (values != null) {
for (int i = 0; i < values.length; i++) {
//处理get乱码
values[i] = new String(values[i].getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
}
}
}
hasEncode = true;
}
return parameterMap;
}
return super.getParameterMap();
}
public String getParameter(String name) {
Map<String, String[]> parameterMap = getParameterMap();
String[] values = parameterMap.get(name);
if (values == null) {
return null;
}
return values[0];//取回参数的第一个值
}
//取所有值
public String[] getParameterValues(String name) {
Map<String, String[]> parameterMap = getParameterMap();
String[] values = parameterMap.get(name);
return values;
}
}
}
2.使用Spring的:
web.xml
<filter>
<filter-name>encoding</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>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
JSON
依赖:
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
乱码解决:
springmvc-servlet.xml:
<!--JSON乱码问题配置-->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8"/>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="failOnEmptyBeans" value="false"/>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
工具类:
public class JsonUtils {
public static String getJson(Object object){
return getJson(object,"yyyy-MM-dd HH:mm:ss");
}
public static String getJson(Object object,String format) {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS,false);
SimpleDateFormat sdf = new SimpleDateFormat(format);
mapper.setDateFormat(sdf);
try {
return mapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
}
使用:
@RestController //直接返回一个字符串
// @Controller
public class UserController {
//@RequestMapping(value = "/j1",produces = "application/json;charset=utf-8")
@RequestMapping("/j1")
// @ResponseBody //它就不会走视图解析器,直接返回一个字符串
public String json1() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
User user = new User(1, "张三", 12);
return mapper.writeValueAsString(user);
}
@RequestMapping("/j2")
public String json2() {
List<User> userList = new ArrayList<>();
userList.add(new User(1, "张三1", 12));
userList.add(new User(2, "张三2", 12));
userList.add(new User(3, "张三3", 12));
userList.add(new User(4, "张三4", 12));
return JsonUtils.getJson(userList);
}
@RequestMapping("/j3")
public String json3() {
Date date = new Date();
return JsonUtils.getJson(date);
}
@RequestMapping("/j4")
public String json4() {
List<User> userList = new ArrayList<>();
userList.add(new User(1, "张三1", 12));
userList.add(new User(2, "张三2", 12));
userList.add(new User(3, "张三3", 12));
userList.add(new User(4, "张三4", 12));
// Java对象 转 Json 字符串
String str1 = JSON.toJSONString(userList.get(0));
System.out.println(str1);
// Json字符串转 Java对象
User user = JSON.parseObject(str1, User.class);
System.out.println(user);
// Java对象转Json对象
JSONObject jsonObject = (JSONObject) JSON.toJSON(userList.get(1));
System.out.println(jsonObject.getString("name"));
// Json对象转Java对象
User user1 = JSON.toJavaObject(jsonObject, User.class);
System.out.println(user1);
return JSON.toJSONString(userList);
}
}
拦截器
public class MyInterceptor implements HandlerInterceptor {
// return true; 执行下一个拦截器,放行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("========处理前========");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// System.out.println("========处理后=======");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// System.out.println("========清理=======");
}
}
配置:
<mvc:interceptors>
<mvc:interceptor>
<!--包括这个请求下面的所有请求-->
<mvc:mapping path="/**"/>
<bean class="com.jiutian.config.MyInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<!--包括这个请求下面的所有请求-->
<mvc:mapping path="/user/**"/>
<bean class="com.jiutian.config.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
文件上传及下载
依赖:
<!--导入高版本的servlet-api-->
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
文件上传
配置:
<!--文件上传配置-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--请求的编码方式,必须和jsp的pageEncoding属性一致,以便正确读取表单中的内容,默认为ISO-8859-1-->
<property name="defaultEncoding" value="utf-8"/>
<!--上传文件大小限制,单位为字节(10485760=10M)-->
<property name="maxUploadSize" value="10485760"/>
<property name="maxInMemorySize" value="40960"/>
</bean>
controller:
//@RequestParam("file") 将 name=file控件得到的文件封装成 CommonsMultipartFile 对象
// 批量上传CommonsMultipartFile则为数组即可
@RequestMapping("/upload")
public String fileUpload(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {
//获取文件名 : file.getOriginalFilename();
String uploadFileName = file.getOriginalFilename();
//如果文件名为空,直接回到首页!
if ("".equals(uploadFileName)) {
return "redirect:/index.jsp";
}
System.out.println("上传文件名 : " + uploadFileName);
//上传路径保存设置
String path = request.getServletContext().getRealPath("/upload");
//如果路径不存在,创建一个 File
File realPath = new File(path);
if (!realPath.exists()) {
realPath.mkdir();
}
System.out.println("上传文件保存地址:" + realPath);
InputStream is = file.getInputStream();//文件输入流
OutputStream os = new FileOutputStream(new File(realPath, uploadFileName));//文件输出流
// 读取写出
int len = 0;
byte[] buffer = new byte[1024];
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
os.flush();
}
os.close();
is.close();
return "redirect:/index.jsp";
}
//采用file.Transto 来保存上传的文件
@RequestMapping("/upload2")
public String fileUpload2(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {
//上传路径保存设置
String path = request.getServletContext().getRealPath("/upload");
File realPath = new File(path);
if (!realPath.exists()) {
realPath.mkdir();
}
//上传文件地址
System.out.println("上传文件保存地址:" + realPath);
//通过CommonsMultipartFile的方法直接写文件(注意这个时候)
file.transferTo(new File(realPath + "/" + file.getOriginalFilename()));
return "redirect:/index.jsp";
}
使用:
<%--上传文件--%>
<form action="${pageContext.request.contextPath}/upload" enctype="multipart/form-data" method="post">
<input type="file" name="file"/>
<input type="submit" value="upload">
</form>
文件下载
@RequestMapping(value = "/download")
public String downloads(HttpServletResponse response, HttpServletRequest request) throws Exception {
//要下载的图片地址
String path = request.getServletContext().getRealPath("/upload");
String fileName = "1.png";
//1、设置response 响应头
response.reset();//设置页面不缓存,清空buffer
response.setCharacterEncoding("UTF-8"); //字符编码
response.setContentType("multipart/form-data"); //二进制传输数据
// 设置响应头
response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(fileName, "UTF-8"));
File file = new File(path, fileName);
//2、 读取文件--输入流
InputStream input = new FileInputStream(file);
//3、 写出文件--输出流
OutputStream out = response.getOutputStream();
byte[] buff = new byte[1024];
int index = 0;
//4、执行 写出操作
while ((index = input.read(buff)) != -1) {
out.write(buff, 0, index);
out.flush();
}
out.close();
input.close();
return "ok";
}
使用:
<%--下载文件--%>
<p><a href="${pageContext.request.contextPath}/download">下载图片1</a></p>
<p><a href="${pageContext.request.contextPath}/statics/1.png">下载图片2</a></p>