分类
Spring MVC

Spring请求参数注解@RequestParam

译者注

原文

https://www.baeldung.com/spring-request-param

Demo

https://github.com/eugenp/tutorials/tree/master/spring-web-modules/spring-mvc-basics-5

一、前言

在本文中,我们将要学习Spring的@RequestParam注解,和它的作用。
我们可以使用@RequestParam,从请求的参数或文件中,去解析查询参数。

二、简单的映射

假设我们定义了一个请求地址/api/foos,它接收了一个名为id的参数:

@GetMapping("/api/foos")
@ResponseBody
public String getFoos(@RequestParam String id) {
    return "ID: " + id;
}

在这个示例中,我们使用@RequestParam来解析id的查询参数。

一个简单的GET请求将会调用这个getFoos()方法:

http://localhost:8080/spring-mvc-basics/api/foos?id=abc
----
ID: abc

接下来,我们看一下这个注解的参数,包括:

  • 名称name
  • value
  • 必须required
  • 默认值defaultValue

3. 指定请求参数名称

在前面的例子中,变量名称和参数名称是相同的。但有时我们需要让这二者不同,或者我们没有使用SpringBoot,此时我们可能需要在编译时的配置文件中做一些额外的操作,否则参数名称将不会进行二进制编码。

幸运的是,我们可以使用name属性来配置请求参数@RequestParam的名称:

@PostMapping("/api/foos")
@ResponseBody
public String addFoo(@RequestParam(name = "id") String fooId, @RequestParam String name) { 
    return "ID: " + fooId + " Name: " + name;
}

我们还可以写成@RequestParam(value = “id”)或者干脆写成@RequestParam(“id”)

4. 可选的请求参数

使用@RequestParam注解的方法参数默认是必须的,这就一位置如果参数没有在请求中给出,就会发生错误:

GET /api/foos HTTP/1.1
-----
400 Bad Request
Required String parameter 'id' is not present

我们可以配置required = false来实现可选功能optional

@GetMapping("/api/foos")
@ResponseBody
public String getFoos(@RequestParam(required = false) String id) { 
    return "ID: " + id;
}

此时,无论请求中有没有给出这个参数,都可以正确的解析到我们刚才写的方法上:

http://localhost:8080/spring-mvc-basics/api/foos?id=abc
----
ID: abc

如果请求中没有给出参数,获取到的是null,而不是出错:

http://localhost:8080/spring-mvc-basics/api/foos
----
ID: null

4.1 使用Java8的Optional

我们还可以用Optional去装饰这个参数:

@GetMapping("/api/foos")
@ResponseBody
public String getFoos(@RequestParam Optional<String> id){
    return "ID: " + id.orElseGet(() -> "not provided");
}

此时,我们就不需要再去设置required属性了。如果请求中没有给出参数的值,就会返回默认值。

http://localhost:8080/spring-mvc-basics/api/foos
----
ID: not provided

5. 请求参数的默认值

我们通过defaultValue属性,为请求参数设置默认值:

@GetMapping("/api/foos")
@ResponseBody
public String getFoos(@RequestParam(defaultValue = "test") String id) {
    return "ID: " + id;
}

这样写的效果就类似required=false,用户不必再去提供这个参数:

http://localhost:8080/spring-mvc-basics/api/foos
----
ID: test

也可以提供参数:

http://localhost:8080/spring-mvc-basics/api/foos?id=abc
----
ID: abc

需要注意的是,当我们设置默认值属性时,必须属性required就已经设置为false了。

6. 映射所有的参数

我们还可以用映射,实现在没有定义参数名称的情况下,使用多个参数:

@PostMapping("/api/foos")
@ResponseBody
public String updateFoos(@RequestParam Map<String,String> allParams) {
    return "Parameters are " + allParams.entrySet();
}

上面的写法会返回请求的参数,如果发起请求就会:

curl -X POST -F 'name=abc' -F 'id=123' http://localhost:8080/spring-mvc-basics/api/foos
-----
Parameters are {[name=abc], [id=123]}

7. 映射多值参数

