5.2 创建自己的配置属性

5.2 创建自己的配置属性

正如前面提到的,配置属性只不过是指定来接受 Spring 环境抽象配置的 bean 的属性。没有提到的是如何指定这些 bean 来使用这些配置。

为了支持配置属性的属性注入,Spring Boot 提供了@ConfigurationProperties 注释。当放置在任何 Spring bean 上时,它指定可以从 Spring 环境中的属性注入到该 bean 的属性。

为了演示 @ConfigurationProperties 是如何工作的,假设已经将以下方法添加到 OrderController 中,以列出经过身份验证的用户之前的订单:

@GetMapping
public String ordersForUser(
    @AuthenticationPrincipal User user, Model model) {
    model.addAttribute("orders",
        orderRepo.findByUserOrderByPlaceAtDesc(user));

    return "orderList";
}

除此之外,还需要向 OrderRepository 添加了必要的 findByUser() 方法:

List<Order> findByUserOrderByPlaceAtDesc(User user);

请注意,此存储库方法是用 OrderByPlacedAtDesc 子句命名的。OrderBy 部分指定一个属性,通过该属性对结果排序 —— 在本例中是 placedAt 属性。最后的 Desc 让排序按降序进行。因此,返回的订单列表将按时间倒序排序。

如前所述,在用户下了一些订单之后,这个控制器方法可能会很有用。但对于最狂热的 taco 鉴赏家来说,它可能会变得有点笨拙。在浏览器中显示的一些命令是有用的;一长串没完没了的订单只是噪音。假设希望将显示的订单数量限制为最近的 20 个订单,可以更改 ordersForUser():

@GetMapping
public String ordersForUser(
    @AuthenticationPrincipal User user, Model model) {
    Pageable pageable = PageRequest.of(0, 20);
    model.addAttribute("orders",
        orderRepo.findByUserOrderByPlaceAtDesc(user));

    return "orderList";
}

随着这个改变,OrderRepository 跟着需要变为:

List<Order> findByUserOrderByPlaceAtDesc(User user, Pageable pageable);

这里,已经更改了 findByUserOrderByPlacedAtDesc() 方法的签名,以接受可分页的参数。可分页是 Spring Data 通过页码和页面大小选择结果子集的方式。在 ordersForUser() 控制器方法中,构建了一个 PageRequest 对象,该对象实现了 Pageable 来请求第一个页面(page zero),页面大小为 20,以便为用户获得最多 20 个最近下的订单。

虽然这工作得非常好,但它让我感到有点不安,因为已经硬编码了页面大小。如果后来发现 20 个订单太多,而决定将其更改为 10 个订单,该怎么办?因为它是硬编码的,所以必须重新构建和重新部署应用程序。

可以使用自定义配置属性来设置页面大小,而不是硬编码页面大小。首先,需要向 OrderController 添加一个名为 pageSize 的新属性,然后在 OrderController 上使用 @ConfigurationProperties 注解 ,如下面的程序清单所示。程序清单 5.1 在 OrderController 中使用配置属性

@Controller
@RequestMapping("/orders")
@SessionAttributes("order")
@ConfigurationProperties(prefix="taco.orders")
public class OrderController {

    private int pageSize = 20;

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    ...

    @GetMapping
    public String ordersForUser(
        @AuthenticationPrincipal User user, Model model) {
        Pageable pageable = PageRequest.of(0, pageSize);
        model.addAttribute("orders",
            orderRepo.findByUserOrderByPlacedAtDesc(user, pageable));
        return "orderList";
    }
}

程序清单 5.1 中最重要的变化是增加了 @ConfigurationProperties 注解。其 prefix 属性设置为 taco。这意味着在设置 pageSize 属性时,需要使用一个名为 taco.orders.pageSize 的配置属性。

新的 pageSize 属性默认为 20。但是可以通过设置 taco.orders.pageSize 属性轻松地将其更改为想要的任何值。例如,可以在 application.yml 中设置此属性:

taco:
  orders:
    pageSize: 10

或者,如果需要在生产环境中进行快速更改,可以通过设置 taco.orders.pageSize 属性作为环境变量来重新构建和重新部署应用程序:

$ export TACO_ORDERS_PAGESIZE=10

可以设置配置属性的任何方法,都可以用来调整最近订单页面的大小。接下来,我们将研究如何在属性持有者中设置配置数据。

5.2.1 定义配置属性持有者

这里没有说 @ConfigurationProperties 必须设置在控制器或任何其他特定类型的 bean 上,@ConfigurationProperties 实际上经常放在 bean 上。在应用程序中,这些 bean 的惟一目的是作为配置数据的持有者,这使控制器和其他应用程序类不涉及特定于配置的细节,它还使得在几个可能使用该信息的 bean 之间共享公共配置属性变得很容易。

对于 OrderController 中的 pageSize 属性,可以将其提取到一个单独的类中。下面的程序清单以这种方式使用了 OrderProps 类。程序清单 5.2 提取 pageSize 到持有者类

package tacos.web;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import lombok.Data;

@Component
@ConfigurationProperties(prefix="taco.orders")
@Data
public class OrderProps {
    private int pageSize = 20;
}

正如在 OrderController 中所做的,pageSize 属性默认为 20,同时 OrderProps 使用 @ConfigurationProperties 进行注解,以具有 taco.orders 前缀。

它还带有 @Component 注解,因此 Spring 组件扫描时将自动发现它并在 Spring 应用程序上下文中将其创建为 bean。这很重要,因为下一步是将 OrderProps bean 注入到 OrderController 中。

关于配置属性持有者,没有什么特别的。它们是从 Spring 环境中注入属性的 bean。它们可以被注入到任何需要这些属性的其他 bean 中。对于 OrderController,这意味着从 OrderController 中删除 pageSize 属性,而不是注入并使用 OrderProps bean:

@Controller
@RequestMapping("/orders")
@SessionAttributes("order")
public class OrderController {

    private OrderRepository orderRepo;

    private OrderProps props;

    public OrderController(OrderRepository orderRepo,
             OrderProps props) {
        this.orderRepo = orderRepo;
        this.props = props;
    }

    ...

    @GetMapping
    public String ordersForUser(
        @AuthenticationPrincipal User user, Model model) {
        Pageable pageable = PageRequest.of(0, props.getPageSize());
        model.addAttribute("orders",
            orderRepo.findByUserOrderByPlacedAtDesc(user, pageable));
        return "orderList";
    }

    ...
}

现在 OrderController 不再负责处理它自己的配置属性。这使得 OrderController 中的代码稍微整洁一些,并允许在任何其他需要它们的 bean 中重用 OrderProps 中的属性。此外,正在收集与一个地方的订单相关的配置属性:OrderProps 类。如果需要添加、删除、重命名或以其他方式更改其中的属性,只需要在 OrderProps 中应用这些更改。

例如,假设在其他几个 bean 中使用 pageSize 属性,这时最好对该属性应用一些验证,以将其值限制为不小于 5 和不大于 25。如果没有持有者 bean,将不得不对 OrderController、pageSize 属性以及使用该属性的所有其他类应用验证注解。但是因为已经将 pageSize 提取到 OrderProps 中,所以只需要更改 OrderProps:

package tacos.web;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import org.springframework.boot.context.properties.
ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import lombok.Data;

@Component
@ConfigurationProperties(prefix="taco.orders")
@Data
@Validated
public class OrderProps {
    @Min(value=5, message="must be between 5 and 25")
    @Max(value=25, message="must be between 5 and 25")
    private int pageSize = 20;
}
//end::validated[]

尽管可以很容易地将 @Validated、@Min 和 @Max 注解应用到 OrderController(以及可以注入 OrderProps 的任何其他 bean),但这只会使 OrderController 更加混乱。通过使用配置属性持有者 bean,就在在一个地方收集了配置属性的细节,使得需要这些属性的类相对干净。

5.2.2 声明配置属性元数据

