什么是 Spring Boot?
为什么要用 Spring Boot?
- 独立运行
- 简化配置
- 自动配置
- 无代码生成和XML配置
- 应用监控
Spring Boot 的核心配置文件有哪几个?它们的区别是什么?
- Spring Boot 中有以下两种配置文件:
- bootstrap (.yml 或者 .properties)
- application (.yml 或者 .properties)
- bootstrap/ application 的区别:
- bootstrap/ application 的应用场景:
Spring Boot 的配置文件有哪几种格式?它们有什么区别?
- .properties 和 .yml,它们的区别主要是书写格式不同。
- 1).properties
app.user.name = javastack
- 2).yml
- 1).properties
app:
user:
name: javastack
Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
- 启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:
-
@SpringBootConfiguration
:组合了@Configuration
注解,实现配置文件的功能。 -
@EnableAutoConfiguration
:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能:@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
。 -
@ComponentScan
:Spring组件扫描。
-
开启 Spring Boot 特性有哪几种方式?
- 有以下两种方式:
- 继承spring-boot-starter-parent项目
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
</parent>
- 导入spring-boot-dependencies项目依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependencies>
</dependencyManagement>
- Spring Boot依赖注意点:
- 属性覆盖只对继承有效
<properties>
<slf4j.version>1.7.25<slf4j.version>
</properties>
Spring Boot 需要独立的容器运行吗?
运行 Spring Boot 有哪几种方式?
- 打包用命令或者放到容器中运行
- 用 Maven/Gradle 插件运行
- 直接执行 main 方法运行
Spring Boot 自动配置原理是什么?
org.springframework.core.io.support.SpringFactoriesLoader.loadFactoryNames(Class<?>, ClassLoader)
public static List<String> loadFactoryNames(Class<?> factoryClass,ClassLoader classLoader){
String factoryClassName = factoryClass.getName();
try{
Enumeration<URL> urls = (classLoader!=null?classLoader.getResources(FACTORIES_RESOURCE_LOCATION):lassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
while(urls.hasMoreElements()){
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}catch(IOException ex){
throw new IllegalArgumentException("Unable to load ["+factoryClass.getName()+"] factories from location ["+FACTORIES_RESOURCE_LOCATION+"]",ex);
}
}
/**
* The location to look for factories.
* <p>Can be present in multiple JAR files.
*/
- 再来看看数据源自动配置的实现注解
@Configuration,@ConditionalOnClass就是自动配置的核心,首先它得是一个配置文件,其次根据类路径下是否有这个类去自动配置。
Spring Boot 的目录结构是怎样的?
你如何理解 Spring Boot 中的 Starters?
- Starters是什么:
- Starters命名:
- Starters分类:
-
Spring Boot应用类启动器
-
Spring Boot生产启动器
-
Spring Boot技术类启动器
- 其他第三方启动器
如何在 Spring Boot 启动的时候运行一些特定的代码?
CommandLineRunner:启动获取命令行参数
ApplicationRunner:启动获取应用启动的时候参数
使用方式:
或者这样
启动顺序:
Spring Boot 有哪几种读取配置的方式?
- 读取application文件:
- 在application.yml或者properties文件中添加:
info.address=USA
info.company=Spring
info.degree=high
一、@Value注解读取方式:
二、@ConfigurationProperties注解读取方式:
读取指定文件:
db.username=root
db.password=123456
一、@PropertySource+@Value注解读取方式:
二、@PropertySource+@ConfigurationProperties注解读取方式:
三、Environment读取方式:
总结
Spring Boot 支持哪些日志框架?推荐和默认的日志框架是哪个?
- 属性配置日志:
-
参考配置:
如:
- 自定义日志文件:
-
读取系统环境属性:
-
读取当前应用Environment中的属性:
-
Spring Boot也支持通过springProfile来加载不同profiles下的配置:
SpringBoot 实现热部署有哪几种方式?
在Spring Boot实现代码热部署是一件很简单的事情,代码的修改可以自动部署并重新热启动项目。
一、引用devtools依赖:
二、自定义配置热部署:
三、Intellij Idea修改:
1、勾上自动编译或者手动重新编译
File > Settings > Compiler-Build Project automatically
2、注册
ctrl + shift + alt + / > Registry > 勾选Compiler autoMake allow when app running
- 注意事项:
-
下面是devtools自动配置的部分源码:
你如何理解 Spring Boot 配置加载顺序?
1、properties文件;
2、YAML文件;
3、系统环境变量;
4、命令行参数;
等等……
- 配置属性加载的顺序如下:
1、开发者工具 `Devtools` 全局配置参数;
2、单元测试上的 `@TestPropertySource` 注解指定的参数;
3、单元测试上的 `@SpringBootTest` 注解指定的参数;
4、命令行指定的参数,如 `java -jar springboot.jar --name="Java技术栈"`;
5、命令行中的 `SPRING_APPLICATION_JSONJSON` 指定参数, 如 `java -Dspring.application.json='{"name":"Java技术栈"}' -jar springboot.jar`
6、`ServletConfig` 初始化参数;
7、`ServletContext` 初始化参数;
8、JNDI参数(如 `java:comp/env/spring.application.json`);
9、Java系统参数(来源:`System.getProperties()`);
10、操作系统环境变量参数;
11、`RandomValuePropertySource` 随机数,仅匹配:`ramdom.*`;
12、JAR包外面的配置文件参数(`application-{profile}.properties(YAML)`)
13、JAR包里面的配置文件参数(`application-{profile}.properties(YAML)`)
14、JAR包外面的配置文件参数(`application.properties(YAML)`)
15、JAR包里面的配置文件参数(`application.properties(YAML)`)
16、`@Configuration`配置文件上 `@PropertySource` 注解加载的参数;
17、默认参数(通过 `SpringApplication.setDefaultProperties` 指定);
- 数字小的优先级越高,即数字小的会覆盖数字大的参数值,我们来实践下,验证以上配置参数的加载顺序。
1、在主应用程序中添加 Java 系统参数
2、在 application.properties 文件中添加属性
3、在 application-dev.properties 文件中添加属性
4、添加测试类
-
运行 test 单元测试,程序输出:
Spring Boot 如何定义多套不同环境配置?
applcation.properties
application-dev.properties
application-test.properties
application-prod.properties
- 然后在
applcation.properties
文件中指定当前的环境spring.profiles.active=test
,这时候读取的就是application-test.properties
文件。 - 基于yml文件类型
- 基于Java代码
- 指定Profile
Spring Boot 可以兼容老 Spring 项目吗,如何做?
保护 Spring Boot 应用有哪些方法?
Spring Boot 2.X 有什么新特性?与 1.X 有什么区别?
- 配置变更
- JDK 版本升级
- 第三方类库升级
- 响应式 Spring 编程支持
- HTTP/2 支持
- 配置属性绑定
- 更多改进与加强...
具体请看这篇文章《Spring Boot 2.x 新特性总结及迁移指南》。
JavaBean是什么时候创建的?
@Controller
public class TestController {
public TestController(){
System.out.println("TestController 创建了");
}
}
1.直接启动,通过日志可以看到TestController 被创建了
2018-07-02 17:38:58.083 INFO 7076 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2018-07-02 17:38:58.088 INFO 7076 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-07-02 17:38:58.088 INFO 7076 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-07-02 17:38:58.089 INFO 7076 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-07-02 17:38:58.089 INFO 7076 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
TestController 创建了
2018-07-02 17:38:58.483 INFO 7076 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@78dd667e: startup date [Mon Jul 02 17:38:55 CST 2018]; root of context hierarchy
2018-07-02 17:38:58.558 INFO 7076 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-07-02 17:38:58.559 INFO 7076 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-07-02 17:38:58.587 INFO 7076 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-07-02 17:38:58.587 INFO 7076 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2.设置断点进行调试
- 断点一:启动位置
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
- 断点二: run 方法内部
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
- 该方法就是Spring Boot项目启动时 所要执行的代码,根据阅读可以分为以下步骤:
- 加载环境变量
- 创建上下文 ConfigurableApplicationContext 对象
- 准备上下文 prepareContext()
- 刷新上下文 refreshContext()
- 刷新之后的处理 afterRefresh()
TestController 创建了
- 断点三:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
TestController 创建了
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Initialize conversion service for this context.
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
// Register a default embedded value resolver if no bean post-processor
// (such as a PropertyPlaceholderConfigurer bean) registered any before:
// at this point, primarily for resolution in annotation attribute values.
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(new StringValueResolver() {
@Override
public String resolveStringValue(String strVal) {
return getEnvironment().resolvePlaceholders(strVal);
}
});
}
// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
}
// Stop using the temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(null);
// Allow for caching all bean definition metadata, not expecting further changes.
beanFactory.freezeConfiguration();
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
}
通过调试我们可以发现,执行beanFactory.preInstantiateSingletons();这个方法之后,TestController 被创建了,那么我接下来要做的就是看这个方法具体怎么实现的,调试过程中,我们通过shift+alt+F7 强制进入代码,可以看到其具体实现如下:
@Override
public void preInstantiateSingletons() throws BeansException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Pre-instantiating singletons in " + this);
}
// Iterate over a copy to allow for init methods which in turn register new bean definitions.
// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
// Trigger initialization of all non-lazy singleton beans...
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return ((SmartFactoryBean<?>) factory).isEagerInit();
}
}, getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
else {
getBean(beanName);
}
}
}
// Trigger post-initialization callback for all applicable beans...
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
smartSingleton.afterSingletonsInstantiated();
return null;
}
}, getAccessControlContext());
}
else {
smartSingleton.afterSingletonsInstantiated();
}
}
}
}
总结
SpringApplication.run(App.class,args)
refreshContext(context)
finishBeanFactoryInitialization(beanFactory)
beanFactory.preInstantiateSingletons()
getBean(beanName)
doGetBean()
SpringBoot框架中,JavaBean都是单例的吗?多例怎么设置?
singleton:单例模式,当spring创建applicationContext容器的时候,spring会欲初始化所有的该作用域实例,加上lazy-init就可以避免预处理;
prototype:原型模式,每次通过getBean获取该bean就会新产生一个实例,创建后spring将不再对其管理;
====下面是在web项目下才用到的===
request:搞web的大家都应该明白request的域了吧,就是每次请求都新产生一个实例,和prototype不同就是创建后,接下来的管理,spring依然在监听;
session:每次会话,同上;
global session:全局的web域,类似于servlet中的application。
为什么spring要默认是单例呢?原因有二:
1、为了性能:这个不用废话了,单例不用每次都new,当然快了。
2、不需要多例:不需要多例会让很多人迷惑,因为spring mvc官方也没明确说不可以多例。
@Controller
public class MultViewController {
private int index = 0;//非静态
@RequestMapping("/show")
public String toShow(ModelMap model) {
System.out.println(++i);
return"show";
}
@RequestMapping("/test")
public String test() {
System.out.println(++i);
return"test";
}
}
最佳实践:
1、不要在controller中定义成员变量。
2、万一必须要定义一个非静态成员变量时候,则通过注解@Scope("prototype")
,将其设置为多例模式。