SpringJava技术问答springboot

springBoot面试题

2019-10-29  本文已影响0人  全满

什么是 Spring Boot?

SpringBoot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。
更多 Spring Boot 详细介绍请看这篇文章《什么是Spring Boot?》。

为什么要用 Spring Boot?

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

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


image.png

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

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

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

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

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

application 配置文件这个容易理解,主要用于 Spring Boot 项目的自动化配置。
bootstrap 配置文件有以下几个应用场景:1. 使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;2. 一些固定的不能被覆盖的属性,一些加密/解密的场景。

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

    app:
    user:
    name: javastack

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

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

开启 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>
  1. 导入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>
  1. 属性覆盖只对继承有效

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

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

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


image.png

需要注意,要修改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文件会找到自动配置的映射。


image.png

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

Spring Boot 与传统项目最大的区别是,传统项目都是打成 WAR 包部署到服务器上面,需要额外的 Servlet 容器, 而 Spring Boot 则可以直接打成 jar包,并内置集成了 Servlet 容器,通过命令 java -jar xx.jar 则可以直接运行,不需要独立的 Servlet 容器。
打成可执行 jar 包后,我们来看下其中的 META-INF/MANIFEST.MF 文件:

image.png
其中有一个 Start-Class 便是这个 jar 包的入口类,这个入口类推荐是放在一个项目的顶层包中,其他所有的类都放在其子包下面,目录结构如以下所示:
image.png
这个目录结构是主流及推荐的做法,而在主入口类上加上 @SpringBootApplication 注解来开启 Spring Boot 的各项能力,如自动配置、组件扫描等。
image.png
如果你不想这么做,你也可以充分利用 @EnableAutoConfiguration@ComponentScan 注解自定义你的行为,不过这不是推荐的做法。

你如何理解 Spring Boot 中的 Starters?

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

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

  1. Spring Boot应用类启动器


    image.png
  2. Spring Boot生产启动器


    image.png
  3. Spring Boot技术类启动器


    image.png
  4. 其他第三方启动器

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

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

CommandLineRunner:启动获取命令行参数

image.png
ApplicationRunner:启动获取应用启动的时候参数
image.png
使用方式:
image.png
或者这样
image.png
启动顺序:

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

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

info.address=USA

info.company=Spring

info.degree=high

一、@Value注解读取方式:

image.png
二、@ConfigurationProperties注解读取方式:
image.png
读取指定文件:

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

db.username=root

db.password=123456

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


image.png

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

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


image.png

三、Environment读取方式:

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


image.png

总结

从以上示例来看,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支持属性配置日志参数,这个不是很灵活,不细讲。

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


image.png

既然默认自带了Logback框架,Logback也是最优秀的日志框架,往资源目录下创建一个logback-spring.xml即可。
强烈推荐使用logback-spring.xml作为文件名,因为logback.xml加载太早。
日志初始化在ApplicationContext创建之前,所以@PropertySources加载的配置是读取不到的,系统环境变量、Spring Environment及application,bootstrap配置文件中的信息可以读取到。

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

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

一、引用devtools依赖:

image.png

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

二、自定义配置热部署:

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


image.png

三、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默认会自动设置,参考完整属性。

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 系统参数


image.png

2、在 application.properties 文件中添加属性


image.png
3、在 application-dev.properties 文件中添加属性
image.png

4、添加测试类


image.png

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

首先我们要了解一个名词:Profile
简单来说,Profile就是Spring Boot可以对不同环境或者指令来读取不同的配置文件。
假如有开发、测试、生产三个不同的环境,需要定义三个不同环境下的配置。
基于properties文件类型
你可以另外建立3个环境下的配置文件:

applcation.properties

application-dev.properties

application-test.properties

application-prod.properties

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


image.png

此时读取的就是prod的配置,prod包含proddb,prodmq,此时可以读取proddb,prodmq下的配置。
也可以同时激活三个配置。


image.png

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


image.png

main方法启动方式:


image.png

插件启动方式:


image.png
jar运行方式:
image.png

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


image.png

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 有什么区别?

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);
    }
}
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);
        }
    }
  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个:

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

好了,上面都说了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"),将其设置为多例模式。

上一篇下一篇

猜你喜欢

热点阅读