一万字了解SpringMVC

文章详细介绍了MVC开发模式和三层架构的概念,强调它们的低耦合特性。SpringMVC作为主流JavaWeb框架,属于三层架构中的Controller层,文中通过配置和执行流程展示了其工作原理。此外,还提到了注解开发SpringMVC的方式,以及处理乱码问题和使用Jackson进行JSON转换的方法。
摘要由CSDN通过智能技术生成

mvc和三层架构

MVC(开发模式)

mvc是一种开发模式,强调的是程序设计的思想

M:model模型 表示实体模型以及对数据库操作的Dao层、以及service业务层
V:view视图 进行可视化展现的一层
C:controller控制器 控制请求转发以及响应的一层 前后端进行数据交互的层级

mvc具有低耦合、代码重用性高的特点,利于项目的分工协作。但同时也会让项目架构变得较为复杂

三层架构

三层架构将整个项目划分为展示层、业务逻辑层、数据访问层。而三层架构最显著的特点就是:解耦。任何一层发生改变都不会影响到另外一层。
比如学完springMVC之后的SSM三层框架
mybatis代表数据访问层:操作数据库进行CRUD
spring实现业务逻辑层:组合dao层的方法形成复杂的业务逻辑
springMVC是展示层:接收用户参数,封装和发送数据,转发JSP页面

三层架构与mvc都具有低耦合、维护性扩展性强的特点。但是会在一定程度上降低系统的性能。

mvc与三层架构的区别:

mvc中的模型就包含了三层框架中的业务逻辑层和数据访问层。而三层架构中的展示层包含了mvc中的视图和控制器,如下图所示:

在这里插入图片描述

SpringMVC

springMVC是spring framework提供的web组件,是一款主流的Javaweb开发框架。同时它对应的就是三层中的controller层,主要目的就是获取客户端请求,并在后端进行操作最终响应给浏览器。

SpringMVC的一些组件:

DispatcherServlet是整个springMVC的核心组件,主要负责调度其他组件执行,所有的请求request都会首先经过DispatcherServlet再去spring-mvc.xml文件中找到对应的handler执行。

HandlerMapping处理器映射器,主要目的是解析用户需求的url,并通过url寻找对应的handler。

Handler就是servlet,也可以是我们实现了Controller接口的类

HandlerAdapter处理器适配器,使Handler按照一定规则去执行

ModelAndView模型视图,作为Handler的返回结果,返回给DispatcherServlet

ViewResolver视图解析器,DispatcherServlet通过它将最终得到的ModelAndView数据进行可视化处理,并最终渲染到浏览器

执行流程:

  1. DispatcherServlet接收客户端的请求
  2. DispatcherServlet根据请求url利用HandlerMapping来寻找对应的handler
  3. HandlerExecution表示具体的handler控制器,它会把解析好的信息再发送给DispatcherServlet
  4. DispatcherServlet找到适配器HandlerAdapter,让其去执行handler
  5. Handler去执行编写好的controller
  6. 执行controller时需要开发人员控制去操作数据业务逻辑层
  7. 最终Controller将会返回给HandlerAdapter结果信息再交给DispatcherServlet,比如ModelAndView(包含要响应给前端的数据,以及将要跳转的视图)
  8. DispatcherServlet再把获取到的视图数据交给viewResolver进行解析,将这些物理数据转换为可视化的视图
  9. viewResolver再将数据传给DispatcherServlet
  10. 由DispatcherServlet将视图数据渲染给浏览器

HelloSpringMVC

第一个使用springmvc的程序,按照上面阐述的执行流程来进行编写

  1. 首先web.xml文件中配置DispatcherServlet
