返回介绍

3.2.2 应用程序 Bean 的配置外置

发布于 2025-04-21 21:10:07 字数 7503 浏览 0 评论 0 收藏

假设我们在某人的阅读列表里不止想要展示图书标题,还要提供该书的 Amazon 链接。我们不仅想提供该书的链接,还要标记该书,以便利用 Amazon 的 Associate Program,这样如果有人用我们应用程序里的链接买了书,我们还能收到一笔推荐费。

这很简单,只需修改 Thymeleaf 模板,以链接的形式来呈现每本书的标题就可以了:

<a th:href="'http://www.amazon.com/gp/product/'
            + ${book.isbn}
            + '/tag=habuma-20'"
   th:text="${book.title}">Title</a>

这样就好了。现在如果有人点击该链接并购买了本书,我就能得到推荐费了,因为 habuma-20 是我的 Amazon Associate ID。如果你也想收到推荐费,可以把 Thymeleaf 模板中 tag 的值改成你的 Amazon Associate ID。

虽然在模板里修改这个值很简单,但这毕竟也是硬编码。现在只在一个模板里链接到 Amazon,但后续可能会有更多页面链接到 Amazon,于是需要为应用程序添加功能。那样的话,修改 Amazon Associate ID 就要改动好几个地方。因此,这种细节最好不要放在代码里,要把它们集中在一个地方维护。

我们可以不在模板里硬编码 Amazon Associate ID,而是把它变成模型中的一个值:

<a th:href="'http://www.amazon.com/gp/product/'
            + ${book.isbn}
            + '/tag=' + ${amazonID}"
   th:text="${book.title}">Title</a>

此外, ReadingListController 需要在模型里包含 amazonID 这个键,其中的内容是 Amazon Associate ID。同样的道理,我们不应该硬编码这个值,而是应该引用一个实例变量。这个变量的值应该来自属性配置。代码清单 3-4 就是新的 ReadingListController ,它会返回注入的 Amazon Associate ID。

代码清单 3-4 修改后的 ReadingListController ,能接受 Amazon ID

package readinglist;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/")
@ConfigurationProperties(prefix="amazon")        ←---属性注入
public class ReadingListController {

  private String associateId;

  private ReadingListRepository readingListRepository;

  @Autowired
  public ReadingListController(
        ReadingListRepository readingListRepository) {
    this.readingListRepository = readingListRepository;
  }

  public void setAssociateId(String associateId) {     ←---associateId 的 setter 方法
    this.associateId = associateId;
  }

  @RequestMapping(method=RequestMethod.GET)
  public String readersBooks(Reader reader, Model model) {
    List<Book> readingList =
               readingListRepository.findByReader(reader);
    if (readingList != null) {
      model.addAttribute("books", readingList);
      model.addAttribute("reader", reader);
      model.addAttribute("amazonID", associateId);     ←---将 associateId 放入模型
    }
    return "readingList";
  }

  @RequestMapping(method=RequestMethod.POST)
  public String addToReadingList(Reader reader, Book book) {
    book.setReader(reader);
    readingListRepository.save(book);
    return "redirect:/";
  }

}

如你所见, ReadingListController 现在有了一个 associateId 属性,还有对应的 setAssociateId() 方法,用它可以设置该属性。 readersBooks() 现在能通过 amazonID 这个键把 associateId 的值放入模型。

棒极了!现在就剩一个问题了 - 从哪里能取到 associateId 的值。

请注意, ReadingListController 上加了 @ConfigurationProperties 注解,这说明该 Bean 的属性应该是(通过 setter 方法)从配置属性值注入的。说得更具体一点, prefix 属性说明 ReadingListController 应该注入带 amazon 前缀的属性。

综合起来,我们指定 ReadingListController 的属性应该从带 amazon 前缀的配置属性中进行注入。 ReadingListController 只有一个 setter 方法,就是设置 associateId 属性用的 setter 方法。因此,设置 Amazon Associate ID 唯一要做的就是添加 amazon.associateId 属性,把它加入支持的任一属性源位置里即可。

