吐血整理SpringBoot面试题

目录

什么是 Spring Boot?

为什么要用 Spring Boot?

Spring Boot 的核心配置文件有哪几个?它们的区别是什么?

Spring Boot 的配置文件有哪几种格式?它们有什么区别?

Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?

开启 Spring Boot 特性有哪几种方式?

Spring Boot 需要独立的容器运行吗?

运行 Spring Boot 有哪几种方式?

Spring Boot 自动配置原理是什么?

Spring Boot 的目录结构是怎样的?

你如何理解 Spring Boot 中的 Starters?

如何在 Spring Boot 启动的时候运行一些特定的代码?

Spring Boot 有哪几种读取配置的方式?

Spring Boot 支持哪些日志框架?推荐和默认的日志框架是哪个?

SpringBoot 实现热部署有哪几种方式?

你如何理解 Spring Boot 配置加载顺序?

Spring Boot 如何定义多套不同环境配置?

Spring Boot 可以兼容老 Spring 项目吗,如何做?

保护 Spring Boot 应用有哪些方法?

Spring Boot 2.X 有什么新特性?与 1.X 有什么区别?

JavaBean是什么时候创建的?

SpringBoot框架中,JavaBean都是单例的吗?多例怎么设置?


什么是 Spring Boot?

SpringBoot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。

更多 Spring Boot 详细介绍请看这篇文章《什么是Spring Boot?》。

为什么要用 Spring Boot?

Spring Boot 优点非常多,如:

一、独立运行

Spring Boot而且内嵌了各种servlet容器,Tomcat、Jetty等,现在不再需要打成war包部署到容器中,Spring Boot只要打成一个可执行的jar包就能独立运行,所有的依赖包都在一个jar包内。

二、简化配置

spring-boot-starter-web启动器自动依赖其他组件,简少了maven的配置。

三、自动配置

Spring Boot能根据当前类路径下的类、jar包来自动配置bean,如添加一个spring-boot-starter-web启动器就能拥有web的功能,无需其他配置。

四、无代码生成和XML配置

Spring Boot配置过程中无代码生成,也无需XML配置文件就能完成所有配置工作,这一切都是借助于条件注解完成的,这也是Spring4.x的核心功能之一。

五、应用监控

Spring Boot提供一系列端点可以监控服务及应用,做健康检测。

 

Spring Boot 的核心配置文件有哪几个?它们的区别是什么?

Spring Boot 中有以下两种配置文件:

  • bootstrap (.yml 或者 .properties)

  • application (.yml 或者 .properties)

bootstrap/ application 的区别:

Spring Cloud 构建于 Spring Boot 之上,在 Spring Boot 中有两种上下文,一种是 bootstrap,,另外一种是 application,,bootstrap 是应用程序的父上下文,也就是说 bootstrap 加载优先于 applicaton。bootstrap 主要用于从额外的资源来加载配置信息,还可以在本地外部配置文件中解密属性。这两个上下文共用一个环境,它是任何Spring应用程序的外部属性的来源。bootstrap 里面的属性会优先加载,它们默认也不能被本地相同配置覆盖。

因此,对比 application 配置文件,bootstrap 配置文件具有以下几个特性:

  • boostrap 由父 ApplicationContext 加载,比 applicaton 优先加载

  • boostrap 里面的属性不能被覆盖

bootstrap/ application 的应用场景:

application 配置文件这个容易理解,主要用于 Spring Boot 项目的自动化配置。

bootstrap 配置文件有以下几个应用场景:

  • 使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;

  • 一些固定的不能被覆盖的属性

  • 一些加密/解密的场景。

Spring Boot 的配置文件有哪几种格式?它们有什么区别?

.properties 和 .yml,它们的区别主要是书写格式不同。

1).properties

app.user.name = javastack

2).yml

app:
  user:
    name: javastack

另外,.yml 格式不支持 @PropertySource 注解导入配置。

Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?

启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:

@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。

@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。

@ComponentScan:Spring组件扫描。

开启 Spring Boot 特性有哪几种方式?

有以下两种方式:

1. 继承spring-boot-starter-parent项目

<parent>  
<groupId>org.springframework.boot</groupId>   
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
</parent>

2. 导入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依赖注意点:

