Spring MVC 详解

概述

image-20220602154656508
  • SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于 Spring FrameWork 的后续产品,已经融合在 Spring Web Flow 里面。
  • Spring 框架提供了构建 Web 应用程序的全功 能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用 Spring 进行 WEB 开发时,可以选择使用 Spring 的 Spring MVC 框架或集成其他 MVC 开发框架,如 Struts1(现在一般不用),Struts2 等。
  • SpringMVC 已经成为目前最主流的 MVC 框架之一,并且随着 Spring3.0 的发布,全面超越 Struts2,成 为最优秀的 MVC 框架。 它通过一套注解,让一个简单的 Java 类成为处理请求的控制器,而无须实现任何接口。同时它还支持 RESTful 编程风格的请求。
image-20220602154718408

一、SpringMVC 入门案例

入门案例的需求:实现 jsp 界面点击超链接,发送请求,成功转发到指定的 jsp 界面

1. 环境搭建

Ⅰ 创建基于 Maven 的 web 工程

image-20220602154741677

Ⅱ 创建文件夹 java、resource

创建两个文件夹 :java 存放源码、resources 存放资源

Ⅲ 导入依赖

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <spring.version>5.0.2.RELEASE</spring.version> <!--版本锁定-->
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

Ⅳ 配置文件准备

在 web.xml 中配置核心控制器 DispatcherServlet

  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

创建spingmvc.xml 配置文件

image-20220602154809595

2. 编写代码

Ⅰ springmvc.xml 配置 IoC 和视图解析器

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       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
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启注解扫描-->
    <context:component-scan base-package="com.smallbeef"></context:component-scan>

    <!--视图解析器
    prefix 前缀: 去 /WEB-INF/pages/ 路径下找资源,与Controller的return语句中的路径构成完整路径名
    suffix 后缀: 匹配.jsp为后缀的文件-->
    <bean id = "internalResourceViewResolver" class = "org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

    <!--开启SpringMVC对注解的支持-->
    <mvc:annotation-driven></mvc:annotation-driven>
</beans>

Ⅱ 将 springmvc.xml 加载进 web.xml

	<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

Ⅲ 前端界

  • index.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Titl</title>
    </head>
    <body>
        <h3>入门SpringMVC案例</h3>
    
        <a href="/hello">hello</a>
    </body>
    </html>
    
  • 跳转界面 success.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
        <h1>Success!</h1>
    </body>
    </html>
    

Ⅳ 控制器类

//控制器类
@Controller
public class HelloController {

    /*请求映射*/
    @RequestMapping(path = "/hello")
    public String sayHello(){
        System.out.println("Hello SpringMVC");
        return "success"; //返回名为success的jsp文件
    }
}

整个项目结构如下:

image-20220602154839004

二、案例的执行过程及相关组件

1. 执行过程

image-20220602154855680

2. 相关组件

① DispatcherServlet:前端控制器

用户请求到达前端控制器,它就相当于 MVC模式中的 C, DispatcherServlet 是整个流程控制的中心 ,由 它调用其它组件处理用户的请求,DispatcherServlet 的存在降低了组件之间的耦合性。

DispatcherServlet 在 web.xml 中进行配置

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
  • load-on-startup:表示启动容器时初始化该 Servlet
  • contextConfigLocation:表示 SpringMVC 配置文件的路径
  • url-pattern:表示将哪些请求交给 Spring Web MVC 处理。'/' 是用来定义默认 servlet 映射的。也可以如 '*.html' 表示拦截所有以 html 为扩展名的请求

② HandlerMapping:处理器映射器

HandlerMapping 负责根据用户请求找到 Handler 即处理器(Controller) ,SpringMVC 提供了不同的映射器实现不同的 映射方式,例如:配置文件方式,实现接口方式,注解方式等。

③ Handler:处理器(Controller)

它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler。 由 Handler 对具体的用户请求进行处理。

④ HandlAdapter:处理器适配器

通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理 器进行执行。

⑤ View Resolver:视图解析器

View Resolver 负责将处理结果生成 View 视图 ,View Resolver 首先根据逻辑视图名解析成物理视图名 即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染 将处理结果通过页面展示给用户 。

< mvc:annotation-driven >说明

在 SpringMVC 的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。

但是在案例中,我们并没有配置 HandlerMapping 和 HandlAdapter

因为使用 < mvc:annotation-driven > 就会自动加载 RequestMappingHandlerMapping (处理映射器)和 RequestMappingHandlerAdapter ( 处 理 适 配 器 ) , 可在 SpringMVC.xml 配 置 文 件 中 使 用 < mvc:annotation-driven > 替代处理器和适配器的配置。

三、@RequestMapping 详解

1. 作用

@RequestMapping: 请求映射

用于建立请求 URL 和处理请求方法之间的对应关系。

若 @RequestMapping 出现在类上,则类中的所有方法的 url 都必须加上该前缀

比如 以下代码中 findAccount() 的 前端请求 url 就是 /account/findAccount

