SpringMVC

MVC

MVC是模型(Model), 视图(View), 控制器(Controller)的简写, 是一种软件设计规范, 是将业务逻辑, 数据, 显示分离的方法来组织代码.

主要作用是降低 降低视图与业务逻辑间的双向耦合

MVC不是一种设计模式, MVC是一种架构模式

回顾Servlet

  1. 新建Maven项目, 导入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    <dependencies>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.7.RELEASE</version>
    </dependency>

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

    <!-- https://mvnrepository.com/artifact/javax.servlet.jsp/jsp-api -->
    <dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.2</version>
    <scope>provided</scope>
    </dependency>

    <!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
    <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
    </dependency>
    </dependencies>
  2. 编写Servlet方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //1. 获取前端参数
    String method = req.getParameter("method");
    if (method.equals("add")) {
    req.getSession().setAttribute("msg", "执行了add方法");
    }
    if (method.equals("delete")) {
    req.getSession().setAttribute("msg", "执行了delete方法");
    }

    //2. 调用业务层

    //3. 视图转发或重定向
    req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    doGet(req, resp);
    }
    }
  3. 在web.xml中进行页面的绑定

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <?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>HelloServlet</servlet-name>
    <servlet-class>com.lxb.servlet.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
    <servlet-name>HelloServlet</servlet-name>
    <url-pattern>/helloServlet</url-pattern>
    </servlet-mapping>

    </web-app>

