SpringBoot 入门经验

最近接触到SpringBoot,才接触(太落伍)

数据源配置:

spring:
  application:
    name: xxx.xxx.xxx
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
  aop:
    auto: true
    proxy-target-class: false

另外还有一个配置:

image.png

以这样的方式配置的数据源是没有问题的。但是仔细看SpringBoot会自动反复创建一个datasource,但是这个spring.datasource是配置不全的。
因而会反复报错。

[ERROR] [17:25:13.546][DiscoveryClient][1477]:Cannot fetch registry from server
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in URL [file:/E:/XXXXXX/target/classes/spring/applicationContext-mybatis.xml]: Cannot resolve reference to bean 'dataSource' while setting bean property 'dataSource'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [XXXXXXXXconfig/HikariCPConfig.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSourceInitializer': Invocation of init method failed; nested exception is java.lang.IllegalStateException: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1fb5261 has been closed already
.....
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [XXXXXXXXconfig/HikariCPConfig.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSourceInitializer': Invocation of init method failed; nested exception is java.lang.IllegalStateException: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1fb5261 has been closed already
    ... 23 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSourceInitializer': Invocation of init method failed; nested exception is java.lang.IllegalStateException: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1fb5261 has been closed already
    ... 23 more
Caused by: java.lang.IllegalStateException: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1fb5261 has been closed already
    ... 23 more


解决方法就是,要么把这个数据源写全。要么就加上一个

@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})

注解在@Configuration类上。
@SpringBootApplication包含了这个注解

就不会自动创建这个残缺的数据源了。

2017年9月30日 11:12:18 更新

上面说的问题,基本很少遇到,其实后来发现是因为Profile配置和Maven Resource标签写的很混乱,导致配置文件复制不全。

Spring Boot 正确配置

下面给出正确的配置

@EnableEurekaClient
@EnableDiscoveryClient
@SpringBootApplication
@ImportResource("classpath*:spring/applicationContext.xml")
@EnableFeignClients
public class Bootstrap {
    public static void main(String[] args) {
        SpringApplication.run(Bootstrap.class, args);
    }
}

注意,下面的这段是多余的,不用写。SpringBoot 会自动扫瞄到这个文件(resources根目录下)。

@PropertySources({
        @PropertySource("classpath:application.yaml"),
})

前两行是Eureka的配置,如果想连接上注册中心的话。
第三行是SpringBootApplication主要注解。等价于

@Configuration
@EnableAutoConfiguration
@ComponentScan

第四行是导入经典的xml配置,一般是Mybatis的。个人是比较喜欢Java Config和XML混合的方式去配置。如下

<bean id="yamlProperties" class="org.springframework.beans.factory.config.YamlPropertiesFactoryBean">
        <property name="resources" value="classpath:conf/application*.yaml"/>
    </bean>

    <context:property-placeholder properties-ref="yamlProperties" ignore-unresolvable="true"/>

    <import resource="applicationContext-mybatis.xml"/>

还有,如果用SpringBoot不需要下面的配置。

<mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <property name="features">
                    <list>
                        <value>DisableCircularReferenceDetect</value>
                        <value>WriteNullListAsEmpty</value>
                        <value>WriteNullStringAsEmpty</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

    <!-- spring实例化扫描器 -->
    <context:component-scan base-package="com.xxx.xxx" />

因为,mvc:annotation-driven 会和SpringBoot的Component一起扫瞄,这样会导致HandlerMapping 处理 Controller 的@RequestMapping 两次。简单说,看日志会打印两次Controller的 RequestMapping的URL。

那么,MessageConverter怎么处理呢?

MessageConverter

任意写一个Configuration类

@Configuration
public class SpringConfig {

    @Bean
    public HttpMessageConverter configureMessageConverter() {
        HttpMessageConverter<?> messageConverter = new FastJsonHttpMessageConverter();
        ((FastJsonHttpMessageConverter)messageConverter).setFeatures(SerializerFeature.DisableCircularReferenceDetect);
        ((FastJsonHttpMessageConverter)messageConverter).setFeatures(SerializerFeature.WriteNullListAsEmpty);
        ((FastJsonHttpMessageConverter)messageConverter).setFeatures(SerializerFeature.WriteNullStringAsEmpty);
        ((FastJsonHttpMessageConverter)messageConverter).setFeatures(SerializerFeature.WriteMapNullValue);

        return messageConverter;
    }
}

这样才算是正确的,清晰的,符合SpringBoot的理念的配置方式。

MessageConverter 更好的配置方式

 @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        super.configureMessageConverters(converters);
        FastJsonHttpMessageConverter4 fastConverter = new FastJsonHttpMessageConverter4();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);
        fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteNullListAsEmpty);
        fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteNullStringAsEmpty);
        fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue);
        fastConverter.setFastJsonConfig(fastJsonConfig);
        converters.add(fastConverter);
    }

这是在使用Feign并且,因为FastJSON低版本的安全漏洞升级FastJSON 到1.2.29时遇到的。
改成这样就没问题了。具体原因没有详细探究,FastJSON 1.2.29是支持Spring Core 4.2.6的,并且通过FastJsonHttpMessageConverter4 进行支持。

Spring Boot 启动完成后执行特定代码

看了很多文章,一般都是判断DisplayName是否是Root Application这种,或者getParent() == null
感觉都不怎么好用,下面给出经过验证的代码。

@Component
public class ApplicationStartup implements ApplicationListener<ContextRefreshedEvent> {
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();
        if(applicationContext instanceof EmbeddedWebApplicationContext) {
            log.info("ApplicationStartup {}", applicationContext.getDisplayName());
        }
    }
}

如果是使用

event.getApplicationContext().getParent() == null  

或者使用

event.getApplicationContext().getDisplayName().equals("Root WebApplicationContext")

都是不对的。

  1. EmbeddedWebApplicationContext 的Parent 不是 null。
  2. EmbeddedWebApplicationContext 的DisplayName是
    AbstractApplicationContext
/** Display name */
private String displayName = ObjectUtils.identityToString(this);

产生。不等于Root WebApplicationContext

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容