Spring MVC-04循序渐进之基于注解的控制器

2021-08-17 10:34:27 浏览数 (1)

  • 概述
  • Spring MVC注解类型
    • Controller注解类型
    • RequestMapping注解类型
      • value属性
      • 其他属性
  • 编写请求处理方法
  • 应用基于注解的控制器
    • 目录结构
    • 配置文件
    • Controller类
    • View
    • 测试应用
  • 使用@Autowired和@Service进行依赖注入
  • 重定向和Flash属性
  • 请求参数和路径变量
    • 获取请求参数
    • 获取路径变量
    • 使用路径变量有可能出现的问题
  • @ModelAttribute
    • @ModelAttribute的第一个用途
    • @ModelAttribute的第二个用途
  • 总结

概述

Spring MVC-03循序渐进之Spring MVC中我们介绍了传统的开发方式,其弊端Controller接口实现类只能处理一个单一动作,本篇博文我们来介绍下基于注解的控制器。


Spring MVC注解类型

基于注解的控制器优点如下:

  1. 一个控制器可以处理多个请求动作,而一个实现了Controller接口的控制器只能处理一个动作
  2. 基于注解的控制器的请求映射不需要存储在配置文件中,使用RequestMapping注解类型,可以对一个方法进行请求处理。

Controller和RequestMapping注解类型是SpringMVC API最重要的两个注释类型,当然了我们这里也会介绍其他一些注解类型


Controller注解类型

org.springframework.stereotype.Controller注解类型用于指示Spring类的实例是一个控制器。

下面是一个带有@Controller注解的例子

代码语言:javascript复制
import org.springframework.stereotype.Controller;

@Controller
public class ArtisanController {

}

Spring使用注解扫描的方式来找到应用中所有基于注解的控制器类,为了确保Spring能扫描到你的控制器,需要完成两件事情

  1. 在Spring MVC配置文件中声明spring-context及指定schema
  2. 然后配置component-scan扫描路径
代码语言:javascript复制
<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"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd 
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

     <context:component-scan base-package="com.artisan.springmvc.controller"/>

   

beans>

确保所有控制器类都在基本包下,并且不要指定一个太宽泛的基本包,这样会使Spring扫描了无关的包。


RequestMapping注解类型

现在我们需要在控制器类内部为每一个动作开发相应的处理方法,要让Spring知道哪一种方法来处理它的动作,需要使用org.springframework.web.bind.annotation.RequestMapping注解类型映射的URL与方法。

RequestMapping注释类型的作用:映射一个请求和一种方法,可以使用@RequestMapping注释一种方法或者一个类

一个采用了@RequestMapping注解的方法将成为一个请求处理方法,并由调度程序在接收到对应的URL请求时调用

下面是一个@RequestMapping注解方法的控制器类

代码语言:javascript复制
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ArtisanController {

    @RequestMapping(value="/doSomething")
    public String doSomething() {
        // do some bussiness logic here

        return "custoomer";
    }
}

使用RequestMapping注解的value属性将URI映射到方法。在上面的例子中,我们将doSomething映射到doSomething方法,这样,就可以使用如下URL访问doSomething方法

代码语言:javascript复制
http://domain/context/doSomething

value属性

由于value属性是RequestMapping注解的默认属性,如果只有唯一的属性,则可以省略属性名称。换句话说

代码语言:javascript复制
    @RequestMapping(value="/doSomething")
    @RequestMapping("/doSomething")

效果是等同的,但是如果超过一个属性时,就必须要输入value属性名称。

请求映射的值可以是一个空字符,此时该方法被映射到如下网址 http://domain/context

其他属性

RequestMapping除了具有value属性,还有其他属性。比如method属性用来指示改方法仅处理哪些HTTP方法. 当method为多个值时,后面写为数组{method1, method2}

例如只有在HTTP POST或者PUT方法时才能访问到下面的方法

代码语言:javascript复制
@Controller
public class ArtisanController {

    @RequestMapping(value="/doSomething",method={RequestMethod.POST,RequestMethod.PUT})
    public String doSomething() {

        // do some bussiness logic here

        return "customer";
    }
}

如果method属性仅有一个HTTP方法值,则不需要花括号