HelloSpringMVC

  1. 创建一个Maven项目, 确定依赖和包都正确导入

  2. 新建一个.jsp页面

  3. 在web.xml中配置DispatcherServlet, 这是SpringMVC的核心!

    作为请求分发器, 或前端控制器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <?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>
    <!-- DispatcherServlet要绑定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>

    </web-app>

    这里的地址写的是/, 而不是/*

    /: 只匹配所有的请求, 不会去匹配jsp页面

    /*: 匹配所有的请求, 包括jsp页面

  4. 写Spring配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <?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"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver">
    <property name="prefix" value="WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
    </bean>

    <bean id="/hello" class="com.lxb.controller.HelloController"/>
    </beans>

    第一个bean是 处理器映射器

    第二个bean是 处理器适配器

    第三个bean是 视图解析器

    ​ 视图解析器中第一个属性为 前缀

    ​ 第二个属性为 后缀

  5. 编写Controller代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class HelloController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
    ModelAndView mv = new ModelAndView();

    // 业务代码
    String result = "HelloSpringMVC";

    mv.addObject("msg", result);

    // 视图跳转
    mv.setViewName("test");

    return mv;
    }
    }

使用注解开发

  1. 写依赖, 导包

  2. 写web.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <?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>
    <!-- DispatcherServlet要绑定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>
    </web-app>
  3. 写Spring配置文件

    注意引入的约束, 这些都是写死的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <?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:contex="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">

    <contex:component-scan base-package="com.lxb.controller"/>
    <mvc:default-servlet-handler/>
    <mvc:annotation-driven/>

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

    </beans>
  4. 写业务控制器代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Controller
    public class HelloController {
    // 真实访问地址
    @RequestMapping("/hello")
    public String hello(Model model){
    // 封装数据, 向模型中添加属性msg值, 可以在jsp页面中取出并渲染
    model.addAttribute("msg", "Hello, springMVCAnnotation");
    // 对应的jsp文件名
    return "hello";
    }
    }

    就这么简单, 一个方法就对应了一个Servlet.

    总结:

    使用SpringMVC必须配置的三大件:

    处理器映射器, 处理器适配器, 视图解析器

    前两项被<mvc:annotation-driven/>干掉了, 只需要手动配置一个视图解析器就好, 而且是固定的

Controller

如果是实现接口, 这个类会处理请求, 并返回一个ModelAndView类

如果使用注解, 代表这个类会被Spring接管, 被这个注解的类, 所有的方法, 如果返回值是String, 并且有具体页面可以跳转, 那么就会被视图解析器解析.

RequestMapping

1
2
3
4
5
6
7
8
9
@Controller
@RequestMapping("/c2")
public class ControllerDemo2 {
@RequestMapping("/t1")
public String test1(Model model){
model.addAttribute("msg", "helloControllerDemo2");
return "test";
}
}

@RequestMapping可以声明在类上, 也可以声明在方法上, 如果声明在类上, 如上述代码所示, 则真实的访问地址为:

http://localhost:8080/c2/t1

RestFull风格

对于如下代码:

1
2
3
4
5
6
7
8
9
@Controller
public class RestFullController {
@RequestMapping("/add")
public String test1(int a, int b, Model model) {
int res = a + b;
model.addAttribute("msg", "结果为: " + res);
return "test";
}
}

如果直接访问http://localhost:8080/add是错误的页面, 需要填写地址为http://localhost:8080/add?a=1&b=2

以上原来的方式, 下面是用RestFull风格

修改代码为:

1
2
3
4
5
6
7
8
9
@Controller
public class RestFullController {
@RequestMapping("/add/{a}/{b}")
public String test1(@PathVariable int a, @PathVariable int b, Model model) {
int res = a + b;
model.addAttribute("msg", "结果为: " + res);
return "test";
}
}

加入两个注解之后, 直接把需要查询的数字写在地址里即可, 访问地址为http://localhost:8080/add/1/2

可以限定访问的方法:

1
@RequestMapping(value = "/add/{a}/{b}", method = RequestMethod.GET)

这样只能通过Get方法访问

或者直接使用以下注解:

1
@GetMapping("/add/{a}/{b}")

每种方法都对应了一个Mapping

重定向和转发

无需视图解析器(不推荐)

转发

  1. 在Spring配置文件中, 把视图解析器注释掉

  2. 修改return 的地址

    1
    2
    3
    4
    5
    6
    @RequestMapping("/m1/t1")
    public String test(HttpServletRequest request, HttpServletResponse response){
    HttpSession session = request.getSession();
    System.out.println(session.getId());
    return "/WEB-INF/jsp/test.jsp";
    }

    其实视图解析器就是处理了一下这个地址字符串

  3. 这就实现了转发功能

方式二:

1
2
3
4
5
6
@RequestMapping("/m1/t1")
public String test(HttpServletRequest request, HttpServletResponse response){
HttpSession session = request.getSession();
System.out.println(session.getId());
return "forward:/WEB-INF/jsp/test.jsp";
}

重定向

把关键字forward 变成 redirect 即可

使用视图解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Controller
public class ModelTest01 {

@RequestMapping("/m1/t1")
public String test(){
// 转发
return "test";
}

@RequestMapping("/m1/t2")
public String test2(){
// 重定向
return "redirect:/index.jsp";
}

}

视图解析器检测到redirect 关键字后, 就不会加前缀和后缀

数据处理

从前端获取数据

提交的域名称和处理方法的参数名一致

提交数据: http://localhost:8080/user/t1?name=lxb

处理方法:

1
2
3
4
5
6
7
8
9
10
11
12
//localhost:8080/user/t1?name=xxx
@GetMapping("/t1")
public String test1(String name, Model model) {
//1.接收前端参数
System.out.println("接收到前端的参数为: " + name);

//2.将返回的结果传递给前端
model.addAttribute("msg", name);

//3.跳转视图
return "test";
}

一致情况下, 直接拿来用. 后台输出为:

接收到前端的参数为: lxb

提交的域名称和处理方法的参数名不一致

提交数据: http://localhost:8080/user/t1?username=lxb

处理方法:

1
2
3
4
5
6
7
8
9
10
11
12
//localhost:8080/user/t1?username=xxx
@GetMapping("/t1")
public String test1(@RequestParam("username") String name, Model model) {
//1.接收前端参数
System.out.println("接收到前端的参数为: " + name);

//2.将返回的结果传递给前端
model.addAttribute("msg", name);

//3.跳转视图
return "test";
}

无论是否一致, 都推荐使用这种方式, 提醒自己这是从前端接收的数据, 而且如果域名城不是username的话浏览器会报错

前端接收的是一个对象

  1. 接收前端用户传递的参数, 判断参数的名字, 假设名字在方法上, 可以直接使用
  2. 假设传递的是一个对象, 匹配User对象字段名, 如果一致, 则OK, 有一个不一致就不能成功匹配

提交数据: http://localhost:8080/user/t2?id=1&name=lxb&age=23

处理方法:

1
2
3
4
5
6
//前端接收的是一个对象: id, name, age
@GetMapping("/t2")
public String test2(User user){
System.out.println(user);
return "test";
}

输出:

User(id=1, name=lxb, age=23)

数据显示到前端

通过ModelAndView

通过ModelMap

ModelMap又继承了LinkedHashMap

通过Model

相当于ModelMap精简版

大多数情况下都使用Model

乱码问题

从前端获取中文字符

  1. 编写一个jsp页面传递一个中文字符

    1
    2
    3
    4
    <form action="/e/t1" method="post">
    <input type="text" name="name">
    <input type="submit">
    </form>
  2. 处理程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Controller
    public class EncodingController {
    @PostMapping("/e/t1")
    public String test1(String name, Model model){
    System.out.println(name);
    model.addAttribute("msg", name);
    return "test";
    }
    }
  3. 输出:

    查看后台输出发现:

    说明从前台获取后就是乱码

过滤器解决乱码

SpringMVC提供了过滤器, 直接在web.xml中配置即可

1
2
3
4
5
6
7
8
9
10
11
12
<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>

SpringMVC拦截器

类似于Servlet开发中的过滤器Filter, 用于对处理器进行预处理和后处理, 可以自定义一些拦截器来实现特定的功能.

*过滤器与拦截器的区别: * 拦截器是AOP思想的具体应用

*过滤器: *

  • servlet规范中的一部分, 任何java web工程都可以使用
  • 在url-pattern中配置了/*之后, 可以对所有要访问的资源进行拦截

*拦截器: *

  • 拦截器是SpringMVC框架自己的, 只有使用了SpringMVC框架的工程才能使用
  • 拦截器只会拦截访问的控制器方法, 如果访问的是jsp/html/css/image/js是不会进行拦截的

自定义拦截器

必须实现HandlerInterceptor接口

  1. 实现接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class MyInterceptor implements HandlerInterceptor {


    // return true: 执行下一个拦截器, 放行
    // return false: 不执行下一个拦截器
    @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("=====清理=====");
    }
    }
  2. 配置拦截器

    1
    2
    3
    4
    5
    6
    <mvc:interceptors>
    <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <bean class="com.lxb.config.MyInterceptor"/>
    </mvc:interceptor>
    </mvc:interceptors>
  3. 执行结果:

    1
    2
    3
    4
    =====处理前=====
    TestController ==> test()执行了
    =====处理后=====
    =====处理后=====

一般后两个方法是用来写日志的, 没有返回值, 真正起拦截作用的是第一个函数

-------------本文结束感谢您的阅读-------------
可以请我喝杯奶茶吗