MVC设计模式

MVC是三个单词的首字母缩写,它们是Model(模型)、View(视图)和Controller(控制)

1、视图

​ 视图(View)代表用户交互界面,对于Web应用来说,可以概括为HTML界面,但有可能为XHTML、XML和Applet。随着应用的复杂性和规模性,界面的处理也变得具有挑战性。一个应用可能有很多不同的视图,MVC设计模式对于视图的处理仅限于视图上数据的采集和处理,以及用户的请求,而不包括在视图上的业务流程的处理。业务流程的处理交予模型(Model)处理。比如一个订单的视图只接受来自模型的数据并显示给用户,以及将用户界面的输入数据和请求传递给控制和模型。

2、 模型

​ 模型(Model):就是业务流程/状态的处理以及业务规则的制定。业务流程的处理过程对其它层来说是黑箱操作,模型接受视图请求的数据,并返回最终的处理结果。**业务模型的设计可以说是MVC最主要的核心。**目前流行的EJB模型就是一个典型的应用例子,它从应用技术实现的角度对模型做了进一步的划分,以便充分利用现有的组件,但它不能作为应用设计模型的框架。它仅仅告诉你按这种模型设计就可以利用某些技术组件,从而减少了技术上的困难。对一个开发者来说,就可以专注于业务模型的设计。MVC设计模式告诉我们,把应用的模型按一定的规则抽取出来,抽取的层次很重要,这也是判断开发人员是否优秀的设计依据。抽象与具体不能隔得太远,也不能太近。MVC并没有提供模型的设计方法,而只告诉你应该组织管理这些模型,以便于模型的重构和提高重用性。我们可以用对象编程来做比喻,MVC定义了一个顶级类,告诉它的子类你只能做这些,但没法限制你能做这些。这点对编程的开发人员非常重要。

3、控制

​ 控制(Controller)可以理解为从用户接收请求, 将模型与视图匹配在一起,共同完成用户的请求。划分控制层的作用也很明显,它清楚地告诉你,它就是一个分发器,选择什么样的模型,选择什么样的视图,可以完成什么样的用户请求。控制层并不做任何的数据处理。例如,用户点击一个连接,控制层接受请求后, 并不处理业务信息,它只把用户的信息传递给模型,告诉模型做什么,选择符合要求的视图返回给用户。因此,一个模型可能对应多个视图,一个视图可能对应多个模型。
1641207552232.png
1641207563989.png

SpringMVC

SpringMVC是Spring的一部分,如图:
1641207760118.png

SpringMVC的核心架构:
1641207917226.png

具体流程:

(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>

Q.E.D.


以无限为有限,以无法为有法