代码语言:javascript复制
@RequestMapping(value="/doSomething",method=RequestMethod.POST)

如果没有指定method属性值,则请求处理方法可以处理任意HTTP方法。


此外RequestMapping注释类型也可以用来注释一个控制器类

代码语言:javascript复制
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/artisan")
public class ArtisanController {

}

这种情况下,所有的方法都将映射为相对于类级别的请求,比如下面的deleteArtisan方法

代码语言:javascript复制
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/artisan")
public class ArtisanController {

    @RequestMapping(value="/delete",method={RequestMethod.POST,RequestMethod.PUT})
    public String deleteArtisan() {

        // do delete opertaion

        return "artisanList";
    }
}

由于控制器类的映射使用了“/artisan” ,而deleteArtisan方法映射为/delete,则如下的URL将映射到该方法上

代码语言:javascript复制
htpp://domain/context/artisan/delete

编写请求处理方法

每个请求处理方法可以有多个不同类型的参数,以及一个多钟类型的返回结果。

比如在请求处理方法中需要访问HttpSession对象,则可以添加HttpSession作为参数,Spring会将对象正确传递给方法

代码语言:javascript复制
    @RequestMapping("/uri")
    public String method(HttpSession session){

        // do something

        session.setAttribute(key, value);

        return ...;
    }

或者,如果需要访问客户端环境和HttpServletRequest,则可以在方法签名上包括这样的参数

代码语言:javascript复制
@RequestMapping("/uri")
    public String method(HttpServletRequest request,Locale locale){

        // access HttpServletRequest or Locale here 



        return ...;
    }

每个请求处理方法可以有多个不同类型的参数,下面时可以在请求处理方法中出现的参数类型:

  • javax.servlet.ServletRequest 或 javax.servlet.HttpServletRequest
  • javax.servlet.ServletResponse 或 javax.servlet.httpHttpServletResponse
  • javax.servlet.http.HttpSession
  • org.springframework.web.context.request.WebRequest 或 org.springframework.web.context.request.nativeWebRequest
  • java.util.Locale
  • java.io.InputStream 或 java.io.Reader
  • java.io.OutputStream 或 java.io.Writer
  • java.security.Principal
  • HttpEntity
  • java.util.Map 或 org.springframework.ui.Model
  • org.springframework.ui.ModelMap
  • org.springframework.web.servlet.mvc.support.RedirectAttributes
  • org.springframework.validation.Errors
  • org.springframework.validation.BindingResult
  • 命令或表单对象
  • org.springframework.web.util.UriCompontsBuilder
  • org.springframework.web.util.UriComponentsBuilder
  • @PathVariable, @MatrixVariable注释的对象
  • @RequestParam, @RequestHeader, @RequestBody 或 @RequestPart

特别重要的是org.springframework.ui.Model类型不是一个Servlet API类型,而是一个包涵Map的Spring MVC类型。每次调用请求处理方法时,Spring MVC都创建Model对象将其Map注入到各种对象

请求处理方法可以返回如下类型的对象:

  • ModelAndView
  • Model
  • Map包含模型的属性
  • View
  • 代表逻辑视图名的String
  • void
  • 提供对Servlet的访问。以相应HTTP头部和内容HttpEntity或ResponseEntity对象
  • Callable
  • DeferredResult
  • 其他任意类型,Spring将其视作输出给View的对象模型

应用基于注解的控制器

该处的示例是对前面几篇博文的重写,区别于前几篇博文中的示例在于

  • 控制器类中增加了@Controller注解
  • Spring配置文件增加了部分元素,下面详解

目录结构

maven工程结构如上,在这里,只有一个控制器类,而不是之前示例中的两个。

同时增加了一个名为index.html的静态文件,以便Spring MVC Servlet的URL模式设置为”/”时,依然可以访问静态资源


配置文件

两个配置文件,第一个为部署描述符(web.xml文件)中注册Spring MVC的DispatcherServlet ,第二个Spring MVC的配置文件 springmvc-config.xml

web.xml

代码语言:javascript复制
<web-app version="3.0" 
        xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <servlet>
        <servlet-name>springmvcservlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        servlet-class>
        <init-param>
            <param-name>contextConfigLocationparam-name>
            <param-value>/WEB-INF/config/springmvc-config.xmlparam-value>
        init-param>
        <load-on-startup>1load-on-startup>    
    servlet>

    <servlet-mapping>
        <servlet-name>springmvcservlet-name>
        <url-pattern>/url-pattern>
    servlet-mapping>