根据 IDE 的情况,你可能已经注意到 application.yml(或是 appication.properties)中的 taco.orders.pageSize 属性有一个警告,说类似未知属性 ’taco’ 之类的东西。出现此警告是因为缺少关于刚刚创建的配置属性的元数据。图 5.2 显示了我将鼠标悬停在 Spring Tool Suite 中 taco 属性时的效果。

![图 5.2 缺少配置属性元数据出现的警告](E:\Document\spring-in-action-v5-translate\第一部分 Spring 基础\第五章 使用配置属性\图 5.2 缺少配置属性元数据出现的警告.jpg)

图 5.2 缺少配置属性元数据出现的警告

配置属性元数据是完全可选的,并不会阻止配置属性的工作。但是元数据对于提供有关配置属性的最小文档非常有用,特别是在 IDE 中。

例如,当我将鼠标悬停在 security.user.password 属性上时,如图 5.3 所示,虽然悬停帮助你获得的是最小的,但它足以帮助你了解属性的用途以及如何使用它。

![图 5.3 在 Spring Tool Suite 中悬停显示配置属性文档](E:\Document\spring-in-action-v5-translate\第一部分 Spring 基础\第五章 使用配置属性\图 5.3 在 Spring Tool Suite 中悬停显示配置属性文档.jpg)

图 5.3 在 Spring Tool Suite 中悬停显示配置属性文档

为了帮助那些可能使用你定义的配置属性(甚至可能是你自己定义的)的人,通常最好是围绕这些属性创建一些元数据,至少它消除了 IDE 中那些恼人的黄色警告。

要为自定义配置属性创建元数据,需要在 META-INF(例如,在项目下的 src/main/resources/META-INF 中)中创建一个名为 addition-spring-configuration-metadata.json 的文件。

快速修复缺失的元数据。

如果正在使用 Spring Tool Suite,则有一个用于创建丢失的属性元数据的快速修复选项。将光标放在缺少元数据警告的行上,然后按下 Mac 上的 CMD-1 或 Windows 和 Linux 上的 Ctrl-1 弹出的快速修复(参见图 5.4)。

![图 5.4 在 Spring Tool Suite 中使用快速弹出方式创建配置属性元数据](E:\Document\spring-in-action-v5-translate\第一部分 Spring 基础\第五章 使用配置属性\图 5.4 在 Spring Tool Suite 中使用快速弹出方式创建配置属性元数据.jpg)

图 5.4 在 Spring Tool Suite 中使用快速弹出方式创建配置属性元数据

然后选择 Create Metadata for… 选项来为属性添加一些元数据(在 additional-spring-configuration-metadata 中)。如果该文件不存在,则创建该文件。

对于 taco.orders.pageSize 属性,可以用以下 JSON 设置元数据:

{
  "properties": [
    {
      "name": "taco.orders.page-size",
      "type": "java.lang.String",
      "description": "Sets the maximum number of orders to display in a list."
    }
  ]
}

注意,元数据中引用的属性名是 taco.orders.pagesize。Spring Boot 灵活的属性命名允许属性名的变化,比如 taco.orders.page-size 相当于 taco.orders.pageSize。

有了这些元数据,警告就应该消失了。更重要的是,如果你悬停在 taco.orders.pageSize 属性,你将看到如图 5.5 所示的描述。

![图 5.5 悬停显示自定义配置属性帮助](E:\Document\spring-in-action-v5-translate\第一部分 Spring 基础\第五章 使用配置属性\图 5.5 悬停显示自定义配置属性帮助.jpg)

图 5.5 悬停显示自定义配置属性帮助

另外,如图 5.6 所示,可以从 IDE 获得自动完成帮助,就像 Springprovided 的配置属性一样。

![图 5.6 配置属性元数据让属性值自动填充](E:\Document\spring-in-action-v5-translate\第一部分 Spring 基础\第五章 使用配置属性\图 5.6 配置属性元数据让属性值自动填充.jpg)

图 5.6 配置属性元数据让属性值自动填充

配置属性对于调整自动配置的组件和注入到应用程序 bean 中的细节非常有用。但是,如果需要为不同的部署环境配置不同的属性呢?让我们看看如何使用 Spring 配置文件来设置特定于环境的配置。

上一页
下一页