1. 属性覆盖只对继承有效

Spring Boot依赖包里面的组件的版本都是和当前Spring Boot绑定的,如果要修改里面组件的版本,只需要添加如下属性覆盖即可,但这种方式只对继承有效,导入的方式无效。

<properties>
    <slf4j.version>1.7.25<slf4j.version>
</properties>

如果导入的方式要实现版本的升级,达到上面的效果,这样也可以做到,把要升级的组件依赖放到Spring Boot之前。

需要注意,要修改Spring Boot的依赖组件版本可能会造成不兼容的问题。

Spring Boot 需要独立的容器运行吗?

可以不需要,内置了 Tomcat/Jetty 等容器。

运行 Spring Boot 有哪几种方式?

1)打包用命令或者放到容器中运行

2)用 Maven/Gradle 插件运行

3)直接执行 main 方法运行

Spring Boot 自动配置原理是什么?

Spring Boot的自动配置注解是@EnableAutoConfiguration, 从上面的@Import的类可以找到下面自动加载自动配置的映射。

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);

	}

}

这个方法会加载类路径及所有jar包下META-INF/spring.factories配置中映射的自动配置的类。

/**

 * The location to look for factories.

 * <p>Can be present in multiple JAR files.

 */

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

查看Spring Boot自带的自动配置的包: spring-boot-autoconfigure-1.5.6.RELEASE.jar,打开其中的META-INF/spring.factories文件会找到自动配置的映射。

再来看看数据源自动配置的实现注解

@Configuration,@ConditionalOnClass就是自动配置的核心,首先它得是一个配置文件,其次根据类路径下是否有这个类去自动配置。

Spring Boot 的目录结构是怎样的?

Spring Boot 与传统项目最大的区别是,传统项目都是打成 WAR 包部署到服务器上面,需要额外的 Servlet 容器, 而 Spring Boot 则可以直接打成 jar包,并内置集成了 Servlet 容器,通过命令 java -jar xx.jar 则可以直接运行,不需要独立的 Servlet 容器。

打成可执行 jar 包后,我们来看下其中的 META-INF/MANIFEST.MF 文件:

其中有一个 Start-Class 便是这个 jar 包的入口类,这个入口类推荐是放在一个项目的顶层包中,其他所有的类都放在其子包下面,目录结构如以下所示:

这个目录结构是主流及推荐的做法,而在主入口类上加上 @SpringBootApplication 注解来开启 Spring Boot 的各项能力,如自动配置、组件扫描等。

如果你不想这么做,你也可以充分利用 @EnableAutoConfiguration 和 @ComponentScan 注解自定义你的行为,不过这不是推荐的做法。

你如何理解 Spring Boot 中的 Starters?

Starters是什么:

Starters可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包,你可以一站式集成Spring及其他技术,而不需要到处找示例代码和依赖包。如你想使用Spring JPA访问数据库,只要加入spring-boot-starter-data-jpa启动器依赖就能使用了。Starters包含了许多项目中需要用到的依赖,它们能快速持续的运行,都是一系列得到支持的管理传递性依赖。

Starters命名:

Spring Boot官方的启动器都是以spring-boot-starter-命名的,代表了一个特定的应用类型。第三方的启动器不能以spring-boot开头命名,它们都被Spring Boot官方保留。一般一个第三方的应该这样命名,像mybatis的mybatis-spring-boot-starter。

Starters分类:

1. Spring Boot应用类启动器

2. Spring Boot生产启动器

3. Spring Boot技术类启动器

4. 其他第三方启动器

如何在 Spring Boot 启动的时候运行一些特定的代码?

如果你想在Spring Boot启动的时候运行一些特定的代码,你可以实现接口ApplicationRunner或者CommandLineRunner,这两个接口实现方式一样,它们都只提供了一个run方法。

CommandLineRunner:启动获取命令行参数

ApplicationRunner:启动获取应用启动的时候参数

使用方式:

或者这样

启动顺序:

如果启动的时候有多个ApplicationRunner和CommandLineRunner,想控制它们的启动顺序,可以实现 org.springframework.core.Ordered接口或者使用 org.springframework.core.annotation.Order注解。

Spring Boot 有哪几种读取配置的方式?

读取application文件:

在application.yml或者properties文件中添加:

info.address=USA

info.company=Spring

info.degree=high

一、@Value注解读取方式:

二、@ConfigurationProperties注解读取方式:

读取指定文件:

资源目录下建立config/db-config.properties:

db.username=root

db.password=123456

一、@PropertySource+@Value注解读取方式:

注意:@PropertySource不支持yml文件读取。

二、@PropertySource+@ConfigurationProperties注解读取方式:

三、Environment读取方式:

以上所有加载出来的配置都可以通过Environment注入获取到:

总结

从以上示例来看,Spring Boot可以通过@PropertySource,@Value,@Environment,@ConfigurationProperties来绑定变量。

Spring Boot 支持哪些日志框架?推荐和默认的日志框架是哪个?

Spring Boot支持Java Util Logging,Log4j2,Lockback作为日志框架,如果你使用starters启动器,Spring Boot将使用Logback作为默认日志框架。无论使用哪种日志框架,Spring Boot都支持配置将日志输出到控制台或者文件中。

spring-boot-starter启动器包含spring-boot-starter-logging启动器并集成了slf4j日志抽象及Logback日志框架。

属性配置日志:

Spring Boot支持属性配置日志参数,这个不是很灵活,不细讲。

参考配置:

如:

自定义日志文件:

根据不同的日志框架,默认加载的日志配置文件的文件名,放在资源根目录下,其他的目录及文件名不能被加载。

既然默认自带了Logback框架,Logback也是最优秀的日志框架,往资源目录下创建一个logback-spring.xml即可。

强烈推荐使用logback-spring.xml作为文件名,因为logback.xml加载太早。

日志初始化在ApplicationContext创建之前,所以@PropertySources加载的配置是读取不到的,系统环境变量、Spring Environment及application,bootstrap配置文件中的信息可以读取到。

读取系统环境属性:

读取当前应用Environment中的属性:

Spring Boot也支持通过springProfile来加载不同profiles下的配置:

SpringBoot 实现热部署有哪几种方式?

在Spring Boot实现代码热部署是一件很简单的事情,代码的修改可以自动部署并重新热启动项目。

一、引用devtools依赖:

这样,当修改一个java类时就会热更新。

二、自定义配置热部署:

以下配置用于自定义配置热部署,可以不设置。

三、Intellij Idea修改:

如果是idea,需要改以下两个地方:

1、勾上自动编译或者手动重新编译

File > Settings > Compiler-Build Project automatically

2、注册

ctrl + shift + alt + / > Registry > 勾选Compiler autoMake allow when app running

注意事项:

1、生产环境devtools将被禁用,如java -jar方式或者自定义的类加载器等都会识别为生产环境。

2、打包应用默认不会包含devtools,除非你禁用SpringBoot Maven插件的excludeDevtools属性。

3、Thymeleaf无需配置 spring.thymeleaf.cache:false,devtools默认会自动设置,参考完整属性。

下面是devtools自动配置的部分源码:

4、devtools会在windows资源管理器占用java进程,在开发工具里面杀不掉,只能手动kill掉,不然重启会选成端口重复绑定报错。

你如何理解 Spring Boot 配置加载顺序?

使用 Spring Boot 会涉及到各种各样的配置,如开发、测试、线上就至少 3 套配置信息了。Spring Boot 可以轻松的帮助我们使用相同的代码就能使开发、测试、线上环境使用不同的配置。

在 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 如何定义多套不同环境配置?

首先我们要了解一个名词:Profile

简单来说,Profile就是Spring Boot可以对不同环境或者指令来读取不同的配置文件。

假如有开发、测试、生产三个不同的环境,需要定义三个不同环境下的配置。

基于properties文件类型

你可以另外建立3个环境下的配置文件:

applcation.properties

application-dev.properties

application-test.properties

application-prod.properties

然后在applcation.properties文件中指定当前的环境spring.profiles.active=test,这时候读取的就是application-test.properties文件。

基于yml文件类型

只需要一个applcation.yml文件就能搞定,推荐此方式。

此时读取的就是prod的配置,prod包含proddb,prodmq,此时可以读取proddb,prodmq下的配置。

也可以同时激活三个配置。

基于Java代码

在JAVA配置代码中也可以加不同Profile下定义不同的配置文件,@Profile注解只能组合使用@Configuration和@Component注解。