web-app>

在部署描述符中servlet-mapping元素中url-pattern设置为 / ,而不是之前实例中的action。 实际上映射动作不必一定是要用某种URL扩展。

当然,当URL设置为/,意味着所有的请求( 包括那些静态资源)都被映射到DispatcherServlet, 为了正确的处理静态资源,就必须要在Spring MVC的配置文件中添加一些 resouce元素。


springmvc-config.xml

代码语言:javascript复制
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    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.artisan.springmvc.controller"/>

    <mvc:annotation-driven/>
    <mvc:resources mapping="/css/**" location="/css/"/>
    <mvc:resources mapping="/*.html" location="/"/>

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

Spring MVC配置文件中最重要的是context:component-scan元素,这是要告诉SpringMVC扫描目标包中的类。

接下来是一个mvc:annotation-driven和两个mvc:resources。

  • mvc:annotation-driven元素做的事情内包括注册用于支持基于注解的控制器的请求处理方法的bean对象
  • mvc:resources元素用于指示Spring MVC 哪些静态资源需要单独处理,即不通过Dispatcher Servlet

在这个示例中,第一个resources元素确保/css目录下的所有文件可见 第二个允许显示所有的.html文件

注意:如果没有annotation-driven,resources元素会阻止任意控制器被调用,如果不需要使用resources,则不需要annotation-driven元素


Controller类

使用Controller注释类型的一个优点在于:一个控制器类可以包含多个请求处理方法

代码语言:javascript复制
package com.artisan.springmvc.controller;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import com.artisan.springmvc.domain.Product;
import com.artisan.springmvc.form.ProductForm;



@Controller
public class ProductController {

    private static final Log logger = LogFactory.getLog(ProductController.class);

    @RequestMapping(value="/product_input")
    public String inputProduct() {
        logger.info("inputProduct called");
        return "ProductForm";
    }

    @RequestMapping(value="/product_save")
    public String saveProduct(ProductForm productForm, Model model) {
        logger.info("saveProduct called");
        // no need to create and instantiate a ProductForm
        // create Product
        Product product = new Product();
        product.setName(productForm.getName());
        product.setDescription(productForm.getDescription());
        try {
            product.setPrice(Float.parseFloat(
                    productForm.getPrice()));
        } catch (NumberFormatException e) {
        }

        // add product

        model.addAttribute("product", product);
        return "ProductDetails";
    }
}

其中,ProductController#saveProduct()方法的第二个入参

代码语言:javascript复制
  public String saveProduct(ProductForm productForm, Model model) 

无论是否会使用,SpringMVC都会在每一个请求处理方法被调用时创建一个Model实例,用于增加需要显示在视图中的属性,例如通过调用model.addAttribute("product", product);来添加Product实例。这样Product实例就可以被添加到HttpServletRequestt中那样访问了。


View

ProductForm.jsp

代码语言:javascript复制
<html>
<head>
<title>Add Product Formtitle>
<style type="text/css">@import url(css/main.css);style>
head>
<body>

<div id="global">
<form action="product_save" method="post">
    <fieldset>
        <legend>Add a productlegend>
        <p>
            <label for="name">Product Name: label>
            <input type="text" id="name" name="name" 
                tabindex="1">
        p>
        <p>
            <label for="description">Description: label>
            <input type="text" id="description" 
                name="description" tabindex="2">
        p>
        <p>
            <label for="price">Price: label>
            <input type="text" id="price" name="price" 
                tabindex="3">
        p>
        <p id="buttons">
            <input id="reset" type="reset" tabindex="4">
            <input id="submit" type="submit" tabindex="5" 
                value="Add Product">
        p>
    fieldset>
form>
div>
body>
html>

ProductDetails.jsp

代码语言:javascript复制
<html>
<head>
<title>Save Producttitle>
<style type="text/css">@import url(css/main.css);style>
head>
<body>
<div id="global">
    <h4>The product has been saved.h4>
    <p>
        <h5>Details:h5>
        Product Name: ${product.name}<br/>
        Description: ${product.description}<br/>
        Price: $${product.price}
    p>