@Controller("accountController") 
@RequestMapping("/account") 
public class AccountController { 
	@RequestMapping("/findAccount")  
	public String findAccount() {   
		System.out.println("查询了账户。。。。");   
		return "success";  
	} 
} 

2. 属性

  • path :指定请求路径的url

  • value :value属性和path属性是一样的

  • mthod :指定该方法的请求方式 POST/GET/PUT......

    @RequestMapping(value="/saveAccount",method=RequestMethod.POST) 
    

    a标签的请求方式是固定不变的,是GET类型

  • params :指定限制请求参数的条件

    @RequestMapping(value="/removeAccount",params= {"accountName","money>100"}) 
    
    ——————————————————————————————————————————
    
    <a href="account/removeAccount?accountName=aaa&money>100">删除账户,金额 100</a> <br/> 
    
    <a href="account/removeAccount?accountName=aaa&money>150">删除账户,金额 150</a> 
    

    注意:

    当我们点击第一个超链接时,可以访问成功。

    当我们点击第二个超链接时,无法访问。

  • headers 发送的请求中必须包含的请求头

四、ModelAndView 详解

1. 概述

对于 MVC 框架,控制器Controller执行业务逻辑,用于产生模型数据 Model ,而视图 View 用于渲染模型数据。
使用 Model 和 ModelAndView 这两个类在 Spring 的视图解析时作用以及区别:

  • Model只是用来传输数据的,并不会进行业务的寻址。ModelAndView 却是可以进行业务寻址的,就是设置对应的要请求的静态文件,这里的静态文件指的是类似jsp的文件
  • Model是每一次请求可以自动创建,但是ModelAndView 是需要我们自己去new的

2. Model

Model 是每次请求中都存在的默认参数,利用其 addAttribute() 方法即可将服务器的值传递到 jsp 页面中,可在 jsp 界面通过 EL 表达式获取传值

示例代码

@RequestMapping("listCategory2")
public String listCategory2(Model model) {
    // 接收查询的信息
    List<Category> cs2= categoryService.list();
    // 封装了查询的数据
    model.addAttribute("test", cs2);
    //重要!!需要给出返回model跳转的路径
    return "success";
}
<!-- 获取值的时候,对应的是addAttribute的第一个参数!取了个别名为c-->
    <c:forEach items="${test }" var="c" >
        <tr>
            <td>${c.id}</td>
            <td>${c.name}</td>
        </tr>
    </c:forEach>

3. ModelMap

ModelMap:ModelMap 对象主要用于传递控制方法处理数据到结果页面,也就是说我们把结果页面上需要的数据放到 ModelMap 对象中即可,
他的作用类似于 request 对象的 setAttribute 方法的作用: 用来在一个请求过程中传递处理的数据。
ModelMap 或者 Model 通过 addAttribute 方法向页面传递参数.

public ModelMap addAttribute(String attributeName, Object attributeValue){...}
public ModelMap addAttribute(Object attributeValue){...}
public ModelMap addAllAttributes(Collection<?> attributeValues) {...}
public ModelMap addAllAttributes(Map<String, ?> attributes){...}

4. ModelAndView

使用ModelAndView类用来存储处理完后的结果数据,以及显示该数据的视图。ModelAndView 中的Model代表模型,View代表视图,这个名字就很好地解释了该类的作用

添加模型数据 :

  • ModelAndView addObject(String attributeName, Object attributeValue)

  • ModelAndView addAllObject(Map<String, ?> modelMap)

设置视图

  • void setView(View view)

  • void setViewName(String viewName)

示例代码

	@RequestMapping("listCategory")
    public ModelAndView listCategory(){
        //创建一个模型视图对象
        ModelAndView mav = new ModelAndView();
        //获取到查询的数据
        List<Category> cs= categoryService.list();

        //将数据放置到ModelAndView对象view中
        mav.addObject("cs_model", cs);
        // 放入jsp路径
        mav.setViewName("listCategory");
         //返回ModelAndView对象mav
        return mav;
    }

同样在 jsp 界面通过 EL 表达式获取传值

五、Controller 的返回值

1. 返回 ModelAndView

  /**     
  * 返回ModelAndView对象     
  * 可以传入视图的名称(即跳转的页面),还可以传入对象。     
  * @return     
  * @throws Exception     
  */    
  @RequestMapping(value="/findAll")    
  public ModelAndView findAll() throws Exception {        
  	 ModelAndView mv = new ModelAndView();        
             
  	 // 模拟从数据库中查询所有的用户信息        
  	 List<User> users = new ArrayList<>();        
  	 User user1 = new User();        
  	 user1.setUsername("张三");        
  	 user1.setPassword("123");                
  	 User user2 = new User();        
  	 user2.setUsername("赵四");        
  	 user2.setPassword("456"); 
  	 users.add(user1);        
  	 users.add(user2);       
  	 
  	 // 添加对象        
  	 mv.addObject("users", users);    
  	 // 跳转到list.jsp的页面        
  	 mv.setViewName("list");               
  	 return mv;    
 } 

