Spring Boot Features(Part I)

2017-08-05
Spring Framework

在spring boot官方文档中介绍了不少feature,我觉得有必要整理一下,还是挺有意思的。

文档中根据功能作出了以下区分:

  • Core Feature: SpringApplication(Spring应用)| Externalized Configuration(外部配置)| Profiles(轮廓)| Logging(日志)
  • Web Applications: MVC(不解释)| Embedded Containers(内嵌的容器)
  • Working with data:SQL | NO-SQL
  • Messaging: JMS(Java消息服务)
  • Testing: Boot Applications | Utils (测试相关)
  • Extending: Auto-configuration | @Conditions (拓展)

以上的feature用中文翻译起来很不容易理解,这些专业术语用英文表示更让人觉得熟悉。现在就针对某个feature单独去理解。

SpringApplication

这个特性可以说是spring boot的创新。在以往的web程序开发过程中,我们通常将自己的程序部署在tomcat或者weblogic类似的中间件上的,但使用spring boot不需要依耐这样的中间件。使用spring boot启动你的应用就像启动一个简单的程序一样,类似我们刚接触Java写的一个个的main函数一样。它只需要这样的代码就能启动一个web应用了:

1
2
3
public static void main(String[] args) {
SpringApplication.run(MySpringConfiguration.class, args);
}

正常启动肯定是没什么可讲的,问题是出了问题就很难受了。没关系,spring boot同样为开发者提供了很好的解决方案。spring boot提供FailureAnalyzers帮助你修复问题。比方说,假如你在8080端口上启动web应用,然而这个端口被占用了,这时候控制台会报出这样的错误信息:

1
2
3
4
5
6
7
8
9
10
11
***************************
APPLICATION FAILED TO START
***************************
Description:
Embedded servlet container failed to start. Port 8080 was already in use.
Action:
Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.

通过控制台打印出的错误信息,很容易知道问题出哪里了(英文水平不要太low)。当然这是使用默认的FailureAnalyzers,spring boot 也提供自己定义的FailureAnalyzers。在META-INF/spring.factories这个文件中有这样的定义:

1
2
3
4
5
6
7
8
9
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer

发现了这个PortInUseFailureAnalyzer类吗?这个就是处理端口绑定失败的Analyzer。我们如果要自己定义一个这样的Analyzer很简单。创建一个类,继承AbstractFailureAnalyzer,其中的泛型可以是任何异常。重写其analyze方法,根据传入的参数进行处理,如果没办法处理那就返回一个null,这时候将由下一个Analyzer去处理。如果可以处理,直接返回一个FailureAnalysis对象即可。这个对象和Execption很类似,无非就是一些错误信息罢了。这个是我定义的一个Analyzer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SimpleFailureAnalyzer extends AbstractFailureAnalyzer<NullPointerException> {
public SimpleFailureAnalyzer() {
super();
System.out.println("我是自定义的FailureAnalyzer,我开始搞事情了!");
}
@Override
protected FailureAnalysis analyze(Throwable rootFailure, NullPointerException cause) {
System.out.println("正在搞事情。。。");
System.out.println("获取这个异常:");
String message = cause.getMessage();
System.out.println(message);
System.out.println("事情搞完了,但是不想处理");
return null;
}
}

定义这个类还没结束,还要创建一个META-INF/spring.factories文件,内容如下:

1
2
org.springframework.boot.diagnostics.FailureAnalyzer=\
com.dw.simpledemo.failureanalyzer.SimpleFailureAnalyzer

除了这种方式诊断问题之外,还可以这样去发现问题。可以设置日志的级别:

1
2
3
4
5
logging:
level:
root: WARN
org.springframework: DEBUG
org.hibernate: ERROR

假如你使用的是Java命令行的方式去启动你的app可以在命令后加这样的参数java -jar myproject-0.0.1-SNAPSHOT.jar --debug,以debug的方式去启动。(我相信这种方式会让你的控制台爆炸,满屏都是日志,2333333。)
启动失败的问题分析我觉得不算特别有创新的feature,毕竟一般情况下通过报错也能找到问题出哪里了。