div>
body>
html>

测试应用

http://localhost:8080/chapter04a/product_input

输入对应的表格

提交后 http://localhost:8080/chapter04a/product_save 调用saveProduct方法


使用@Autowired和@Service进行依赖注入

使用Spring框架的一个好处是容易进行依赖注入,将依赖注入到Spring MVC控制器的最简单的方法是通过注解@Autowired到字段或者方法。Autowired注解类型属于org.springframework.beans.factory.annotation包

分两步

  1. 在配置文件中添加context:component-scan元素扫描依赖基本包
  2. 标注注解,如果是服务层,标注@Service

我们新建个maven工程来演示该功能

首先我们先看下Controller类

代码语言:javascript复制
package com.artisan.springmvc.controller;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.artisan.springmvc.domain.Product;
import com.artisan.springmvc.form.ProductForm;
import com.artisan.springmvc.service.ProductService;

@Controller
public class ProductController {

    private static final Logger logger = Logger.getLogger(ProductController.class);

    @Autowired
    private ProductService productService;

    @RequestMapping(value = "/product_input")
    public String inputProduct() {
        logger.info("inputProduct called ....");
        return "ProductForm";
    }

    @RequestMapping(value = "/product_save", method = RequestMethod.POST)
    public String saveProduct(ProductForm productForm, RedirectAttributes attributes) {
        logger.info("saveProduct called ....");
        // no need to create and instantiate a ProductForm
        // create Product
        Product product = new Product();
        product.setName(productForm.getName());
        product.setDescription(productForm.getDescription());
        try {
            product.setPrice(Float.parseFloat(productForm.getPrice()));
        } catch (NumberFormatException e) {
        }

        // add product
        Product savedProduct = productService.add(product);

        attributes.addFlashAttribute("message", "The product has been saved successfully");
        return "redirect:/product_view/"   savedProduct.getId();
    }

    /**
     * 
     * @param id
     * @param model
     * @return
     * @PathVariable用来获得请求url中的动态参数
     */
    @RequestMapping(value = "/product_view/{id}")
    public String getProductById(@PathVariable Long id, Model model) {
        logger.info("getProductById called ....");
        Product product = productService.get(id);
        model.addAttribute("product", product);
        return "ProductView";
    }

}

该ProductController类与上个例子中的ProductController类相比,做了一些调整

1. 首先自动注入了ProductService ,加了@AutoWired注解

代码语言:javascript复制
    private ProductService productService;

    public ProductService getProductService() {
        return productService;
    }

    @Autowired
    public void setProductService(ProductService productService) {
        this.productService = productService;
    }

或者

代码语言:javascript复制
@Autowired
private ProductService productService;

productService是一个提供跟踪处理产品方法的接口,为productService字段或者set方法注入@Autowired会使ProductService的一个实例被注入到ProductController实例中。

为了使类能够被Spring扫描到,必须要标注@Service

ProductService接口

代码语言:javascript复制
package com.artisan.springmvc.service;

import com.artisan.springmvc.domain.Product;

public interface ProductService {
    Product add(Product product);
    Product get(long id);
}

ProductServiceImpl类

代码语言:javascript复制
package com.artisan.springmvc.service;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

import org.springframework.stereotype.Service;

import com.artisan.springmvc.domain.Product;
/**
 * 
 * @author Mr.Yang
 * 标注了@Service的服务层
 *
 */
@Service
public class ProductServiceImpl implements ProductService {

    private Map products = new HashMap();
    // 使用AtomicLong能让long的操作在多线程下保持原子型
    private AtomicLong generator = new AtomicLong();

    public ProductServiceImpl() {
        Product product = new Product();
        add(product);
    }

    @Override
    public Product add(Product product) {
        long newId = generator.incrementAndGet();
        product.setId(newId);
        products.put(newId, product);
        return product;
    }

    @Override
    public Product get(long id) {
        return products.get(id);
    }
}

2. 然后在SpringMVC配置文件中有两个component-scan元素,一个是用于扫描控制器类,另一个新增加的为扫描服务类

