Spring06:使用配置属性详解(application.yml或application.properties)
1 引言
https://www.cnblogs.com/fancy2022/p/16789970.html 在上一篇中,我们使用spring security为Taco Cloud应用完善了基本安全性功能;在前几篇内容中,我们也一直在使用Spring不断构建和完善该应用,包括主页展示、订单、用户注册,但是,你也许注意到了在过程中 我们没有像Javaweb阶段一样去配置一个类似applicationContext.properties的配置文件,而在示例代码中,有两个空的配置文件(如下图)-application.yml、application.properties,接下来我们将深入了解它。
【本文内容】
-
回顾Javaweb阶段的xml配置文件,以及相关实现原理
-
了解并创建spring的配置文件
-
了解spring profile的作用和使用方法
2 回顾Javaweb阶段的配置文件
① IOC和DI
在回顾Javaweb阶段的配置文件applicationContext.xml之前,我们需要知道一个重要概念——IOC:控制反转,我认为他是Spring最核心的概念。(IOC 就是一种反转控制的思想, 而 DI 是对 IOC 的一种具体实现)
IOC
IOC—Inversion of Control,即“控制反转”,是一种设计思想。
-
之前在Servlet中,我们创建service对象 , FruitService fruitService = new FruitServiceImpl(); 这句话如果出现在servlet中的某个方法内部,那么这个fruitService的作用域(生命周期)应该就是这个方法级别; 如果这句话出现在servlet的类中,也就是说fruitService是一个成员变量,那么这个fruitService的作用域(生命周期)应该就是这个servlet实例级别
-
之后我们在applicationContext.xml中定义了这个fruitService。然后通过解析XML,产生fruitService实例,存放在beanMap中,这个beanMap在一个BeanFactory中 因此,我们转移(改变)了之前的service实例、dao实例等等他们的生命周期。控制权从程序员转移到BeanFactory。这个现象我们称之为控制反转
在软件系统中,层与层之间是存在依赖的。我们也称之为耦合。 我们系统架构或者是设计的一个原则是: 高内聚低耦合。 层内部的组成应该是高度聚合的,而层与层之间的关系应该是低耦合的,最理想的情况0耦合(就是没有耦合)
public class FruitServiceImpl implements FruitService {
private FruitDAO fruitDAO = new FruitDAOImpl();
public class FruitController extends ViewBaseServlet {
private FruitService fruitService = new FruitServiceImpl();
所谓的解耦,不是说完全没关系,而是在代码层面解除锁定,打个比方就是用胶水黏在一起,还是像乐高那样拼接,以上代码可以看出我们目前的系统各层之前耦合度较高
DI
DI-Dependency Injection,即依赖注入
DI 是 IOC 的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。
在Javaweb阶段,我们实现IOC的步骤是:
①修改配置文件applicationContext
②添加BeanFactory接口
③添加ClassPathXmlApplicationContext实现BeanFactory
④改原先在DispatcherServlet的init方法中的获取对应Controller的方法体
⑤修改中央控制器DispatcherServlet中的代码
⑥去掉FruitServiceImpl和FruitController中的耦合
public class FruitController { private FruitService fruitService = null;}
② 基于xml管理bean
根据ApplicationContext.xml配置文件中配置的bean节点和property属性(如下图)
将bean实例对象保存到map容器中,
我们转移(改变)了之前的service实例、dao实例等等他们的生命周期。控制权从程序员转移到BeanFactory。
各property属性对应的都按配置文件中的关系实例化了,并保存在beanMap中;
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE beans [
<!ELEMENT beans (bean*)>
<!ELEMENT bean (property*)>
<!ELEMENT property (#PCDATA)>
<!ATTLIST bean id ID #REQUIRED>
<!ATTLIST bean class CDATA #IMPLIED>
<!ATTLIST property name CDATA #IMPLIED>
<!ATTLIST property ref IDREF #IMPLIED>
]>
<beans>
<bean id="page" class="com.atguigu.myssm.myspringmvc.PageController"/>
<!-- DAO -->
<bean id="userDAO" class="com.brovovax.attdesk.dao.impl.UserDAOImpl"/>
<bean id="consumableDAO" class="com.brovovax.attdesk.dao.impl.ConsumableDAOImpl"/>
<bean id="listItemDAO" class="com.brovovax.attdesk.dao.impl.ListItemDAOImpl"/>
<bean id="ioOrderDAO" class="com.brovovax.attdesk.dao.impl.IoOrderDAOImpl"/>
<bean id="ioOrderItemDAO" class="com.brovovax.attdesk.dao.impl.IoOrderItemDAOImpl"/>
<!-- service -->
<bean id="userService" class="com.brovovax.attdesk.service.impl.UserServiceImpl">
<property name="userDAO" ref="userDAO"/>
</bean>
<bean id="consumableService" class="com.brovovax.attdesk.service.impl.ConsumableServiceImpl">
<property name="consumableDAO" ref="consumableDAO"/>
</bean>
<bean id="listItemService" class="com.brovovax.attdesk.service.impl.ListItemServiceImpl">
<property name="listItemDAO" ref="listItemDAO"/>
<property name="consumableService" ref="consumableService"/>
</bean>
<bean id="ioOrderService" class="com.brovovax.attdesk.service.impl.IoOrderServiceImpl">
<property name="ioOrderDAO" ref="ioOrderDAO"/>
<property name="ioOrderItemDAO" ref="ioOrderItemDAO"/>
<property name="listItemService" ref="listItemService"/>
<property name="consumableService" ref="consumableService"/>
</bean>
<!-- controller -->
<bean id="user" class="com.brovovax.attdesk.controller.UserController">
<property name="userService" ref="userService"/>
<property name="listItemService" ref="listItemService"/>
</bean>
<bean id="consumable" class="com.brovovax.attdesk.controller.ConsumableController">
<property name="consumableService" ref="consumableService"/>
</bean>
<bean id="inventory" class="com.brovovax.attdesk.controller.InventoryController">
<property name="listItemService" ref="listItemService"/>
</bean>
<bean id="order" class="com.brovovax.attdesk.controller.IoOrderController">
<property name="ioOrderService" ref="ioOrderService"/>
</bean>
</beans>
以上就是我们Javaweb阶段的配置文件applicationContext.xml的相关原理,温故而知新,接下来让我们开始了解Spring阶段的配置文件。
3 Spring阶段的配置文件
①基于注解管理bean
经过之前的学习,你已经发现我们现在不需要配置applicationContext.xml了,而是使用了注解,极大简化了开发。
和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。
Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后续操作。
②bean的属性注入
在Javaweb阶段,我们为bean设置属性是通过xml的 <property/>标签进行
在spring中,我们通过@configuration、@Bean注解将方法的返回值注入到spring容器,给bean的属性赋值
@Configuration//声明一个类是一个java配置类, 相当于一个xml配置文件
@EnableConfigurationProperties(JdbcProperties.class)
public class JdbcConfiguration {
// @Autowired
private JdbcProperties jdbcProperties;
public JdbcConfiguration(JdbcProperties jdbcProperties) {
this.jdbcProperties = jdbcProperties;
}
@Bean//把方法的返回值注入到spring 容器
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(this.jdbcProperties.getDriverClassName());
dataSource.setUrl(this.jdbcProperties.getUrl());
dataSource.setUsername(this.jdbcProperties.getUsername());
dataSource.setPassword(this.jdbcProperties.getPassword());
return dataSource;
}
}
以上方式无论是使用xml文件还是再构造一个JdbcProperties对象,通过.@Autowired和@Bean注入属性来获取一个数据库的连接的方式,都存在扩展性的问题,能否和配置bean一样,只配置一个xml文件就可以帮我们实现属性注入呢?借助配置文件和Spring Boot的自动配置就可以实现。
③spring的各种配置属性
在了解配置文件前,我们需要知道Spring有哪些属性源(如下图)
Spring Boot自动配置的bean都可以通过Spring环境提取的属性进行配置。
4 application.yml或application.properties详解
4.1 配置文件的内容分析
这里以application.yml文件来列举项目src/main/resources/application.yml的配置文件(YAML 是一种用于指定分层配置数据的便捷格式)
①数据库
spring:
datasource:
url: jdbc:mysql://localhost:3306/DatebaseName
username: root
password: 123456
driverClassName: com.mysql.jdbc.Driver
②服务器
# 服务器配置
server:
port: 8082
③日志
日志级别、日志文件路径
loggingh:
root: WARN
org:
springframwork:
security: DEBUG
更多配置请见官方文档:https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/html/appendix-application-properties.html#core-properties 在之后的Spring系列文章中还会有更多配置,这里仅作为部分示例。
4.2 创建自定义的配置文件
第3节中已经说明,我们创建配置文件是为了给bean注入属性,那么具体是如何应用的呢?
①订单OrderController
@Controller
@RequestMapping("/orders")
@SessionAttributes("order")
public class OrderController {
private OrderRepository orderRepo;
private OrderProps props;//针对OrderController中德pageSize属性单独抽取的类
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";
}
}
其中的ordersForUser给用户展示了订单列表,一般情况下我们肯定不会一次性展示全部的订单信息,所以我们需要设置一个PageSize来控制一次请求所返回的订单数,这时就需要用到配置文件了。
②配置类
@Component
@ConfigurationProperties(prefix="taco.orders")
@Data
// end::notValidated[]
@Validated
//tag::notValidated[]
public class OrderProps {
//end::notValidated[]
@Min(value=5, message="must be between 5 and 25")
@Max(value=25, message="must be between 5 and 25")
//tag::notValidated[]
private int pageSize = 20;
}
此配置类只有1个属性pageSize,默认一次展示20页
@ConfigurationProperties(prefix="taco.orders")表示它是一个配置类并且引用了配置文件中的taco.orders.pageSize属性
@Component表示Spring会将这个类扫描到,创建为Spring应用上下文中的bean
@Validation是Java定义的一套基于注解的数据校验规范
@Null 限制只能为null
@NotNull 限制必须不为null
@NotEmpty 只作用于字符串类型,字符串不为空,并且长度不为0
@NotBlank 只作用于字符串类型,字符串不为空,并且trim()后不为空串
@AssertFalse 限制必须为false
@AssertTrue 限制必须为true
@DecimalMax(value) 限制必须为一个不大于指定值的数字
@DecimalMin(value) 限制必须为一个不小于指定值的数字
@Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位
数不能超过fraction
@Future 限制必须是一个将来的日期
@Past 验证注解的元素值(日期类型)比当前时间早
@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@Pattern(value) 限制必须符合指定的正则表达式
@Size(max,min) 限制字符长度必须在min到max之间
@Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
注意:
@NotNull 适用于任何类型被注解的元素必须不能与NULL
@NotEmpty 适用于String Map或者数组不能为Null且长度必须大于0
@NotBlank 只能用于String上面 不能为null,调用trim()后,长度必须大于0
这里用到了@Max和@Min,表示限制了pageSize的最大值和最小值
③自定义的application.yml
taco:
orders:
pageSize: 10
这样我们就可以灵活地对pageSize进行设置,而非硬编码的方式。
4.3 创建配置文件遇到的问题
本节介绍创建配置文件时可能遇到的问题
①鼠标悬停在配置文件Unknown property ‘taco’
原因:创建配置属性缺少元数据
解决方法:在src/main/resources/META-INF下配置属性的说明
additional-spring-configuration-metadata.json
{"properties": [
{
"name": "taco.orders.page-size",
"type": "java.lang.String",
"description": "Sets the maximum number of orders to display in a list."
},
{
"name": "taco.discount.codes",
"type": "java.util.Map<String, Integer>",
"description": "A map of discount codes to a discount percentage."
}
]}
效果:
在src/main/resources/META-INF/additional-spring-configuration-metadata.json添加
{
"name": "taco.orders.pageSize",
"type": "java.lang.String",
"description": "Description Test."
}
若无法显示,请在pop.xml中添加依赖
(参考https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/html/configuration-metadata.html#configuration-metadata-annotation-processor)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>