这个feature我觉得算是一个比较有意思的,虽然我觉得没什么屌用,只能算个彩蛋吧23333.
在我们点击run的时候控制台会打印一个ASCII的字符画。开始以为这个是spring boot自带的,其实这个也可以自定义的。
在类路径下创建一个banner.txt文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
${AnsiColor.BRIGHT_YELLOW}
////////////////////////////////////////////////////////////////////
// _ooOoo_ //
// o8888888o //
// 88" . "88 //
// (| ^_^ |) //
// O\ = /O //
// ____/`---'\____ //
// .' \\| |// `. //
// / \\||| : |||// \ //
// / _||||| -:- |||||- \ //
// | | \\\ - /// | | //
// | \_| ''\---/'' | | //
// \ .-\__ `-` ___/-. / //
// ___`. .' /--.--\ `. . ___ //
// ."" '< `.___\_<|>_/___.' >'"". //
// | | : `- \`.;`\ _ /`;.`/ - ` : | | //
// \ \ `-. \_ __\ /__ _/ .-` / / //
// ========`-.____`-.___\_____/___.-`____.-'======== //
// `=---=' //
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
// 佛祖保佑 永不宕机 永无BUG //
////////////////////////////////////////////////////////////////////

这样你的app在启动的时候就会打印这样的字符画了。个人觉得除了装b没有任何卵用。自己去定义这些字符画的生成程序也是可以的,org.springframework.boot.Banner这个接口就是定义怎么去实现banner的打印的,具体怎么处理可以看看它的实现类。闲的没事可以去折腾这些玩意儿!当然不想看到这些东西也是可以的。在yml文件中这样配置一下就完事儿了:

1
2
3
spring:
main:
banner-mode: "off"

同样的配置写成application.properties也是OK的。
然鹅,硬编码也是可以的:

1
2
3
4
5
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MySpringConfiguration.class);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);
}

接下来的这个feature就比较实用了。我们用的是spring boot当然少不了核心的spring的特性呀,毕竟都姓spring嘛2333!

我们有时候需要在spring容器启动的时候搞一些事情,spring framework也提供了这样的一种机制去帮助我们完成。它定义了很多事件来抽象出这些操作,比如说ContextRefreshedEvent这个事件就是当容器初始化或者被刷新了会触发。

我们注册一个bean可以通过@Bean去完成,bean的注册需要依赖容器被创建,但是创建之前的一些操作我们没办法通过@Bean来完成。spring boot中可以这样去设置SpringApplication.addListeners(…​)或者SpringApplicationBuilder.listeners(…​)去添加你关心的事件.当然这种方式是使用硬编码完成的,不够敞亮不够spring。可以在META-INF/spring.factories文件中去指定。这是默认配置:

1
2
3
4
5
6
7
8
9
10
11
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener

同样,自己去按照这样的写法去整一个自己定义的listener也是ok的,这很spring!文档中给了一段话:我们通常很少去用这个application event,但是我们知道他们存在是很有必要的。spring boot 通过他们完成很多任务。其实在做技术的过程中,也是这个道理,很多时候有些东西我们不常用,但是我们必须知道有这么个东西,说不定在什么时候就派上用场了。

这个feature我觉得没太多用处,毕竟我目前接触到spring boot不是很深入。spring boot启动入口是通过main函数,其中有String[] args作为参数传进来。spring boot提供ApplicationArguments接口让我们直接获取传入的参数。

1
2
3
4
5
6
7
8
9
@Component
public class MyBean {
@Autowired
public MyBean(ApplicationArguments args) {
boolean debug = args.containsOption("debug");
List<String> files = args.getNonOptionArgs();
// if run with "--debug logfile.txt" debug=true, files=["logfile.txt"]
}
}

个人觉得使用这种方式很low,将参数直接写到配置文件中去,让app启动时候去读配置不完了?

如果你需要在SpringApplication启动前去做点别的事情,spring boot同样能够实现。

