十分钟让你跨入 Spring Boot 的大门

Spring Boot ComponentScan

之前学习的IOC 工作模式,知道 Spring 框架通过解析属性的注解,自动把所需要的 Bean 实例注入到属性中。

那么请看下面的演示,自动注入是否可以正确运行呢?

程序运行就出错了。出错提示:

Field subjectService in fm.douban.app.control.SongListControl required a bean of type 'fm.douban.service.SubjectService' that could not be found.

的意思是,找不到需要注入的 bean : fm.douban.service.SubjectService,导致启动失败。这是为什么呢?

前面经常出现的、加了 @SpringBootApplication 注解的类是启动类,是整个系统的启动入口。

本演示中 fm.douban.app.AppApplication 类是启动类。而 Spring Boot 框架就会默认扫描 fm.douban.app 包(启动类所在的包)及其所有子包(fm.douban.app.*fm.douban.app.*.*)进行解析。

fm.douban.servicefm.douban.service.impl 不是 fm.douban.app 的子包(平级),所以不会自动扫描,也不会自动实例化 Bean,自然不会实例化 SongServiceImpl。所以报错了。

务必记住这个规则和机制,在实际开发工作中排查错误会用得到。

解决办法

当然,Spring 框架有机制解决这个问题。

启动类注解 @SpringBootApplication 加一个参数,告知系统需要额外扫描的包:

@SpringBootApplication(scanBasePackages={"fm.douban.app", "fm.douban.service"})
public class AppApplication {
  public static void main(String[] args) {
    SpringApplication.run(AppApplication.class, args);
  }
}
  • 参数名是:scanBasePackages
  • 参数值是一个字符串数组,用于指定多个需要额外自动扫描的包。需要把所有的待扫描的包的前缀都写入。
另一种写法

如果不是 Spring Boot 启动类,可以使用独立的注解 @ComponentScan ,作用也是一样的,用于指定多个需要额外自动扫描的包。这个知识点不太常用,但是也需要记住,未来有可能会用到的。

@ComponentScan({"fm.service", "fm.app"})
public class SpringConfiguration {
  ... ...
}
额外知识点

演示过程中,为了简单起见,本节课的 control 类用的不是 @Controller 而是 @RestController

使用 @RestController 的类,所有方法都不会渲染 Thymeleaf 页面,而是都返回数据。等同于使用 @Controller 的类的方法上添加 @ResponseBody 注解,效果是一样的。但要**注意:**两个注解的包是不一样的哦:

org.springframework.stereotype.Controller

org.springframework.web.bind.annotation.RestController

Spring Boot Logger 运用

SongListControl 实现了一个初始化的方法 init() ,加上了 @PostConstruct 注解,在类初始化后检查依赖的 SubjectService 是否成功注入。

不熟悉 @PostConstruct 注解的,需要复习第 2 章第 6 节。

@PostConstruct
public void init(){
  System.out.println("SongListControl 启动啦");
  if (subjectService != null) {
    System.out.println("subjectService 实例注入成功。");
  } else {
    System.out.println("subjectService 实例注入失败。");
  }
}

但实际上,系统启动成功后,console 并未看到成功的提示。这是为什么呢?

因为在 Spring 这种比较复杂的系统中,System.out.println() 打印的内容会输出到什么地方,是不确定的。所以在企业级的项目中,都用日志系统来记录信息。

日志系统的两大优势:

  1. 日志系统可以轻松的控制日志是否输出。例如淘宝这样超大型的网站,在开发阶段需要打印出调试信息,但是发布到正式环境就不能打印了,因为每天几十几百亿的访问量,大量调试信息到导致磁盘撑爆。这时候就需要控制日志的输出,而不是修改所有的代码。
  2. 日志系统可以灵活的配置日志的细节,例如输出格式,通常在日志输出时,需要自动附带输出日志发生的时间、打印日志的类名等信息,这样能很方便的观察日志分析问题。
步骤

使用日志系统,有两大步骤:

1. 配置

修改 Spring Boot 系统的标准配置文件: application.properties(在项目的 src/main/resources/ 目录下),增加日志级别配置:

logging.level.root=info

表示所有日志(root)都为 info 级别。

我们也可以为不同的的包定义不同的级别,例如

logging.level.fm.douban.app=info

就表示 fm.douban.app 包及其子包中的所有的类都输出 info 级别的日志。

常用的日志级别(优先级),请看下列表格:

优先级级别含义和作用
最高ERROR错误信息日志
WARN暂时不出错但高风险的警告信息日志
INFO一般的提示语、普通数据等不紧要的信息日志
DEBUG进开发阶段需要关注的调试信息日志
级别的作用