代码语言:javascript复制
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    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.artisan.springmvc.controller"/>
    <context:component-scan base-package="com.artisan.springmvc.service"/>

    <mvc:annotation-driven/>
    <mvc:resources mapping="/css/**" location="/css/"/>
    <mvc:resources mapping="/*.html" location="/"/>

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

重定向和Flash属性

经常写Servlet/JSP的童鞋都知道转发和重定向的区别

转发比重定向要快,因为转发不经过客户端,而重定向会经过客户端。但是有时候采用重定向会更好,比如需要重定向到一个外部网站,则无法使用转发

另外一个使用重定向的场景是避免在用户重新加载页面的时候再次调用相同的动作 ,比如,这个示例中, 当提交产品表单时,saveProduct方法会被调用,并执行相应的动作。在真实应用中,这些所述产品会加入到数据库中。但是如果提交表单后重新加载页面,saveProduct会被再此调用,同样的产品可能被再此添加。为了避免这种情况,提交表单后,你可能更愿意将用户重定向到一个不同的页面。这个网页任意加载都没有副作用。我们这个示例中,提交表单后,将用户重定向到一个ViewProduct页面.

在这个示例中,ProductController#saveProduct方法 返回

代码语言:javascript复制
return "redirect:/product_view/"   savedProduct.getId();

这里使用重定向,而不是转发来防止当用户重新加载页面时,saveProduct被二次调用。


使用重定向有个不方便的地方:无法轻松的传值给目标页面,而转发则可以简单的将属性添加到Model中,使目标页面轻松访问。由于重定向经过客户端,所以Model中的一切都在重定向时丢失了。 幸运的是Spring3.1版本及更高的版本通过Flash属性提供了一种重定向传值的方法

要使用Flash属性,必须在Spring MVC的配置文件中有一个元素,然后,还必须在方法上添加一个新的参数类型 org.springframework.web.servlet.mvc.support.RedirectAttributes

如下所示


请求参数和路径变量

获取请求参数

请求参数和路径变量都可以用于发送值给服务器,二者都是URL的一部分。 请求参数采用key=value形式,并用&分割。

比如

代码语言:javascript复制
http://localhost:8080/chapter04b/productRetrive?productId=5

在传统的Servlet编程中,可以使用HttpServletRequest的getParameter方法来获取一个请求参数值

代码语言:javascript复制
String productId=httpServletRequest.getParameter("productId");

Spring MVC则提供了一个更简单的方法来获取请求参数的值:org.springframework.web.bind.annotation.RequestParam注释类型来获取注释方法参数,比如

代码语言:javascript复制
public void sendProduct(@RequestParam int productId)

正如所示,@RequestParam注解的参数类型不一定是字符串。


获取路径变量

路径变量类似请求参数,但是没有key部分,只有一个值, 比如我们这个示例中 product_view动作映射到如下URL

代码语言:javascript复制
/product_view/productId

其中productId表示产品标识符的整数。在SpringMVC中,productId被称作路径变量,用来发送一个值到服务器

接下来我们看下viewProduct方法演示了一个路径变量的使用

代码语言:javascript复制
/**
     * 
     * @param id
     * @param model
     * @return
     * @PathVariable用来获得请求url中的动态参数
     */
    @RequestMapping(value = "/product_view/{id}")
    public String viewProduct(@PathVariable Long id, Model model) {
        logger.info("getProductById called ....");
        Product product = productService.get(id);
        model.addAttribute("product", product);
        return "ProductView";
    }

为了使用路径变量,首先需要在RequestMapping注解的值属性中添加一个变量,该变量必须放在花括号之间,例如下面的RequestMapping注解定义一个名为id的路径变量

代码语言:javascript复制
@RequestMapping(value = "/product_view/{id}")

然后在方法签名中添加一个同名变量,并添加上@PathVariable注解。 当viewProduct方法别调用时,请求URL的id值将被复制到路径变量中,并可以在方法中使用。 路径变量的类型可以不是字符串,Spring MVC将尽量转换为非字符串类型,这个强大的功能,后续在数据绑定和表单参数中详解。

可以在请求映射中使用多个路径变量,比如userId和orderId两个路径变量

代码语言:javascript复制
@RequestMapping(value="/produtc_view/{userId}/{orderId}")

直接在浏览器中输入URL,来测试viewProduct方法的路径变量

