SpringBoot 入门经验

作者: 一杯半盏 | 来源:发表于2017-08-04 17:47 被阅读1174次

    最近接触到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

    相关文章

      网友评论

        本文标题:SpringBoot 入门经验

        本文链接:https://www.haomeiwen.com/subject/vzmvlxtx.html