3.2.2 应用程序 Bean 的配置外置
假设我们在某人的阅读列表里不止想要展示图书标题,还要提供该书的 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 注入了 AmazonProperties
的 ReadingListController
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 来设置特定环境的配置。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论