<?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">
  <!-- 配置DispatcherServlet -->
  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--     绑定spring的配置文件   -->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    <!--    设置启动级别为1  保证DispatcherServlet与项目同时启动   -->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <!--  
	/ :可以接收所有请求但是不包括.jsp
	/* : 接收所有请求包括.jsp
-->
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>
  1. 在springmvc配置文件中配置映射器、适配器、以及视图解析器
<?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
                           https://www.springframework.org/schema/beans/spring-beans.xsd">
  <!--    配置映射器  -->
  <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
  <!--    配置适配器  -->
  <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
  <!--    配置视图解析器  -->
  <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
  </bean>

  <bean class="com.yuqu.test.ReadySpringMVC" id="/ready"/>
</beans>
  1. 编写controller程序
public class ReadySpringMVC implements Controller {
  public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ModelAndView mv = new ModelAndView();
    // 向视图中添加要写给用户的数据
    mv.addObject("message","你好,SpringMVC");
    // 设置将要跳转的视图  设置好后该字段将会在视图解析器中进行补全
    //  ready--->  ready.jsp
    mv.setViewName("ready");
    return mv;
  }
}

最后编写jsp进行测试:

<body>
  ${message}
</body>

简单分析一波理解:首先用户提交请求后,请求先走DispatcherServlet也就是请求分发器,分发器根据将请求交给HandlerMapping映射器,映射器根据请求url来匹配对应的Handler,匹配到之后交给适配器,适配器操控Handler来执行已经编写好的controller。controller最终会返回一个结果,比如视图模型数据,该数据包含了想要展示给用户的数据以及将要跳转的视图界面。controller将结果提交HandlerAdapter适配器,适配器在交给分发器。分发器将视图模型数据交给视图解析器进行解析,解析完毕后再次回到分发器,最终渲染到浏览器视图界面中

使用注解开发SpringMVC

使用注解开发,首先编写web.xml文件,注册DispatcherServlet。

<!-- 配置DispatcherServlet -->
  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--  绑定spring配置文件  -->
    <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>

这个其实是写定的,只是有的时候spring核心配置文件名不同需要修改,每次使用的时候直接拿来就可以。然后为spring打开注解支持,首先我们要在头文件进行不成context和mvc,之后继续打开注解的支持、扫描包、资源过滤、以及开启自动注入的映射器HandlerMapping和HandlerAdapter适配器,最后仍然需要手动的去配置视图解析器,如下

<?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
                           https://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.yuqu.controller"/>
  <!--  默认过滤静态资源  -->
  <mvc:default-servlet-handler/>
  <!--  自动开启和注入HandlerMapping和HandlerAdapter  -->
  <mvc:annotation-driven/>

  <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="suffix" value=".jsp"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
  </bean>
</beans>

springmvc的配置文件其实也是可以给定的,只是当项目包名发生变化时修改一下扫描的路径即可。注解开发就是针对Controller的优化

@Controller
@RequestMapping("/hello")
public class HelloAnnoController {

  // localhost:8080/../hello/h1
  @RequestMapping("/h1")
  public String hello(Model model){
    // 封装数据
    model.addAttribute("message","HELLO ANNOCONTROLLER!");

    // 返回的是视图名会被视图解析器处理
    return "hello";
  }
}

@RequestMapping中我们可以写出请求的路径,以及请求的方式get、post等等
或者可以直接使用更加简便的注解

@PathVariable:RestFul风格再url地址栏获取参数时可以配置方法的参数映射

@RequestMapping(value = "/login/{a}")
    public String testForward(@PathVariable int a, Model model){

@GetMapping:完全等同于@RequestMapping( method = RequestMethod.GET )
@PostMapping、@PutMapping、@DeleteMapping、@PatchMapping
@RestController:不使用视图解析器与@Controller相似
@ResponseBody:所修饰的方法返回的字符串不再被视图解析器解析 仅仅是返回一个字符串
@RequestParam:表示该参数是接收前端的参数,可以为其匹配的字段起别名。前端中的参数名则必须与该别名一致

转发和重定向

首先是在不配置视图解析器的情况下,设置转发和重定向

@RequestMapping(value = "/login")
public String testForward(Model model){
  model.addAttribute("message","Hello,2QEW!");
  // 转发
  return "/WEB-INF/jsp/hello.jsp";// 需要使用全限定名
}

@RequestMapping(value = "/red")
public String testRedirect(Model model){
  model.addAttribute("message","Hello,3GFV!");
  // 重定向  数据无法发送到前端
  return "redirect:/index.jsp";
}

在重定向的过程中发现了一个问题,重定向时,model中所存储的数据会被清空,视图将会无法获取,但是转发不会。配置了视图解析器之后的转发和重定向使用方式类似,如下:

@RequestMapping(value = "/login")
public String testForward(Model model){
  model.addAttribute("message","Hello,2QEW!");
  // 转发
  return "/hello";// 不需要再使用全限定名  
}
@RequestMapping(value = "/red")
public String testRedirect(Model model){
  model.addAttribute("message","Hello,3GFV!");
  // 重定向  数据无法发送到前端
  return "redirect:/index.jsp";
}

前端数据回显

接收前端请求中的参数,并进行显式。首先来看如果接收的是一个字段的情况

@Controller
@RequestMapping(value = "/c3")
public class Controller3 {
  @RequestMapping(value = "/t1")
  public String test1(@RequestParam("username") String name, Model model){
    System.out.println("接收到前端的参数回显name="+name);
    model.addAttribute("message",name);
    return "hello";
  }
}

@RequestParam:表示该参数是接收前端的参数,可以为其匹配的字段起别名。前端中的参数名则必须与该别名一致

下面看如果传的是一个对象

@RequestMapping(value = "/t2")
public String test2(User user){
  System.out.println(user);
  return "hello";
}

我们可以直接将对象的类型作为形参去接收前端传回的对象数据,但是必须保证对象中的各属性字段与请求中的字段名一致,否则传回则为null
传参格式:http://localhost:8080/c3/t2?id=1&name=张三&age=13

解决乱码

配置Springmvc的过滤器

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

大佬过滤器

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;

/**
* 解决get和post请求 全部乱码的过滤器
*/
public class GenericEncodingFilter implements Filter {

