最近接触到SpringBoot,才接触(太落伍)
数据源配置:
spring:
application:
name: xxx.xxx.xxx
datasource:
driver-class-name: com.mysql.jdbc.Driver
aop:
auto: true
proxy-target-class: false
另外还有一个配置:
以这样的方式配置的数据源是没有问题的。但是仔细看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")
都是不对的。
- EmbeddedWebApplicationContext 的Parent 不是 null。
- EmbeddedWebApplicationContext 的DisplayName是
AbstractApplicationContext
的
/** Display name */
private String displayName = ObjectUtils.identityToString(this);
产生。不等于Root WebApplicationContext