jsp 界面通过 EL 表达式取值

 <h3>查询所有的数据</h3>    
 <c:forEach items="${ users }" var="user">        
 	${ user.username }    
 </c:forEach>

2. 返回 void

由于默认的 Maven 项目中 没有 servlet,所以需要额外添加一个依赖

<dependency>
	<groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
</dependency>
  • 通过 HttpServletRequest 做服务端跳转

    @RequstMapping
    public void hello(HttpServletRequest req,HttpServletResponse resp){
        req.getRequestDispatcher("/hello.jsp").forward(req,resp);
    }
    
  • 通过 HttpServletResponse 做重定向

    重定向无法访问 WEB-INF 路径下的资源

    @RequstMapping
    public void hello(HttpServletRequest req,HttpServletResponse resp){
        resp.sendRedirct(request.getContextPath()+"/hello.jsp");
    }
    
  • 通过 HttpServletResponse 给出相应

    这种方式,既可以返回 JSON,也可以返回普通字符串

    @RequstMapping
    public void hello(HttpServletRequest req,HttpServletResponse resp){
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = resp.getWriter();
        out.write("hello");
        out.flush();
        out.close();
    }
    

3. 返回字符串

  • 返回逻辑视图名

    @RequestMapping
    public String hello(Model model){
        model.addAttribute("username","xiaowu");
        return "success"; //去查找一个名为 success 的视图
    }
    
  • 服务端跳转:请求转发

    forward 后面跟上跳转的路径

    @RequestMapping
    public String hello(){
        return "forward:/success.jsp";
    }
    
  • 客户端跳转:重定向

    @RequestMapping
    public String hello(){
        return "redirect:/success";
    }
    
  • 返回字符串

    上面三个返回的字符串都是有特殊含义的,如果一定要返回一个字符串,需要额外添加一个注解 @ResponseBody,表示当前方法的返回值就是要展示出来的返回值,没有特殊含义

    @RequestMapping
    @ResponseBody
    public String hello(){
        return "I love China";
    }
    

    如果返回中文字符串,是会乱码的,需要在 @RequestMapping 中添加 produces 属性解决

    @RequestMapping(produces = "text/html;charset = utf-8")
    

六、请求参数的绑定

1. 绑定基本数据类型

@Controller
public class HelloController {

    /*请求映射*/
    @RequestMapping(path = "/hello")
    public String sayHello(String username, Double password, Boolean ispublic){
        System.out.println(username);
        System.out.println(password);
        System.out.println(ispublic);
        return "success"; //返回名为success的jsp文件
    }
}

a 标签请求中带上和控制器参数名一样的参数,并赋值

<a href="/hello?username=123&password=1234&ispublic=true">hello</a>

2. 绑定实体类型

实体类:

public class Account implements Serializable {
    private String username;
    private String password;
    private Double money;

    public String getUsername() {
        return username;
    }

    public void setUsername(String usernmae) {
        this.username = usernmae;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "usernmae='" + username + '\'' +
                ", password='" + password + '\'' +
                ", money=" + money +
                '}';
    }
}

form 表单中 name 的名称要和 JavaBean 对象的属性名对应

 <form action = "/hello">
        姓名:<input type="text" name="username" /><br/>
        密码:<input type="text" name="password" /><br/>
        金额:<input type="text" name="money" /><br/>
        <input type="submit" value="提交" />
    </form>

控制器类传入该实体类对象

//控制器类
@Controller
public class HelloController {

    /*请求映射*/
    @RequestMapping(path = "/hello")
    public String sayHello(Account account){
        System.out.println(account); //会调用account的toString方法
        return "success"; //返回名为success的jsp文件
    }
}
image-20220602154940874

输出结果:

Account

🥇 如果该类中嵌入了一个其他类

public class Account implements Serializable{
    private String username;
    private String password;
    private Double money;
    private User user;
    
-----------------------------------------
    
public class User implements Serializable {
    private String uname;
    private Integer age;

通过 user.uname 访问该嵌入类的属性。控制器类代码同上

用户姓名:<input type="text" name="user.uname" /> <br/>
用户年龄:<input type="text" name="user.age" /> <br/>
image-20220602155119506

🚨 此处输入中文可能会乱码,参考下一节 七、配置解决中文乱码的过滤器

输出结果:

Account{username='123', password='123', money=123.0, user=User{uname='123', age=123}}

3. 绑定集合类型

public class Account implements Serializable{
    private String username;
    private String password;
    private Double money;