  @Override
  public void destroy() {
  }

  @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 init(FilterConfig filterConfig) throws ServletException {
  }

}

//自定义request对象,HttpServletRequest的包装类
class MyRequest extends HttpServletRequestWrapper {

  private HttpServletRequest request;
  //是否编码的标记
  private boolean hasEncode;
  //定义一个可以传入HttpServletRequest对象的构造函数,以便对其进行装饰
  public MyRequest(HttpServletRequest request) {
    super(request);// super必须写
    this.request = request;
  }

  // 对需要增强方法 进行覆盖
  @Override
  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++) {
              try {
                // 处理get乱码
                values[i] = new String(values[i]
                                       .getBytes("ISO-8859-1"), "utf-8");
              } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
              }
            }
          }
        }
        hasEncode = true;
      }
      return parameterMap;
    }
    return super.getParameterMap();
  }

  //取一个值
  @Override
  public String getParameter(String name) {
    Map<String, String[]> parameterMap = getParameterMap();
    String[] values = parameterMap.get(name);
    if (values == null) {
      return null;
    }
    return values[0]; // 取回参数的第一个值
  }

  //取所有值
  @Override
  public String[] getParameterValues(String name) {
    Map<String, String[]> parameterMap = getParameterMap();
    String[] values = parameterMap.get(name);
    return values;
  }
}

关于Jackson

SpringMVC解决json格式的乱码问题

固定写法,拷贝使用

<!--  Spring配置解决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>

Jackson获取工具类

public static String getJson(Object obj) throws JsonProcessingException {
  ObjectMapper mapper = new ObjectMapper();
  return mapper.writeValueAsString(obj);
}
public static String getJson(Object obj,String dateForm) throws JsonProcessingException {
  ObjectMapper mapper = new ObjectMapper();
  SimpleDateFormat dateFormat = new SimpleDateFormat(dateForm);
  mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,false);
  mapper.setDateFormat(dateFormat);
  return mapper.writeValueAsString(obj);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

绿仔牛奶_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值