1
2
3
4
5
6
7
8
@Component
public class MyBean implements CommandLineRunner {
public void run(String... args) {
// Do something...
}
}

实现ApplicationRunner接口同样也能达到效果。这个接口中的run方法参数就是上面提到的ApplicationArguments用来获取启动参数的。区别就在于一个只能获取String数组类型的参数,一个能获取ApplicationArguments类型的参数。

至此,关于Core Features: SpringApplication 的内容都梳理完了。下一个feature要讲的是Core Features: External Configuration。

Externalized Configuration

说实话,这个词中文翻译真的很难表达它想传达出的意思。作为名词的直译就是外部配置,作为动词翻译过来就是外部化配置。真的让人摸不到头脑。

仔细去读读内容,其实也就是一个关键词:配置。通过配置,可以让你的应用在不同环境中跑同一套代码。可以使用properties文件、YAML文件、environment变量、及命令行参数去使你的参数生效。配置参数可以通过使用@Value注解直接注入到你的bean中。spring boot中的配置属性是遵循一个优先级的:

  1. 全局配置(当devtools生效的时候 ~/.spring-boot-devtools.properties文件作为全局配置文件)。
  2. @TestPropertySource注解配置在你的测试用例中。
  3. @SpringBootTest#properties注解属性值被设置在你的测试用例中。
  4. 命令行参数。
  5. environment variable(启动时候可以设置的参数,类似于program arguments)或者系统参数。
  6. ServletConfig初始化参数。
  7. ServletContext初始化参数。
  8. java:comp/envJNDI参数。
  9. Java System properties(System.getProperties()).
  10. OS environment variables.
  11. A RandomValuePropertySource that only has properties in random.*.(啥啥啥,说啥呢)
  12. jar包外的application-{profile}.properties配置文件或者YAML文件。
  13. jar包内的…(同上)。
  14. jar包外的application.properties文件或者YAML文件。
  15. jar包内的…(同上)。
  16. @Configuration注解过的类上的@PropertySource属性(貌似读不通)。
  17. 默认属性。
    一个栗子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Component
    public class MyBean {
    @Value("${name}")
    private String name;
    // ...
    }

application.properties中有name这个属性就会被自动赋值给name字段。java -jar app.jar --name="Spring"这种方式name就是从命令行中去读取。

我们的配置文件除了application.properties还有这种形式的:application-{profile}.properties这个profile决定你代码运行的环境。可以是dev、prod等等。具体去加载哪个配置文件取决于spring.profiles.active这个属性到底是什么。与之对应即可。如果有多个application-{profile}.properties文件出现,最后出现的生效。

spring推荐使用YMAL作为配置文件。它是JSON的超集。spring framework提供了2个方便的类加载YMAL文件:YamlPropertiesFactoryBeanYamlMapFactoryBean。前者加载成一个Properties,后者加载成一个Map

1
2
3
4
my:
servers:
- dev.bar.com
- foo.bar.com

1
2
3
4
5
6
7
8
9
@ConfigurationProperties(prefix="my")
public class Config {
private List<String> servers = new ArrayList<String>();
public List<String> getServers() {
return this.servers;
}
}

看上去有种很酸爽的感觉。

在一个YMAL文件中也可以指定不同的profile的。

1
2
3
4
5
6
7
8
9
10
11
12
server:
address: 192.168.1.100
---
spring:
profiles: development
server:
address: 127.0.0.1
---
spring:
profiles: production
server:
address: 192.168.1.120

server.address属性在不同的profile中是不同的。development被激活了,就使用127.0.0.1.其他类似。都没激活就使用默认的192.168.1.100.
然而这种方式有缺点,不能通过@PropertySource注解直接注入到bean属性中。

spring针对@Value("${property}")这种方式去注入属性提供一个替换。那就是使用@ConfigurationProperties("foo")来放到你要注入的类中。字段名对应即可,即使有那种继承结构也没关系。YMAL本身对这种继承结构支持的很好。

关于Externalized Configuration的梳理也差不多了。各种炫酷屌炸天。

剩下的在以后几篇文章中继续整理。越来越有趣了。


留言: