标签:执行顺序 group 缺点 去除 reg 开始 jndi 优缺点 嵌入
扫码关注公众号,领取更多资源
@
Spring Boot
是由 Pivotal
团队提供用来简化 Spring
的搭建和开发过程的全新框架。随着近些年来微服务技术的流行,Spring Boot
也成了时下炙手可热的热点技术。
Spring Boot
去除了大量的 xml
配置文件,简化了复杂的依赖管理,配合各种 starter
使用,基本上可以做到自动化配置。Spring
可以做的事情,现在用 Spring boot
都可以做。
过去使用Spring
创建一个可运行的项目需要进行大量的配置工作,很是繁琐,SpringBoot
使用习惯优于配置
的理念,内置一个默认配置,无需手动,可以让项目快速运行起来。
Spring Boot
可以以jar包的形式独立运行,运行一个Spring Boot
项目只需要通过java -jar xx.jar
即可。Spring Boot
可以选择内嵌Tomcat
,Jetty
或者Undertow
,这样就可以无需使用war
包形式部署项目。Spring Boot
提供一系列的starter pom
来简化Maven依赖添加。Spring Boot
可以根据类路径中的jar包,类,自动配置Bean,减少了配置工作量。Spring Boot
提供http
,ssh
,telnet
对项目进行实时监控。Spring Boot
通过注解实现自动配置
,可以无需任何xml就能完成所有配置。 打开http://start.spring.io/
,选择Java语言开发,填写项目信息,然后下载代码。如下图:
解压后可以得到一个Spring Boot
的启动类,代码如下:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
直接选择当前启动类选择运行,一个简单的Spring Boot
项目就启动成功了。
@SpringBootApplication
是一个复合的注解,他是如下几个注解组合而成:
在启动类上加上如上三个注解可以达到与@SpringBootApplication
一样的效果,显然后者更加方便些。
这里的就是Spring IOC
中使用的那个配置。既然Spring Boot
本质上就是一个Spring
应用,那么自然也需要加载某个IOC
容器配置,所以加上@Configuration
,本质上也是IOC
容器的配置类。
所以上面的启动类也可以拆分为如下两个类:
//配置类
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class DemoConfiguration {
@Bean
public Controller controller() {
return new Controller();
}
}
//启动类
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoConfiguration.class, args);
}
}
在如上两个类中,DemoApplication
其实就是一个普通的启动类,而DemoConfiguration
就是一个普通的配置类而已。
@EnableAutoConfiguration
也是一个复合的注解,主要包含如下两个:
其中最关键的是@Import(EnableAutoConfigurationImportSelector.class)
,他借助EnableAutoConfigurationImportSelector
这个类,将当前应用所有符合条件的@Configuration
配置都加载到当前Spring Boot
应用的IOC
容器中去。
SpringApplication
在没有特殊要求的情况下,默认使用模板化的情动流程,但是SpringApplication
也在合适的流程节点开放了不用类型的扩展,我们可以对这些扩展点对SpringBoot
的启动和关闭流程进行修改扩展。
在之前的启动类的main
方法中只有这么简单的依据话:
SpringApplication.run(DemoApplication.class,args);
正常情况下,一个SpringBoot
项目启动后会在打印台或者日志中打印一个字符画。
但是我们可以在SpringApplication
中来修改打印的内容,对启动流程进行扩展:
public class DemoApplication {
public static void main(String[] args) { //SpringApplication.run(DemoConfiguration.class, args);
SpringApplication bootstrap = new SpringApplication(Demo - Configuration.class);
bootstrap.setBanner(new Banner() {
@Override
public void printBanner(Environment environment, Class<?> aClass, PrintStream printStream) {
// 比如打印一个我们喜欢的ASCII Arts字符画
}
});
bootstrap.setBannerMode(Banner.Mode.CONSOLE);
// 其他定制设置...
bootstrap.run(args);
}
}
修改banner
还有一个简单的方式,就是把需要打印的字符放到一个资源文件中,然后通过ResourceBanner
加载:
bootstrap.setBanner(new ResourceBanner(new ClassPathResource("banner.txt")));
正常情况下我们不需要对这个设置进行定制,因为真正的启动逻辑才是我们需要关注的,这只是为了好玩。
SpringApplication
中的run()
方法主要的流程大体可以概括为如下几个步骤:
SpringApplication
中的静态run()
,那么,在这个方法中会自动创建一个SpringApplication
的实例对象,然后调用这个实例对象的run()
。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fsJsNZkq-1604679639272)(Boot.assets/屏幕快照 2020-11-03 13.22.30.png)]
在实例化
SpringApplication
的时候,会提前做好如下几件事情
- 根据
classpath
中是否存在ConfigurableWebApplicationContext
来决定创建的ApplicationContext
类型SpringFactoriesLoader
在classpath
中查找并加载所有可用ApplicationContextInitializer
SpringFactoriesLoader
在classpath
中查找并加载所有可用ApplicationListener
- 推断并设置
main
方法的定义
SpringApplication
初始化并完成设置后就开始执行run
方法的逻辑了,在执行之前,需要通过SpringFactoriesLoader
加载所有的SpringApplicationRunListener
,调用他们的started()
,通知这些类开始执行当前应用。SpringBoot
应用需要使用的Environment
,包括配种的PropertySource
和Profile
。SpringApplicationRunListener
的environmentPrepared()
方法ApplicationContext
,并将之前准备好的Environment
设置给创建号的ApplicationContext
使用。SpringFactoriesLoader
,加载之前的ApplicationContextInitializer
,然后调用initialize(applocationContext)
方法对Application
进一步处理。SpringApplicationRunListener
的contextPrepared()
方法@EnableAutoConfiguration
获取的所有配置以及其他形式的IOC
容器配置加载到已经准备完毕的ApplicationContext
中。SpringApplicationRunListener
的contextLoaded()
方法,告诉所有的SpringApplicationRunListener
,ApplicationContext
准备完毕。ApplicationContext
的refresh()
方法。ApplicationContext
中注册的CommandLineRunner
。SpringApplicationRunListener
的finish()
方法。 至此,一个完整的SpringBoot
应用启动完毕。启动的过程很多都是一些通知事件的扩展点,如果忽略这些过程,可以将启动的逻辑精简到如下几步:
对比可以发现,SpringApplication
提供的扩展点占了启动逻辑的很大一部分,除了初始化并准备好ApplicationContext
之外,剩下的大部分工作都是通过扩展点完成的,下面对这些扩展点进行详细的讲解。
SpringApplicationRunListener
是一个在SpringBoot
的main
方法执行过程中接收事件通知的监听者,主要方法如下:
public interface SpringApplicationRunListener {
void started();
void environmentPrepared(ConfigurableEnvironment environment);
void contextPrepared(ConfigurableApplicationContext context);
void contextLoaded(ConfigurableApplicationContext context);
void finished(ConfigurableApplicationContext context, Throwable exception);
}
正常情况下,我们并不需要自己实现一个SpringApplicationRunListener
,SpringBoot
也只是实现了一个EventPublishingRunListener
,用于处理SpringBoot
启动时不同的ApplicationEvent
。
假如我们需要自定义一个SpringApplicationRunListener
,在实现类DemoSpringApplicationRunListener
的构造方法中需要两个参数SpringApplication
和args
,然后可以通过SpringFactoriesLoader
的规定,在classpath
下的META-INF/spring.factories
中添加如下配置:
org.springframework.boot.SpringApplicationRunListener=\com.keevol.springboot.demo.DemoSpringApplicationRunListener
随后SpringApplication
就会在运行的时候调用他了。
SpringApplication
是java
中监听者模式的一种实现方式。如果需要添加自定的SpringListener
,可以有如下两种方式:
SpringApplication.addListeners(...)
或者SpringApplication.setListeners(...)
添加一个或多个自定义的listener
。SpringFactoriesLoader
,在META-INF/spring.factories
中添加如下配置:org.springframework.context.ApplicationListener=
\org.springframework.boot.builder.ParentContextCloserApplicationListener,
\org.springframework.boot.cloudfoundry.VcapApplicationListener,
\org.springframework.boot.context.FileEncodingApplicationListener
ApplicationContextInitializer
的目的就是在ConfigurableApplicationContext
的ApplicationContext
执行refresh()
之前,对ConfigurableApplicationContext
的实例做一些设置或处理。
实现一个自定义的ApplicationContextInitializer
有如下两种方法:
ngApplication.addInitializers(...)
SpringFactoriesLoader
配置org.springframework.context.ApplicationContextInitializer=
\org.springframework.boot.context.ConfigurationWarningsApplication-ContextInitializer,
\org.springframework.boot.context.ContextIdApplicationContextInitia-lizer,
CommandLineRunner
的源码很简单,如下:
public interface CommandLineRunner{
void run(String... args) throws Exception;
}
需要注意的两点:
CommandLineRunner
需要在ApplicationContext
完全初始化之后执行。ApplicationContext
中的CommandLineRunner
都会被执行,无需手动添加。 以上几个扩展点都建议使用@org.springframework.core.annotation.Order
进行注解或者实现org.springframework.core.Ordered
接口,便于对他们的执行顺序进行调整,避免阻塞。
之前在将@EnableAutoConfiguration
的时候说了,这个配置可以借助SpringFactoriesLoader
的帮助,将注解了@Configuration
的Java类汇总并加载到ApplicationContext
中去。
实际上,@EnableAutoConfiguration
还能够对自动配置进行正价细化的配置和控制。
在Spring框架中,可以使用@Conditional
这个注解配合@Configuration
或者@Bean
来设置一个配置或者实例是否生效,生成一个类似于if-else
条件的生成逻辑。
关注公众号回复“Conditional”获取注解相关详解
如果要基于条件配置,只需要在@Conditional
中指定自己的Condition
实现类就好了,如下:
@Conditional({DemoConditional1.class,DemoConditional2.class...})
@Conditional
除了可以注解在类和方法上之外,还可以注解在其他Annotation
实现类上,组成一个符合的注解,SpringBoot
中已经实现了一批,如下:
@ConditionalOnBean
:当容器里有指定的 Bean 的条件下。@ConditionalOnClass
:当类路径下有指定的类的条件下。@ConditionalOnExpression
:基于 SpEL 表达式作为判断条件。@ConditionalOnJava
:基于 JVM 版本作为判断条件。@ConditionalOnJndi
:在 JNDI 存在的条件下查找指定的位置。@ConditionalOnMissingBean
:当容器里没有指定 Bean 的情况下。@ConditionalOnMissingClass
:当类路径下没有指定的类的条件下。@ConditionalOnNotWebApplication
:当前项目不是 Web 项目的条件下。@ConditionalOnResource
:类路径是否有指定的值。@ConditionalOnSingleCandidate
:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选的 Bean。@ConditionalOnWebApplication
:当前项目是 Web 项目的条件下。 除了可以设置基于条件的配置,我们还可以对当前配置或者组件的加载顺序进行调整,以便于在创建加载过程中解决依赖分析和组装的问题。
@AutoConfigureBefore
或者@AutoConfigureAfter
可以配置当前组件或者配置的加载在其他的之前或者之后进行。比如需要某个配置的加载在另外一个之后,可以进行如下配置:
@Configuration
@AutoConfigureAfter(JmxAutoConfiguration.class)
public class AfterMBeanServerReadyConfiguration {
@AutoWired
MBeanServer mBeanServer;
//通过 @Bean 添加必要的 bean 定义
}
之前说过,SpringBoot对开发者的影响主要有以下两点:
SpringBoot提供了很多以Spring-Boot-Starter开头的依赖模块,这些模块有着默认的配置,正常情况下开箱即用,下面对常用的几个模块做一下介绍:
Java中有很多日志工具,比如log4j,log4j2,sel4j等等,在SpringBoot中,我们可以直接引用现成的依赖包,即可达到开箱即用的效果。比如添加如下Maven依赖:
<dependency>
<groupId> org.springframework.boot </groupId>
<artifactId> spring-boot-starter-logging </artifactId>
</dependency>
那么SpringBoot就会自动使用logback作为应用的日志框架,在应用启动的时候,初始化LoggingApplicationListener并使用。
如果我们要对日志系统做响应的设置,可以通过如下几种方式进行配置:
logging:
config: xml_path
初次之外,还有其他的日志框架,可自行选择,比如: spring-boot-starter-log4j和 spring-boot-starter-log4j2等等,在实际项目中只需要引用一个即可。
通常情况下,我们开发的项目都是一个web项目,需要使用相应的容器来部署。在SpringBoot的maven依赖中添加下面的配置,就可以得到一个可直接运行的web应用。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
在引入这个依赖之后,启动当前项目,会自动嵌入一个默认的tomcat容器,当前服务就运行在这个容器之中。
spring-boot-starter-web默认将当前项目打包成jar包,在运行的时候可以直接使用java -jar xxx.jar来运行,然后访问响应的请求地址就可以得到响应的response。
如果项目中需要使用到数据库,那么添加如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
正常情况下我们在一个服务中只需要使用一个数据库,那么这种情况下,只需要在application.yml中添加如下配置即可:
spring:
datasource:
url: jdbc:mysql://host:port/databaseName
username: username
password: password
某些特殊情况下,如果一个服务中需要使用到多个DataSource,那么只需要配置如下实例就好:
@Bean
public DataSource dataSource1() throws Throwable {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(...);
dataSource.setUsername(...);
dataSource.setPassword(...);
// TODO other settings if necessary in the future.
return dataSource;
}
@Bean
public DataSource dataSource2() throws Throwable {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(...);
dataSource.setUsername(...);
dataSource.setPassword(...);
// TODO other settings if necessary in the future.
return dataSource;
}
但是在添加完上面的代码之后,直接启动会报错,我们需要在启动类上排除SpringBoot在启动的时候对默认的DataSource的加载。
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class })
或者在上面配置多个的DataSource上加上@Primary,可同时达到默认的DataSource和定制化的多个DataSource同时加载的两全其美的效果。
除此之外,还有很多适配不同数据库的依赖,spring-boot-starter-data-jpa、spring-boot-starter-data-mongodb 等,在实际项目中可以根据不同的数据库自行选择。
SpringBoot还有很多其他的maven依赖,比如:
SpringBoot微服务部署到响应的环境之后需要对外提供服务,通常情况下,微服务很少单兵作战,而是同时部署多个节点集群部署。
在这种情况下,如果访问者发出一个请求,这个请求具体访问哪一个节点,如何才能找到对应的节点,这个过程就是服务的发现,而一个服务需要作为一个主题对外提供服务,这个构件主体的过程就成为服务的注册过程。
服务的注册和发现在结构上可以简单的展现为下图的三角关系:
在这整个三角关系中,提供服务的注册者Service Registry是整个关系的核心,他负责登记和保存哪些服务是可以对外提供服务的。当服务的访问者Service Accessor发出请求访问某个服务节点的时候,需要先访问服务注册者,获取可以访问的服务节点信息,这样服务访问者就可以根据返回的信息访问响应的服务。
SpringBoot的微服务注册与发现是有SpringBoot微服务提供服务方式来决定的,如果是基于Dubbo框架的微服务发现方式 ,则很可能是基于Redis或者Zookeeper的注册和发现机制。
上面这种方式,服务的访问者需要了解服务注册机制,获取到服务节点之后再去访问对应的微服务。但是对于用户而言,服务的访问者就是使用者,不应该过多的去了解服务端有多少节点,而且需要多次访问才能获取响应的请求结果。
一个更好的方案是在服务访问者和注册者之后添加一个服务访问代理,访问者直接对代理发出请求,获取可用节点,对可用节点再次发出请求的工作都交给服务代理去做。优化之后的结构如下:
经过这样的调整之后,服务的访问者只需要与服务代理打交道,具体服务代理如何发现服务并访问的,对于访问者来说都只是一个黑盒,并不需要做过多的关心。
标签:执行顺序 group 缺点 去除 reg 开始 jndi 优缺点 嵌入
原文地址:https://www.cnblogs.com/uting/p/13939643.html