单一变量的请求参数可以包含多个值:

@GetMapping("/api/foos")
@ResponseBody
public String getFoos(@RequestParam List<String> id) {
    return "IDs are " + id;
}

并且SpringMVC会自动映射到一个逗号分隔的id参数:

http://localhost:8080/spring-mvc-basics/api/foos?id=1,2,3
----
IDs are [1,2,3]

或者一个分离的id列表:

http://localhost:8080/spring-mvc-basics/api/foos?id=1&id=2
----
IDs are [1,2]

8. 结论

在本文中,我们学习了如何使用@RequestParam注解。
你可以在Github上找到本文中示例的完整代码。

分类
Spring MVC

Spring 路径变量注解 @Pathvariable

译者注

原文

https://www.baeldung.com/spring-pathvariable

Demo

https://github.com/eugenp/tutorials/tree/master/spring-web-modules/spring-mvc-java-2

一、前言

本文中我们将会学习Spring的路径变量注解@PathVariable
路径注解可以用来处理URI映射中的模板变量,并且设置这些变量作为方法参数。

我们来看看@PathVariable和它的各种属性。

二、简单的映射

@PathVariable注解的简单用法,可以通过主键来区分实体:

@GetMapping("/api/employees/{id}")
@ResponseBody
public String getEmployeesById(@PathVariable String id) {
    return "ID: " + id;
}

在这个例子中,我们使用@PathVariable注解来解析URI,前面的部分是固定的,后面是变量{id}

当我们是用GET方式向/api/employees/{id}发起请求时,就会给getEmployeesById方法提供参数id的值:

http://localhost:8080/api/employees/111
---- 
ID: 111

现在我们进一步了解这个注解,并且看一看它的属性。

3. 指定路径变量名称

在前面的例子中,我们跳过了“定义模板路径变量名称”的步骤,因为方法中的参数名称和路径变量的名称是相同的,Spring自动完成了匹配。

然而,如果路径变量名称和参数名称不同,我们可以在路径变量注解@PathVariable中指定它:

@GetMapping("/api/employeeswithvariable/{id}")
@ResponseBody
public String getEmployeesByIdWithVariableName(@PathVariable("id") String employeeId) {
    return "ID: " + employeeId;
}

发起请求时识别的变量如下:

http://localhost:8080/api/employeeswithvariable/1 
----
ID: 1

我们还可以像这样清楚的定义路径变量@PathVariable(value=”id”),而不是PathVariable(“id”)

4. 在单次请求中定义多个路径变量

根据实际使用情况,我们可以在一个控制器方法的URI请求中,使用一个以上的路径变量,当然,这个方法的参数也不止一个:

@GetMapping("/api/employees/{id}/{name}")
@ResponseBody
public String getEmployeesByIdAndName(@PathVariable String id, @PathVariable String name) {
    return "ID: " + id + ", name: " + name;
}

当发起请求时返回的结果如下:

http://localhost:8080/api/employees/1/bar
----
ID: 1, name: bar

我们也可以用一个java.util.Map<String, String>:类型的方法参数,处理一个以上的@PathVariable路径变量,如下面代码,id和name参数被打包成一个整体来处理:

@GetMapping("/api/employeeswithmapvariable/{id}/{name}")
@ResponseBody
public String getEmployeesByIdAndNameWithMapVariable(@PathVariable Map<String, String> pathVarsMap) {
    String id = pathVarsMap.get("id");
    String name = pathVarsMap.get("name");
    if (id != null && name != null) {
        return "ID: " + id + ", name: " + name;
    } else {
        return "Missing Parameters";
    }
}

请求的结果:

http://localhost:8080/api/employees/1/bar
----
ID: 1, name: bar

然而当路径变量@PathVariable中出现句点符号(.)时,就会出现一些小问题。
对于这种少数情况的讨论可以查看链接

5. 可选的路径变量

在Spring中,使用@PathVariable注解的方法参数默认是必要(required)的,即一旦使用注解就必须把值传过来:

@GetMapping(value = { "/api/employeeswithrequired", "/api/employeeswithrequired/{id}" })
@ResponseBody
public String getEmployeesByIdWithRequired(@PathVariable String id) {
    return "ID: " + id;
}

如上所示,这个控制器同时处理两个路径,/api/employeeswithrequired/api/employeeswithrequired/1 request。然而因为这个方法使用了@PathVariables注解,所以它不能处理发送到这个没有参数的/api/employeeswithrequired路径的请求:

http://localhost:8080/api/employeeswithrequired
----
{"timestamp":"2020-07-08T02:20:07.349+00:00","status":404,"error":"Not Found","message":"","path":"/api/employeeswithrequired"}

http://localhost:8080/api/employeeswithrequired/1
----
ID: 111

对于这种问题,有两种处理办法。

5.1 设置@PathVariable注解为非必要(required = false)

我们可以设置@PathVariable注解的必要(required)属性为false,来让它变成可选参数,同时,加入对于参数为空时的处理办法:

@GetMapping(value = { "/api/employeeswithrequiredfalse", "/api/employeeswithrequiredfalse/{id}" })
@ResponseBody
public String getEmployeesByIdWithRequiredFalse(@PathVariable(required = false) String id) {
    if (id != null) {
        return "ID: " + id;
    } else {
        return "ID missing";
    }
}

当对于这个API发起不带参数的请求时,结果如下:

http://localhost:8080/api/employeeswithrequiredfalse
----
ID missing

5.2 使用java.util.Optional

介绍了Spring4.1之后,在JAVA8以后的版本中,我们也可以使用java.util.Optional来处理非必要的路径参数:

@GetMapping(value = { "/api/employeeswithoptional", "/api/employeeswithoptional/{id}" })
@ResponseBody
public String getEmployeesByIdWithOptional(@PathVariable Optional<String> id) {
    if (id.isPresent()) {
        return "ID: " + id.get();
    } else {
        return "ID missing";
    }
}

现在,如果不在请求中指定路径变量id,我们将会得到默认的返回结果:

http://localhost:8080/api/employeeswithoptional
----
ID missing

5.3 使用Map<String, String>类型的方法参数

在前面的示例中,我们可以使用一个java.util.Map类型的方法参数去处理URI中的所有路径变量。现在,我们也可以这样去处理非必要路径变量的情况:

@GetMapping(value = { "/api/employeeswithmap/{id}", "/api/employeeswithmap" })
@ResponseBody
public String getEmployeesByIdWithMap(@PathVariable Map<String, String> pathVarsMap) {
    String id = pathVarsMap.get("id");
    if (id != null) {
        return "ID: " + id;
    } else {
        return "ID missing";
    }
}

6. @PathVariable的默认值

开箱即用,@PathVariable注解没有定义默认值的方法。然而,我们可以用上面提到的一些办法,来让默认值满足我们的需要,只需要检查路径变量是否为null。
例如,使用java.util.Optional<String, String>,我们可以验证路径变量是不是空值,如果它是空值,就可以返回一个默认值:

@GetMapping(value = { "/api/defaultemployeeswithoptional", "/api/defaultemployeeswithoptional/{id}" })
@ResponseBody
public String getDefaultEmployeesByIdWithOptional(@PathVariable Optional<String> id) {
    if (id.isPresent()) {
        return "ID: " + id.get();
    } else {
        return "ID: Default Employee";
    }
}

7. 结论

在本文中,我们讨论了如何使用Spring的路径变量注解@PathVariable
我们有很多高效的方法去应多不同的使用场景,例如“可选参数”和“返回默认值”等。

你可以在Github上获得本文中的示例代码

分类
Spring MVC

在Spring中使用Thymeleaf模板引擎

1. 概述

Thymeleaf是一款优秀的JAVA模板引擎。该引擎工作在服务端,能够处理HTML、XML、JavaScript、css甚至纯文本。Thymeleaf拥有良好的扩展性,与其他流行的模板引擎(例如JSP)相比,拥有更快的开发效率,特别是在团队合作开发中。

本文将阐述如何在Spring MVC应用中的视图层中使用Thymeleaf模板引擎。

2. Thymeleaf与Spring集成

Thymeleaf能够非常轻松的与Spring结合在一起,在Spring MVC项目中,只需要引入依赖spring boot提供的Thymeleaf即可:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

如果你没有使用Spring Boot,则可参考官方文档进行配置。当然了,那将比使用Spring Boot麻烦的多。

3. 显示多语言文件(Message Source)中的值

th:text=”#{key}”标签可以显示多语言文件中的值。Spring Boot项目默认加载messages多语言系列源。若加载自定义的多语言源,则可以参考以下实现

    /**
     * 手动注册多国语言文件messages
     * Spring Boot其实已经默认注册了messages,所以删除该方法后不会对应用造成影响
     * 在此的代码仅做演示用
     *
     * @return
     */
    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }

在Thymeleaf模板中,便可如下使用:

<h1 th:text="#{welcome}"></h1>

则H1中的值将根据当前用户的地区及语言,对应显示不同的内容。比如对简体中文用户显示“您好”,而对英文用户显示"welcome"。

4. 显示模型属性

4.1 简单属性

th:text=”${attributename}”用于显示模型model上的属性。比如我们在模型上添加当前用户currentUser

model.addAttribute("currentUser", "zhangsan");

则在Thymeleaf模板中可以如下显示:

<h2 th:text="${currentUser}"></h2>

4.2 集合Collection属性

如果model上的某个属性类型为集合Collection,则可以使用th:each来达到遍历集合的目的。比如有学生Student类有如下字段:

public class Student {

    private Long id;
    private String name;
    private Boolean sex;
    
    // 省略getter/setter

控制器中绑定如下:

List<Student> students = new ArrayList<>();
for (int i = 0; i < 10; i++) {
    students.add(new Student(Long.valueOf(i), "name" + i, i % 2 == 0));
}
model.addAttribute("students", students);

则在Themeleaf模板中如下进行遍历:

<tr th:each="student: ${students}">
    <td th:text="${student.id}"></td>
    <td th:text="${student.name}"></td>
</tr>

5. 条件判断

5.1 if以及unless

th:if=”${condition}”用以当condition成立时进行显示。相反的th:unless=”${condition}” 用以当condition不成立时显示。

比如我们可以如下显示性别:

    <td th:if="${student.sex}" th:text="男"></td>
    <td th:unless="${student.sex}" th:text="女"></td>

5.2 switch以及case

th:switch和th:case的用法当然也不能理解,比如可以使用switch、case如下显示性别:

    <td th:switch="${student.sex}">
        <span th:case="true" th:text="男"></span>
        <span th:case="false" th:text="女"></span>
    </td>

6. 处理输入

Form表单输入可以使用 th:action=”@{url}”以及th:object=”${object}” 轻松完成。th:action用于表示Form表单的提交地址,th:object用以标注Form表单绑定的对象。在表单中直接使用th:field=”*{name}” 来绑定表单中的字段,其中name关键字与object上的属性相对应。比如新建如下添加学生表单:

<form action="#" th:action="@{/student/save}" th:object="${student}" method="post">
    <table border="1">
        <tr>
            <td>ID</td>
            <td><input type="number" th:field="*{id}"/></td>
        </tr>
        <tr>
            <td>姓名</td>
            <td><input type="text" th:field="*{name}"/></td>
        </tr>
        <tr>
            <td><input type="submit" value="提交"/></td>
        </tr>
    </table>
</form>

上述代码中th:action上的/student/save标明了该表单的提交地址,th:object上的student则是该表单绑定的对象。

然后在C层中可以如下接收:

    @PostMapping("save")
    String save(Student student) {
        // 处理新增学生逻辑
    }

7. 显示校验信息

借助于spring-boot-starter-validation,可以轻松的完成输入数据的校验工作:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

在C层中完成校验仅仅需要加入@Valid注解以及注入BingdingResult:

    @PostMapping("save")
    String save(@Valid Student student, BindingResult errors) {
        if (errors.hasErrors()) {
            return "student/add";
        }

        // 具体持久化学生的代码略过

        return "redirect:/student/success";
    }

在模板中,可以使用#fields.hasErrors()方法来获取是否发生校验错误;使用#fields.hasErrors(final String field), #fields.errors(final String field)方法来更精确到获取到某个字段是否发生校验错误;使用th:errors 属性来显示具体的错误信息:

        <tr>
            <td>ID</td>
            <td><input type="number" th:field="*{id}"/></td>
            <td th:if="${#fields.errors('id')}" th:errors="*{id}">id校验信息</td>
        </tr>
        <tr>
            <td>姓名</td>
            <td><input type="text" th:field="*{name}"/></td>
            <td th:if="${#fields.hasErrors('name')}" th:errors="*{name}">name校验信息</td>
        </tr>

此外#fields.errors()还可以接收*all做为参数值,表示:发生的所有的错误的集合。比如实现遍历错误信息并依次输出:

    <h1 th:if="${#fields.hasErrors()}">发生错误</h1>
    <ul>
        <li th:each="err : ${#fields.errors('*')}" th:text="${err}"/>
    </ul>

或者:

<li th:each="err : ${#fields.errors('all')}" th:text="${err}"/>

或者传入global来获取全局的错误信息:

<li th:each="err : ${#fields.errors('global')}" th:text="${err}" />

8. 数据转换

在Thymeleaf模板中,可以使用{{}}来格式化输出字段。比如我们在输出学生姓名name时,将首字母进行大写,则可以建立实现Formatter接口的如下NameFormatter:

/**
 * 名称首写字大写转换器
 */
public class NameFormatter implements Formatter<String> {

    @Override
    public String parse(String s, Locale locale) throws ParseException {
        if (s != null &amp;&amp; !s.isEmpty()) {
            return s.toUpperCase().charAt(0) + s.substring(1);
        }

        return "";
    }

    @Override
    public String print(String s, Locale locale) {
        return s;
    }
}

并于配置文件中注册该转换器:

@Configuration
public class SpringWebConfig implements WebMvcConfigurer {

    /**
     * 注册转换器
     *
     * @param registry 转换器注册商
     */
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addFormatter(new NameFormatter());
    }

然后便可以在模板中引入{{}}使得Thymeleaf自动调用该转换器了:

<td th:text="${{student.name}}"></td>

除按字段名进行转换外,还支持按类型进行转换。比如我们新建如下StudentFormatter用以显示Student类型:

/**
 * 学生实体 转换器
 */
public class StudentFormatter implements Formatter<Student> {

    /**
     * 将字符串转换为Student实体
     * 本例中未使用该方法,直接抛出异常
     *
     * @param text   字符器
     * @param locale 地区
     * @return
     * @throws ParseException
     */
    @Override
    public Student parse(String text, Locale locale) throws ParseException {
        throw new RuntimeException("方法未实现");
    }

    /**
     * 将学生实体转为输出的字符串
     * @param student 学生
     * @param locale 地区
     * @return
     */
    @Override
    public String print(Student student, Locale locale) {
        return student.getId().toString() + "-" + (student.getSex() ? "男" : "女") + "-" + student.getName();
    }
}

注册如下:

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addFormatterForFieldType(Student.class, new StudentFormatter());
        registry.addFormatter(new NameFormatter());
    }

便可在模板中使用如下代码来显示学生的整体信息:

<td th:text="${{student}}"></td>

最后,还可以使用#conversions 模板中将对象的进行强制转换。#conversions.convert(Object, Class)语法表示将Object转换为Class类型。比如我们将性别sex字段由Boolean类型转换为String类型:

<td th:text="${#conversions.convert(student.sex, 'String')}"></td>

9. 总结

本文对Spring MVC应用集成Thymeleaf模板引擎进行了简单的介绍。Thymeleaf模板引擎可以基于Spring MVC快速的创建应用程序,数据绑定方式灵活、易用。

最后,我们一如既往的为本文提供了完整、可运行的code demo,希望能对你有所帮助。