(七)Spring Boot配置篇 之 应用程序 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) {
        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);
        }
        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自动配置后面的全部配置类都已经加上了@EnableConfigura
tionProperties注解。因此,除非你完全不使用自动配置(那怎么可能?),否则就无需显式地添加@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")
public class AmazonProperties {
    private String associateId;

    public void setAssociateId(String associateId) {
        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) {
        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());
        }
        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来设置特定环境的配置。

posted @ 2019-11-23 20:42  跃小云  阅读(193)  评论(0编辑  收藏  举报