logging.level.root=error 意味着 不输出 更低 优先级的 WARN、INFO、DEBUG 日志, 只输出 ERROR 日志。

logging.level.root=warn 意味着 不输出 更低 优先级的 INFO、DEBUG 日志, 只输出 WARN 和 更高 优先级的 ERROR 日志。以此类推。

在开发阶段配置为 DEBUG,在项目发布时调整为 INFO 或更高级别,即可做到不改代码而控制只输出关心的日志。

日志级别是重要的概念,请务必理解每个级别的作用

2. 编码

配置完成后,编码很简单,只需要实例化日志对象即可打印日志了。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;

@RestController
public class SongListControl {
    private static final Logger LOG = LoggerFactory.getLogger(SongListControl.class);

    @PostConstruct
    public void init(){
        LOG.info("SongListControl 启动啦");
    }
}

先定义一个类变量 LOG,然后在 LOG.info() 方法的参数中输入日志内容。

注意这里的方法名(info())与日志级别一一对应:

优先级级别方法名
最高ERRORerror()
WARNwarn()
INFOinfo()
DEBUGdebug()

如果想输出警告信息就调用 LOG.warn() 方法,以此类推。

日志按级别输出

配置为 logging.level.root=error 时, warn()info()debug() 三个方法是无效的,都不会在 Console 打印日志内容( 不会报错 哦),只有 error() 可以。

当修改配置为 logging.level.root=warn 后,warn() 自动变的有效,也可以打印日志内容了(高级别 error() 本来就有效),info()debug() 仍然不行。

这样,就可以通过修改 一个配置 ,并 不修改 每行日志打印代码,即可方便的 调节 日志输出的内容。

自己思考并体会配置的作用

代码演示

定义类变量 LOG 的语句属于固定写法,只需要在不同的类中,修改 getLogger() 方法参数为当前的类名即可。这样就能识别每段日志的来源。

修饰为 static final 的目的就是尽量复用,一个类无论多少个实例只需要一个日志对象。日志对象一旦定义也不需要修改。

Spring Boot Properties

我们之前已经学习了使用https://start.spring.io/创建Spring Boot 的工程。创建完毕即可运行,这是因为 Spring Boot 框架中已经免除了大部分手动配置。

但是对于一些特定的情况,还是需要我们进行手动配置的,框架为我们提供了 application.properties 配置文件,让我们可以进行自定义配置,来对默认的配置进行修改,以适应具体的情况。

在上一节中,我们已经使用过 application.properties 配置文件了。这是一个固定位置(在项目的 src/main/resources/ 目录下)、固定名字的文件,框架会自动加载解析这个配置文件,使用起来十分方便。

配置文件格式

application.properties 配置文件的格式也很简单。每一行是一条配置项:配置项名称=配置项值

logging.level.root=info
logging.level.fm.douban.app=info

注意:等号两边不要加空格,要写紧凑一些。

为了方便阅读和维护,书写配置文件时推荐遵守如下约定:

  • 配置项名称能准确表达作用、含义,以点 . 分割单词,
  • 相同前缀的配置项写在一起,
  • 不同前缀的配置项之间空一行,
配置的意义

配置的主要作用,是把可变的内容从代码中剥离出来,做到在不修改代码的情况下,方便的修改这些可变的或常变的内容。这个过程称之为避免硬编码、做到解耦

怎么判断可变呢?通常跟项目运行相关的上下文环境,比如端口号、路径等可能变化信息,是可变的或常变的内容。但主要还是根据具体的项目经验积累,提前判断。

在经验欠缺的时候,主要是依靠代码重构,当一个值变化的时候,要有敏感度思考是否应该采用配置的方式。

自定义配置项

我们可以在 application.properties 配置文件中加入自定义的配置项。

song.name=God is a girl

框架会 自动加载自动解析 整个文件。

那么代码中怎么使用自定义的配置项呢?实际上很简单:

import org.springframework.beans.factory.annotation.Value;

public class SongListControl {
    @Value("${song.name}")
    private String songName;
}

只需要使用 @Value 注解即可,注意写法,花括号中的配置项名称,与配置文件中保持一致即可。

项目启动的时候,Spring 系统会自动把 application.properties 配置文件中的 song.name 的值,赋值给 SongListControl 对象实例的 songName 变量。

代码中使用配置项,application.properties 文件必须有配置,缺少了就会报错;但 application.properties 文件中的配置没有被代码使用,则没关系。就是说,多了没事,少了就报错

就是由于使用了注解,系统才能自动完成赋值。

没有注解,或者没有配置项,或者注解中的配置项名称写错了,系统都不能完成自动赋值的步骤。

从浏览器请求 /songlist 的结果看,可以正确取得配置项的结果了。

  • 15
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值