例如,我们可以在 application.properties 里设置该属性:

amazon.associateId=habuma-20

或者在 application.yml 里设置:

amazon:
  associateId: habuma-20

或者,我们可以将其设置为环境变量,把它作为命令行参数,或把它加到任何能够设置配置属性的地方。

开启配置属性 从技术上来说, @ConfigurationProperties 注解不会生效,除非先向 Spring 配置类添加 @EnableConfigurationProperties 注解。但通常无需这么做,因为 Spring Boot 自动配置后面的全部配置类都已经加上了 @EnableConfigurationProperties 注解。因此,除非你完全不使用自动配置(那怎么可能?),否则就无需显式地添加 @EnableConfigurationProperties

还有一点需要注意,Spring Boot 的属性解析器非常智能,它会自动把驼峰规则的属性和使用连字符或下划线的同名属性关联起来。换句话说, amazon.associateId 这个属性和 amazon.associate_id 以及 amazon.associate-id 都是等价的。用你习惯的命名规则就好。

在一个类里收集属性

虽然在 ReadingListController 上加上 @ConfigurationProperties 注解跑起来没问题,但这并不是一个理想的方案。 ReadingListController 和 Amazon 没什么关系,但属性的前缀却是 amazon ,这看起来难道不奇怪吗?再说,后续的各种功能可能需要在 ReadingListController 里新增配置属性,而它们和 Amazon 无关。

与其在 ReadingListController 里加载配置属性,还不如创建一个单独的 Bean,为它加上 @ConfigurationProperties 注解,让这个 Bean 收集所有配置属性。代码清单 3-5 里的 AmazonProperties 就是一个例子,它用于加载 Amazon 相关的配置属性。

代码清单 3-5 在一个 Bean 里加载配置属性

package readinglist;

import org.springframework.boot.context.properties.
                                   ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties("amazon")      ←---注入带 amazon 前缀的属性
public class AmazonProperties {

  private String associateId;

  public void setAssociateId(String associateId) {     ←---associateId 的 setter 方法
    this.associateId = associateId;
  }

  public String getAssociateId() {
    return associateId;
  }

}

有了加载 amazon.associateId 配置属性的 AmazonProperties 后,我们可以调整 ReadingListController (如代码清单 3-6 所示),让它从注入的 AmazonProperties 中获取 Amazon Associate ID。

代码清单 3-6 注入了 AmazonPropertiesReadingListController

package readinglist;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/")
public class ReadingListController {

  private ReadingListRepository readingListRepository;
  private AmazonProperties amazonProperties;

  @Autowired
  public ReadingListController(
      ReadingListRepository readingListRepository,
      AmazonProperties amazonProperties) {    ←---注入 AmazonProperties
    this.readingListRepository = readingListRepository;
    this.amazonProperties = amazonProperties;
  }

  @RequestMapping(method=RequestMethod.GET)
  public String readersBooks(Reader reader, Model model) {
    List<Book> readingList =
        readingListRepository.findByReader(reader);
    if (readingList != null) {
      model.addAttribute("books", readingList);
      model.addAttribute("reader", reader);
      model.addAttribute("amazonID", amazonProperties.getAssociateId());   ←---向模型中添加 Associate ID
    }
    return "readingList";
  }

  @RequestMapping(method=RequestMethod.POST)
  public String addToReadingList(Reader reader, Book book) {
    book.setReader(reader);
    readingListRepository.save(book);
    return "redirect:/";
  }

}

ReadingListController 不再直接加载配置属性,转而通过注入其中的 AmazonProperties Bean 来获取所需的信息。

如你所见,配置属性在调优方面十分有用,这里说的调优不仅涵盖了自动配置的组件,还包括注入自有应用程序 Bean 的细节。但如果我们想为不同的部署环境配置不同的属性又该怎么办?让我们看看如何使用 Spring 的 Profile 来设置特定环境的配置。

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。