    private List<User> list;
    private Map<String,User> map;

-----------------------------------------
    
public class User implements Serializable {
    private String uname;
    private Integer age;

将用户属性封装进 list 对象和 map 对象。控制器类代码同上

此处 map 中的 key 命名为 first ,自己随意命名

用户姓名:<input type="text" name="list[0].uname" /><br/>
用户年龄:<input type="text" name="list[0].age" /><br/>

用户姓名:<input type="text" name="map['first'].uname" /><br/>
用户年龄:<input type="text" name="map['first'].age" /><br/>
image-20220602155143526

输出结果:

Account{username='231', password='12', money=123.0, list=[User{uname='嘿嘿', age=123}], map={first=User{uname='呵呵', age=321}}}

4. 绑定自定义类型

表单提交的任何数据类型全部都是字符串类型,但是后台定义 Integer 类型,数据也可以封装上,因为 Spring 框架内部会默认进行数据类型转换

比如:日期 Date 的格式如果为 2000/11/11 则可正确封装,但是如果不是这个格式(比如 2000-11-11),就会出错。

如果想自定义数据类型转换,需要实现 Converter 的接口

  • 定义一个类,实现 Converter 接口,该接口有两个泛型

    // 将一个 Date 类型转换为 String 类型
    @Component
    public class StringToDateConverter implements Converter<String, Date> {
        /**
         * @param source 传进来的字符串
         * @return
         */
        @Override
        public Date convert(String source) {
            DateFormat format = null;
            try {
                if(StringUtils.isEmpty(source)) {
                    throw new NullPointerException("请输入要转换的日期");
                }
                format = new SimpleDateFormat("yyyy-MM-dd");
                Date date = format.parse(source); //字符串转日期类型
                return date;
            } catch (Exception e) {
                throw new RuntimeException("输入日期有误");
            }
    
        }
    }
    
  • 在 Spring 配置文件中配置类型转换器

    <!--配置自定义类型转换器-->
    <bean id = "conversionServiceFactoryBean" class = "org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
             <set>
                  <bean class="com.smallbeef.utils.StringToDateConverter"></bean>
             </set>
        </property>
    </bean>
    <!--开启SpringMVC对注解的支持-->
    <mvc:annotation-driven conversion-service="conversionServiceFactoryBean"></mvc:annotation-driven>
    

    配置完成后,在服务器端就可以接收到前端传过来的 2000-11-11 这种格式的日期参数了

5. 使用 Servlet 原生的 API 对象作为方法参数

SpringMVC 还支持使用原始 ServletAPI 对象作为控制器方法的参数。
支持原始 ServletAPI 对象有:

  • HttpServletRequest
  • HttpServletResponse
  • HttpSession
  • java.security.Principal
  • Locale
  • InputStream
  • OutputStream
  • Reader
  • Writer

我们可以把上述对象,直接写在控制器方法的参数中使用。

@RequestMapping("/testServletAPI") 
public String testServletAPI(HttpServletRequest request, 
							HttpServletResponse response,
							HttpSession session) {  
       System.out.println(request);  
       System.out.println(response);  
       System.out.println(session);  
       return "success"; 
} 
image-20220602155202974

七、配置解决中文乱码的过滤器

在 web.xml中 配置过滤器