代码语言:javascript复制
http://localhost:8080/chapter04b/product_view/5

使用路径变量有可能出现的问题

有时候,使用路径变量会遇到一个小问题:在某些情况下,浏览器可能会误解路径变量。 考虑下面的URL

代码语言:javascript复制
http://example.com/context/abc

浏览器会(正确)认为abc是一个动作。 任何静态文件路径的解析,比如css文件,将使用http://example.com/context作为基本路径。

这就是说,若服务器发送的网页包含如下img元素

,改浏览器将试图通过http://example.com/context/logo.png来加载logo.png

然而,若一个应用程序被部署为默认上线文(默认上下文是一个空字符串),则对于同一个目标的URL,会是这样

代码语言:javascript复制
http://example.com/abc

下面是带有路径变量的URL

代码语言:javascript复制
http://example.com/abc/1

这种情况下,浏览器会认为abc是上下文,没有动作。 如果在页面中使用

,浏览器将试图通过http://example.com/abc/logo.png来寻找图片,并且它将找不到图片。

幸运的是,我们有个简单的解决方法,即通过JSTL标记的URL。 标签会通过正确解析URL来修复该问题。

比如我们该案例中的css加载

代码语言:javascript复制
<style type="text/css">@import url(css/main.css);style>

改为

代码语言:javascript复制
<style type="text/css">@import url(""/>");style>

若程序部署为默认的上下文,链接标签会将改URL转换成如下形式

代码语言:javascript复制
<style type="text/css">@import url(css/main.css);style>

若程序不在默认上下文中,则会被转换为

代码语言:javascript复制
<style type="text/css">@import url(""/>");style>

我们这个示例中的上下文为chapter04b , 通过f12查看如下方式加载css http://localhost:8080/chapter04b/css/main.css

如果写成@import url(css/main.css);将加载不到css的样式。


@ModelAttribute

前面讲到Spring MVC在每次调用请求处理方法时,都会创建Model类型的一个实例。若打算使用该实例,则可以在方法中添加一个Model类型的参数。事实上还可以使用在方法中添加ModelAttribute注释类型来访问Model实例。 该注释类型也是org.springframework.web.bind.annotation包的成员。


@ModelAttribute的第一个用途

可以用@ModelAttribute来注释方法参数或者方法。

带@ModelAttribute注解的方法会将其输入的或创建的参数对象添加到Model对象中(若方法中没有显式添加)。

比如,Spring MVC将在每次调用submitOrder方法时创建一个Order实例

代码语言:javascript复制
@RequestMapping(value="/submitOrder",method=RequestMethod.Post)
public String submitOrder(@ModelAttribute("newOrder") Order order,Model model){
    ...
}

输入或者创建的Order实例将用newOrder键值添加到Model对象中,如果未定义键值名,则使用该对象类型的名称。

比如,每次调用如下方法,会使用键值order将Order实例添加到Model对象中

代码语言:javascript复制
@RequestMapping(value="/submitOrder",method=RequestMethod.Post)
public String submitOrder(@ModelAttribute Order order,Model model){
    ...
}

@ModelAttribute的第二个用途

@ModelAttribute的第二个用途是标注一个非请求的处理方法。 被@ModelAttribute注释的方法会在每次调用该控制器类的请求处理方法时被调用。

这就意味着,如果一个控制器类有两个请求处理方法,以及一个带有@ModelAttribute注解的方法,该方法的调用次数就会比每个处理请求方法更加频繁。

Spring MVC会在调用请求处理方法之前调用带有@ModelAttribute注解的方法,带@ModelAttribute注解的方法可以返回一个对象或者一个void类型,

如果返回一个对象,则返回对象会自动添加到Model中

代码语言:javascript复制
@ModelAttribute
public Product addProduct(@RequestParam String productId){
    return productService.get(produtId)
}

若返回的是void,这还必须添加一个Model类型的参数,并自行将实例添加到Model中

代码语言:javascript复制
@ModelAttribute
public void populateModel(@RequestParam String id,Model model){
    model.addAttribute(new Account(id));
}

总结

这里介绍了如何编写基于注解的控制器的Spring MVC应用,也讲解了各种注解类、方法或者方法的参数的注释类型

0 人点赞