指定Profile

main方法启动方式:

插件启动方式:

jar运行方式:

除了在配置文件和命令行中指定Profile,还可以在启动类中写死指定,通过SpringApplication.setAdditionalProfiles方法。

SpringApplication.class:

Spring Boot 可以兼容老 Spring 项目吗,如何做?

可以兼容,使用 @ImportResource 注解导入老 Spring 项目配置文件。

保护 Spring Boot 应用有哪些方法?

1.在生产中使用HTTPS
2.使用Snyk检查你的依赖关系
3.升级到最新版本
4.启用CSRF保护
5.使用内容安全策略防止XSS攻击
6.使用OpenID Connect进行身份验证
7.管理密码?使用密码哈希!
8.安全地存储秘密
9.使用OWASP的ZAP测试您的应用程序
10.让你的安全团队进行代码审查

更多请看这篇文章《10 种保护 Spring Boot 应用的绝佳方法》。

Spring Boot 2.X 有什么新特性?与 1.X 有什么区别?

  • 配置变更
  • JDK 版本升级
  • 第三方类库升级
  • 响应式 Spring 编程支持
  • HTTP/2 支持
  • 配置属性绑定
  • 更多改进与加强...

具体请看这篇文章《Spring Boot 2.x 新特性总结及迁移指南》。

JavaBean是什么时候创建的?

在项目里面写一个bean:

@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项目启动时 所要执行的代码,根据阅读可以分为以下步骤: 

1. 加载环境变量 
2. 创建上下文 ConfigurableApplicationContext 对象 
3. 准备上下文 prepareContext() 
4. 刷新上下文 refreshContext() 
5. 刷新之后的处理 afterRefresh() 

可以在以上的五个地方进行断点设置,然后开始调试,结合控制台的日志一步一步观察。

通过断点调试,发现执行完refreshContext()方法执行完毕之后,日志打印了:

TestController 创建了

初步判断,Bean在执行refreshContext() 方法的时候创建的,那么接下来我们看一下refreshContext这个方法干了什么事情?

断点三: 
进入refreshContext 方法,找到最终调用的方法 refresh()

    @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();
            }

以步骤二的断点设置方式,我们在进行一步调试,发现执行finishBeanFactoryInitialization(beanFactory)方法之后,日志打印了

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();
                }
            }
        }
    }

采用调试的方式,通过观察我们可以看到,我们的TestController Bean 被加载到beanNames这个List,通过循环语句最终调用了getBean(beanName);去创建了对象,当然getBean内部会调用doGetBean() 方法进行对象的获取或者创建。

总结

TestController 这个对象的创建,经过了如下方法调用:

SpringApplication.run(App.class,args) 
refreshContext(context) 
finishBeanFactoryInitialization(beanFactory) 
beanFactory.preInstantiateSingletons() 
getBean(beanName) 
doGetBean()

SpringBoot框架中,JavaBean都是单例的吗?多例怎么设置?

spring bean作用域有以下5个:

singleton:单例模式,当spring创建applicationContext容器的时候,spring会欲初始化所有的该作用域实例,加上lazy-init就可以避免预处理;

prototype:原型模式,每次通过getBean获取该bean就会新产生一个实例,创建后spring将不再对其管理;

====下面是在web项目下才用到的===

request:搞web的大家都应该明白request的域了吧,就是每次请求都新产生一个实例,和prototype不同就是创建后,接下来的管理,spring依然在监听;

session:每次会话,同上;

global session:全局的web域,类似于servlet中的application。

好了,上面都说了spring的controller默认是单例,那很自然就是singleton了。

为什么spring要默认是单例呢?原因有二:

1、为了性能:这个不用废话了,单例不用每次都new,当然快了。

2、不需要多例:不需要多例会让很多人迷惑,因为spring mvc官方也没明确说不可以多例。

我这里说不需要的原因是看开发者怎么用了,如果你给controller中定义很多的属性,那么单例肯定会出现竞争访问了。

因此,只要controller中不定义属性,那么单例完全是安全的。下面给个例子说明下:

@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"),将其设置为多例模式。

猜你喜欢

转载自blog.csdn.net/Kevin_Gu6/article/details/88547424