 <!--配置解决中文乱码的过滤器-->
  <filter>
    <filter-name>characterEncodingFilter</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>characterEncodingFilter</filter-name>
    <!--拦截什么网址,此处设置全部拦截-->
    <url-pattern>/*</url-pattern>
  </filter-mapping>

八、常用注解

1. RequestParam

作用: 把请求中指定名称的参数给控制器中的形参赋值。

属性:

  • value:请求参数中的名称。
  • required:请求参数中是否必须提供此参数。默认值:true。表示必须提供,如果不提供将报错。

示例代码如下:

@RequestMapping("/useRequestParam") 
public String useRequestParam(@RequestParam("name")String username,      
             				@RequestParam(value="age",required=false)Integer age){  	
		System.out.println(username+","+age);  
		return "success"; 
}

则前端参数名称必须为name、age(由于设置了 required=false ,所以该参数可不提供)

<a href="/useRequestParam?name=test">requestParam 注解</a> 

2. RequestBody

作用:用于获取请求体内容。直接使用得到是 key=value&key=value...结构的数据。

(get 请求方式不适用。因为get请求会直接把参数放在地址 url 上 )

属性:

  • required:是否必须有请求体。默认值是:true

    当取值为 true 时,get 请求方式会报错。如果取值 为 false,get 请求得到是 null

示例代码如下:

@RequestMapping("/useRequestBody") 
public String useRequestBody(@RequestBody(required=false) String body){  	
	System.out.println(body);  
	return "success"; 
}
post 请求 jsp代码: 
<form action="springmvc/useRequestBody" method="post"> 
 用户名称:<input type="text" name="username" ><br/>  
 用户密码:<input type="password" name="password" ><br/>  
 用户年龄:<input type="text" name="age" ><br/> 
 <input type="submit" value=" 保存 "> </form> 
------------------------------------------------------------------------------------
get 请求 jsp代码: 
<a href="springmvc/useRequestBody?body=test">requestBody 注解 get 请求</a> 
image-20220602155223952

3. PathVaribale

作用:用于绑定 url 中的占位符。例如:请求 url 中 /delete/{id},这个 {id} 就是 url 占位符。

url 支持占位符是 Spring3.0 之后加入的。是 SpingMVC 支持 rest 风格 URL(见下一节 九、RESTful)的一个重要标志。

属性:

  • value:用于指定 url 中占位符名称。
  • required:是否必须提供占位符

示例代码如下:

@RequestMapping("/usePathVariable/{sid}") 
public String usePathVariable(@PathVariable("sid") Integer id){  
	System.out.println(id);  
	return "success"; 
} 

路径上的名称 @RequestMapping("/usePathVariable/{sid}") 和注解中的名称 @PathVariable("sid") 要一致,和参数名称 Integer id 无关。

前端传参

<a href="/usePathVariable/100">pathVariable 注解</a> 

4. RequestHeader

作用:用于获取请求消息头。

属性:

  • value:提供消息头名称
  • required:是否必须有此消息头

示例代码如下:

@RequestMapping("/useRequestHeader") 
public String useRequestHeader(@RequestHeader(value="Accept-Language",          	
								required=false)String requestHeader){  
	System.out.println(requestHeader);  
	return "success"; 
}

5. CookieValue

作用:用于把指定 cookie 名称的值传入控制器方法参数。

属性:

  • value:指定 cookie 的名称。
  • required:是否必须有此 cookie。
@RequestMapping("/useCookieValue") 
public String useCookieValue(@CookieValue(value="JSESSIONID",required=false) String 	
cookieValue){  
	System.out.println(cookieValue);  
	return "success"; 
}

6. ModelAttribute

① 概述

作用:

该注解是 SpringMVC4.3 版本以后新加入的。它可以用于修饰方法和参数。

@ModelAttribute 注解作用在方法上或者方法的参数上,表示将被注解的方法的返回值或者是被注解的参数作为Model的属性加入到Model中,然后Spring框架自会将这个Model传递给ViewResolver。Model的生命周期只有一个http请求的处理过程,请求处理完后,Model就销毁了。

  • 出现在方法上,表示当前方法会在控制器的方法执行之前,先执行。 它可以修饰没有返回值的方法,也可以修饰有具体返回值的方法。
  • 出现在参数上,获取指定的数据给参数赋值

属性:

  • value:用于获取数据的 key。key 可以是 POJO(简单Java对象,实际就是普通JavaBean) 的属性名称,也可以是 map 结构的 key。

应用场景:

当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据。

例如: 我们在编辑一个用户时,用户有一个创建信息字段,该字段的值是不允许被修改的。在提交表单数据是肯定没有此字段的内容,一旦更新会把该字段内容置为 null,此时就可以使用此注解解决问题。

示例代码

<a href="springmvc/testModelAttribute?username=test">测试 modelattribute</a>
/** 
  * 被 ModelAttribute 修饰的方法,该方法会在控制器方法执行之前执行
  * @param user   
  * */  
  @ModelAttribute  
  public void showModel(User user) {   //直接获取前端传过来的值
  	System.out.println("执行了 showModel 方法"+user.getUsername());  
  } 
   /** 
  * 接收请求的方法   
  * @param user   
  * @return   
  */  
  @RequestMapping("/testModelAttribute")  
  public String testModelAttribute(User user) {   
  	System.out.println("执行了控制器的方法"+user.getUsername());   
  	return "success";  
  } 
image-20220602155249159

② 修饰方法带返回值

需求:修改用户信息,要求用户的密码不能修改

示例代码如下:

<!-- 修改用户信息 
     没有用户密码项--> 
<form action="/updateUser" method="post"> 
 用户名称:<input type="text" name="username" ><br/>  
 用户年龄:<input type="text" name="age" ><br/>  
 <input type="submit" value=" 保存 "> </form> 
/** 
 * 模拟去数据库查询  
 * @param username  用户输入的用户名
 * @return  
 */ 
 private User findUserByName(String username) {  
 	User user = new User();  
 	user.setUsername(username); 
 	user.setAge(19);  //假设数据库中该用户年龄为19
 	user.setPassword("123456");  //假设数据库中该用户密码为123456
 	return user; 
}

@ModelAttribute 
public User showModel(String username) {  //直接获取前端传递过来的username
	//模拟去数据库查询  
	User abc = findUserByName(username); 
 	System.out.println("执行了 showModel 方法" + abc);  
 	return abc; 
}

/** 
 * 模拟修改用户方法  
 * @param user  该user为ModelAttribute返回的user对象abc
 * @return  
 */ 
@RequestMapping("/updateUser") 
public String testModelAttribute(User user) { //接收ModeleAttribute返回的user对象并根据前端传值进行修改
	System.out.println("控制器中处理请求的方法:修改用户:"+user);  
	return "success"; 
} 
image-20220602155307378 修改了年龄,password不变

执行了showModel方法User{username='das', age=19, password='123456'}
控制器修改用户User

③ 修饰方法不带返回值 + 修饰参数

/** 
 * 查询数据库中用户信息  
 * 无返回值,将user对象存入map中
 * @param user  
 */ 
 @ModelAttribute 
 public void showModel(String username,Map<String,User> map) { 
 	//模拟去数据库查询  
 	User user = findUserByName(username); 
 	System.out.println("执行了 showModel 方法"+user);  
 	map.put("abc",user); 
 } 
 
 /** 
 * 模拟修改用户方法  
 * @param user 直接用ModelAttrubute封装在map中的user对象
 * @return  
 */ 
 @RequestMapping("/updateUser") 
 public String testModelAttribute(@ModelAttribute("abc")User user) {  
 	System.out.println("控制器中处理请求的方法:修改用户:"+user);  
 	return "success"; 
 }

7. SessionAttribute

这个注解只能标注在类上,用于在多个请求之间传递参数,类似于SessionAttribute

但不完全一样:一般来说@SessionAttribute设置的参数只用于暂时的传递,而不是长期的保存,长期保存的数据还是要放到Session中。(比如重定向之间暂时传值,用这个注解就很方便)

官方解释:当用@SessionAttribute标注的Controller向其模型Model添加属性时,将根据该注解指定的名称/类型检查这些属性,若匹配上了就顺带也会放进Session里。匹配上的将一直放在Sesson中,直到你调用了SessionStatus.setComplete()方法就消失了

属性

  • value:用于指定存入的属性名称

  • type:用于指定存入的数据类型。 和value中顺序要对应上

    所以可以这样写:
    @SessionAttributes(types = {User.class,Integer.class},value={“attr1”,”attr2”})

示例代码

<a href="/testPut">存入 SessionAttribute</a> <hr/> 
<a href="/testGet">取出 SessionAttribute</a> <hr/> 
<a href="/testClean">清除 SessionAttribute</a> 
@Controller
@SessionAttributes(value ={"username","password"},types={Integer.class})  
public class SessionAttributeController {    
 
  @RequestMapping("/testPut")    
  public String testPut(Model model){    
  	//下面的代码将Model中的参数保存到了session中       
  	model.addAttribute("username", "泰斯特");           		
  	model.addAttribute("password","123456");           
  	model.addAttribute("age", 31);   
    //跳转之前将 username 和 password 保存到 session 中
    //因为注解 @SessionAttribute 中有这几个参数           
    return "success";       
  }               

	@RequestMapping("/testGet")       
	public String testGet(ModelMap model){           
	   System.out.println(model.get("username")+";"
	         +model.get("password")+";"+model.get("age"));           
	   return "success";       
	 }   
             
    @RequestMapping("/testClean")        
    public String complete(SessionStatus sessionStatus){       
    	sessionStatus.setComplete();            
    	return "success";        
    }   
}	 

九、RESTful

REST 风格 URL

什么是 rest:

  • REST(英文:Representational State Transfer,简称 REST) 描述了一个架构样式的网络系统, 比如 web 应用程序。它首次出现在 2000 年 Roy Fielding 的博士论文中,他是 HTTP 规范的主要编写者之 一。在目前主流的三种 Web 服务交互方案中,REST 相比于 SOAP(Simple Object Access protocol,简单 对象访问协议)以及 XML-RPC 更加简单明了,无论是对 URL 的处理还是对 Payload 的编码,REST 都倾向于用更 加简单轻量的方法设计和实现。值得注意的是 REST 并没有一个明确的标准,而更像是一种设计的风格。
  • 它本身并没有什么实用性,其核心价值在于如何设计出符合 REST 风格的网络接口。

restful 的优点:

它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。

restful 的特性:

  • 资源(Resources):网络上的一个实体,或者说是网络上的一个具体信息。 它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的存在。可以用一个 URI(统一 资源定位符)指它,每种资源对应一个特定的 URI 。要 获取这个资源,访问它的 URI 就可以,因此 URI 即为每一个资源的独一无二的识别符。

  • 表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层(Representation)。 比如,文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至可以采用二进制格式。

  • 状态转化(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。
    HTTP 协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生“状态转化”(State Transfer)。而这种转化是建立在表现层之上的,所以就是 “表现层状态转化”。

    具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。

一般的 url 路径:每个方法对应不同的 url 路径

@RequeMapping("/user/delete")
public void delete(){
}

@RequeMapping("/user/findAll")
public void findAll(){
}

@RequeMapping("/user/add")
public void add(){
}

RESTful 风格的路径

@RequeMapping(value = "/user", method = RequestMethod.DELETE)
public void delete(){  // DELETE
}

@RequeMapping(value = "/user",method = RequestMethod.GET)
public void findAll(){ // GET
}

@RequeMapping(value = "/user/{id}",method = RequestMethod.GET)
public void findById(@PathVariable("id") String id){ // GET
}

@RequeMapping(value = "/user",method = RequestMethod.POST)
public void add(){	// POst
}

所有方法的路径都是一样的,通过发送的请求方式 get/post/delete/put 来确定使用哪个方法,如果两个方法的请求方式是相同的,则根据所带参数来判断调用哪个方法,比如上述代码中的 findAll 和 findById 方法

十、@ResponseBody 响应 json 数据


需求: 使用@ResponseBody 注解实现将 controller 方法返回对象转换为 json 响应给客户端

1. 资源准备

首先需要导入 jquery.min.js 框架文件以及 jackson 的 依赖

image-20220602155337065

导入jackson依赖(Springmvc 默认用 MappingJacksonHttpMessageConverter 对 json 数据进行转换,需要加入 jackson 的包。 )

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.10.2</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.10.2</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.10.2</version>
    </dependency>

DispatcherServlet 会拦截到所有的资源,导致一个问题就是静态资源(img、css、js)也会被拦截到,从而不能被使用。解决该问题的就是需要配置静态资源不进行拦截,在 springmvc.xml 配置文件添加如下配置 :

    <!-- 设置静态资源不过滤 -->
    <mvc:resources location="/css/" mapping="/css/**"/>  <!-- 样式 -->
    <mvc:resources location="/images/" mapping="/images/**"/>  <!-- 图片 -->
    <mvc:resources location="/js/" mapping="/js/**"/>  <!-- javascript -->
  • location 元素表示 webapp 目录下的包下的所有文件
  • mapping 元素表示以 /static 开头的所有请求路径,如 /static/a 或者/static/a/b

前端 jsp

<head>
    <title>Title</title>

    <srcipt src = "js/jquery.min.js"></srcipt>
    <script type = "text/javascript">

        $(function(){
            $("#testJson").click(function(){
                $.ajax({
                    type: "post",
                    url: "/testResponseJson",
                    contentType: "application/json; charset= utf-8",
                    data: '{"uname":"小黑", "age":20, "password":"123"}',
                    dataType: "json",
                    success:function(data){
                        alert(data);
                        // alert(data.age);
                    }
                });
            });
        });
    </script>
</head>

<body>
    <button id = "testJson">测试 ajax 请求 json 和响应 json</button>
</body>

2. 获取请求体数据

使用@RequestBody获取请求体数据

@Controller
public class HelloController {
    @RequestMapping("/testResponseJson")
    public void testResponseJson(@RequestBody String body){
        System.out.println("test方法执行了"+body);
    }
}

3. json 字符串 —> JavaBean 的对象

使用 @RequestBody 把 json 的字符串转换成 JavaBean 的对象

//控制器类
@Controller
public class HelloController {
    @RequestMapping("/testResponseJson")
    public void testResponseJson(@RequestBody User user){
        System.out.println("test方法执行了"+ user);
    }
}

4. JavaBean 对象 —> json 字符串

使用 @ResponseBody 把 JavaBean 对象转换成 json 字符串

//控制器类
@Controller
public class HelloController {
    @RequestMapping("/testResponseJson")
    public @ResponseBody User testResponseJson(@RequestBody User user){
        System.out.println("test方法执行了"+ user);
        user.setAge(21);
        return user;
    }
}

十一、文件上传

1. 传统方式的文件上传

传统方式的文件上传,指的是我们上传的文件和访问的应用存在于同一台服务器上。 并且上传完成之后,浏览器可能跳转。

① 导入依赖

  <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.1</version>
    </dependency>

    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>            
      <version>2.4</version>        
    </dependency>

② Jsp界面

<form action="/fileupload" method="post" enctype="multipart/form-data">
        <input type="file" name = "upload"/>
        <br/>
        <input type="submit" value="上传文件">
</form>

③ Controller控制器

//控制器类
@Controller
public class FileController {

    @RequestMapping("/fileupload")
    public String fileupload(HttpServletRequest request) throws Exception {
        // 先获取到要上传的文件目录
        String realPath = request.getSession().getServletContext().getRealPath("/uploads");
        // 创建File对象,一会向该路径下上传文件
        File file = new File(realPath);
        // 判断路径是否存在,如果不存在,创建该路径
        if(!file.exists()){
            file.mkdirs();
        }
        // 创建磁盘文件项工厂
        DiskFileItemFactory factory = new DiskFileItemFactory();
        ServletFileUpload servletFileUpload = new ServletFileUpload(factory);
        // 解析request对象
        List<FileItem> list = servletFileUpload.parseRequest(request);
        // 遍历
        for(FileItem fileItem : list){
            // 判断文件项是普通字段,还是上传的文件
            if(fileItem.isFormField()){

            }
            // 上传文件项
            else{
                // 获取到上传文件的名称
                String filname = fileItem.getName();
                // 上传文件
                fileItem.write(new File(file,filname));
                // 删除临时文件
                fileItem.delete();
            }
        }
        return "success";
    }
}

④ 运行结果

文件会上传到 target 目录下的 uploads 文件夹

image-20220602155402663

2. SpringMVC传统方式的文件上传

同样需要导入上一步的pom依赖文件

原理如下:

image-20220602155419200

① 配置文件解析器 CommonsMultipartResolve

springmvc.xml

 <!-- 配置文件上传解析器 -->
    <bean id="multipartResolver"
        class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
            <!-- 设置上传文件的最大尺寸为 5MB -->
            <property name="maxUploadSize">   
                <value>5242880</value>
            </property>
    </bean>

注意: 文件上传的解析器 id 是固定的,不能起别的名称,否则无法实现请求参数的绑定。(不光是文件,其他 字段也将无法绑定)

② Jsp界面

<form action="/fileUpload2" method="post" enctype="multipart/form-data">
        名称:<input type="text" name="picname"/><br/>
        图片:<input type="file" name="uploadFile"/><br/>
        <input type="submit" value=" 上传 "/> 
    </form>

③ Controller控制器

@RequestMapping("/fileUpload2")
    // 参数picname、uploadFile要和jsp界面的name一致
    public String FileUpload2(String picname, MultipartFile uploadFile, HttpServletRequest request) throws IOException {
        // 1. 定义文件名
        String fileName = "";
        // 获取原始文件名
        String uploadFileName = uploadFile.getOriginalFilename();
        // 截取文件扩展名
        String extendName = uploadFileName.substring(uploadFileName.lastIndexOf(".") + 1, uploadFileName.length());
        // 把文件加上随机数,防止文件重复
        String uuid = UUID.randomUUID().toString().replace("-","").toUpperCase();
        // 判断是否输入了文件名
        if(!StringUtils.isEmpty(picname)){
            fileName = uuid + "_" + picname + "." + extendName;
        }
        else{
            fileName = uuid + "_" + uploadFileName;
        }
        System.out.println(fileName);

        // 2. 获取到要上传的文件目录
        String realPath = request.getSession().getServletContext().getRealPath("/uploads");
        // 3. 创建File对象,一会向该路径下上传文件
        File file = new File(realPath);
        // 判断路径是否存在,如果不存在,创建该路径
        if(!file.exists()){
            file.mkdirs();
        }

        // 4. 使用 MulitpartFile 接口中方法,把上传的文件写到指定位置
        uploadFile.transferTo(new File(file, fileName));
        return "success";
    }

④ 运行结果

image-20220602155438190

3. SpringMVC 跨服务器方式的文件上传

image-20220602155513251

此处我们用两个 Tomcat 服务器模拟应用服务器和图片服务器

① 新建一个Tomcat服务器存储图片

新建一个 fileuploadserver web工程

image-20220602155530272

在webapp 下创建uploads文件夹用来存储图片

image-20220602155549056

同时需要在target目录下创建uploads文件夹(这一步很重要,不做的话会报409错误)

image-20220602155608930

Tomcat 服务器配置,注意修改端口号

image-20220602155625638 image-20220602155640093

还需要在web.xml中修改serlvet权限,servlet默认权限是只读 (这步不做会报405错误)

<web-app>

  <display-name>Archetype Created Web Application</display-name>

  <servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
    <init-param>
      <param-name>debug</param-name>
      <param-value>0</param-value>
    </init-param>
    <init-param>
      <param-name>readonly</param-name>
      <param-value>false</param-value>
    </init-param>
    <init-param>
      <param-name>listings</param-name>
      <param-value>false</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
</web-app>

org.apache.catalina.servlets.DefaultServlet 会爆红,不用理会,我们只需要能够修改权限就可以了

OK,至此,图片服务器 fileupload 的配置完毕,接下来开始应用服务器 springmvc 的配置和代码编写

② 导入依赖

<dependency>
      <groupId>com.sun.jersey</groupId>
      <artifactId>jersey-core</artifactId>
      <version>1.18.1</version>
    </dependency>
    
    <dependency>
      <groupId>com.sun.jersey</groupId>            
      <artifactId>jersey-client</artifactId>            
      <version>1.18.1</version>        
    </dependency>

③ Jsp界面

<form action="/fileUpload3" method="post" enctype="multipart/form-data">
        图片:<input type="file" name="uploadFile"/><br/>
        <input type="submit" value=" 上传 "/>
</form>

④ Controller控制器

 @RequestMapping(value = "/fileUpload3")
    // 参数picname、uploadFile要和jsp界面的name一致
    public String FileUpload3(MultipartFile uploadFile) throws IOException {
        System.out.println("SpringMVC跨服务器方式的文件上传......");
        //定义图片服务器的请求路径
        String path = "http://localhost:9090/uploads/";
        //获取到上传文件的名称
        String filename = uploadFile.getOriginalFilename();
        // 把文件加上随机数,防止文件重复
        String uuid = UUID.randomUUID().toString().replace("-", "").toUpperCase();
        // 文件名称唯一化
        filename = uuid + "_" + filename;

        //向图片服务器上传文件
        // 1. 创建客户端对象
        Client client = Client.create();
        // 2. 连接图片服务器
        WebResource resource = client.resource(path + filename);
        // 3. 上传文件
        resource.put(uploadFile.getBytes());

        return "success";
    }

⑤ 运行结果

最终图片保存在 图片服务器 的 target 文件夹下的 uploads 中

image-20220602155706005

引用

黑马程序员

posted @ 2022-06-07 20:44  Maple~  阅读(87)  评论(0编辑  收藏  举报