在將SpringMVC專案轉移到Springboot上的過程中,主要做了以下的事情
- Profile配置
- 全域性變數從properties檔案讀入
- 資料來源與Mybatis配置
- 日誌檔案配置
- WebConfig配置(包括原有的web.xml和spring-mvc.xml)
- 去掉多餘的bean注入
本篇文章除了介紹做了些什麼和怎麼做之外,會多很多多餘的廢話,關於對原理的一些探討,知其然也要知其所以然。
Profile配置
在傳統的Spring專案中,多個profile的配置方式首先是在pom.xml檔案中寫入多個profile,再通過啟動專案前先執行一個maven檔案來預載入選定的profile環境。載入完之後,執行專案的時候,會根據已載入的Environment,來決定去將哪個.properties檔案load到全域性變數中。
而在Springboot中對多個profile的管理就非常簡單了。
可以在jar包用命令列執行時選擇profile
java -jar example.jar --spring.profiles.active=test
複製程式碼
或者在application.properties這個全域性配置中配置
在application.properties中新增spring.profiles.active=test
複製程式碼
以上兩種方法均可啟動“test"這個profile,前者在執行上的優先順序要高於後者。
(順便一提,在Springboot裡面,這兩種方式本質上都是用“外部化配置”的方式,來對Environment進行編輯和替換)
另外,每個獨立的profiles的配置方式為以"application-xxx.properties"格式,針對每個不同環境,例如:
- application-pro.properties 表示預演環境
- application-dev.properties 表示開發環境
- application-test.properties 表示測試環境
當我們需要測試是否正常載入了profile的時候,可以在對應的.properties檔案中寫入
server.port=9080
複製程式碼
在啟動的時候就可以看到,是否已經啟動了這個埠。
在這裡可以順便提一下Springboot載入配置檔案的順序
- home目錄下的devtools全域性設定屬性( ~/.spring-boot-devtools.properties ,如果devtools啟用)。
- 測試用例上的@TestPropertySource註解。
- 測試用例上的@SpringBootTest#properties註解。
- 命令列引數
- 來自 SPRING_APPLICATION_JSON 的屬性(環境變數或系統屬性中內嵌的內聯JSON)。
- ServletConfig 初始化引數。
- ServletContext 初始化引數。
- 來自於 java:comp/env 的JNDI屬性。
- Java系統屬性(System.getProperties())。
- 作業系統環境變數。
- RandomValuePropertySource,只包含 random.* 中的屬性。
- 沒有打進jar包的Profile-specific應用屬性( application-{profile}.properties 和YAML變數)。
- 打進jar包中的Profile-specific應用屬性( application-{profile}.properties 和YAML變數)。
- 沒有打進jar包的應用配置( application.properties 和YAML變數)。
- 打進jar包中的應用配置( application.properties 和YAML變數)。
- @Configuration 類上的 @PropertySource 註解。
- 預設屬性(使用 SpringApplication.setDefaultProperties 指定)。
全域性變數從properties檔案讀入
在上一面一小節寫了針對不同環境的properties配置,這裡會寫關於如果將這些屬性寫入到全域性變數中,方便後面其他地方直接呼叫。
/**
* 全域性變數
*/
public class Global {
public static String examplePath;
@Value("${example_path}")
public void setExamplePath(String example) {
Global.examplePath = examplePath;
}
}
複製程式碼
通過這樣子,我們便將.properties檔案中的
example_path=http://localhost:9090
複製程式碼
這個屬性讀到了全域性變數中。
資料來源與Mybatis配置
在傳統的Spring專案中,用Mybatis連線資料庫
- 首先要建立一個名為datasource的bean
- 然後將這個datasource裝配到SqlSessionFactory中
- 最後再將SqlSessionFactory裝配到MapperScannerConfigurer中
這一切都是在xml配置檔案中配置的,比較繁瑣。在Springboot中會盡量去避免這樣子的xml配置。
Mybatis現在已經為Springboot提供了支援,我們只需要新增MyBatis-Spring-Boot-Starter這個依賴,它就會為我們去做好以下的事情:
- 自動檢測已有的datasource
- 建立一個SqlSessionFactoryBean的例項SqlSessionFactory,並將datasource裝配進去
- 建立一個SqlSessionTemplate的例項,並將SqlSessionFactory裝配進去
- 自動掃描你的mapper,將它們連線到SqlSessionTemplate,並將它們註冊到Spring的上下文,以便將它們注入到其他的bean中。
所以,在Springboot的Mybatis配置中,我們需要去做以下幾件事情:
- 在application-{profile}.properties中填入資料庫資訊,例如:
spring.datasource.url=jdbc:oracle:thin:@//localhost:1234/example
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.maxActive=10
spring.datasource.maxIdle=5
spring.datasource.maxWait=-1
複製程式碼
通過這種方式,我們便在Spring上下文中註冊了datasource這個bean。
- 建立一個MybatisConfig檔案,用java的方式取代xml:
/**
* Created by WuTaoyu on 2017/12/7.
*/
@Configuration
@EnableTransactionManagement
@MapperScan("com.example.db.dao")
public class MybatisConfig {
@Autowired
private DataSource dataSource;
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactoryBean() {
SqlSessionFactoryBean sqlsession = new SqlSessionFactoryBean();
sqlsession.setDataSource(dataSource);
try {
//新增XML目錄
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sqlsession.setMapperLocations(resolver.getResources("classpath:mapping/*.xml"));
return sqlsession.getObject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean
public PlatformTransactionManager annotationDrivenTransactionManager() {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "exampleSequence")
public OracleSequenceMaxValueIncrementer exampleSequenceBean(){
OracleSequenceMaxValueIncrementer exampleSequence = new OracleSequenceMaxValueIncrementer();
exampleSequence.setIncrementerName("EXAMPLE_SEQ");
exampleSequence.setDataSource(dataSource);
return exampleSequence;
}
}
複製程式碼
@MapperScan是掃描這個包下面的mapper。
另外這裡mapper.xml的位置,是在resource資料夾下面建了一個mapping資料夾,放在下面。
這裡的作用跟XML比較類似,是將傳統的xml表達方式用.java檔案來描述出來,本質上還是將datasource一步步注入。
由於示例用的是oracle資料庫,所以最後一個exampleSequence是示範如何新增序列。
- 對所有mapper的interface註解@Mapper
例如:
@Mapper
public interface UserMapper {
...
}
複製程式碼
日誌檔案配置
Logback支援用properties的方式外部化配置,但是對於比較細的配置來說,還是要沿用xml配置。
為了讓xml檔案從.properties檔案讀取一些路徑之類可能需要經常修改的靜態配置,需要在logback-spring.xml中配置
<property resource="application.properties" />
<property name="log.root.level" value="${log.root.level}" />
<property name="log.path" value="${log.path}" />
<property name="log.moduleName" value="${log.module}" />
複製程式碼
這樣子就可以將application.properties檔案中的
log.path=/home/logs/example
log.root.level=INFO
log.module=example
複製程式碼
讀入到logback-spring.xml中,然後再去呼叫。
WebConfig配置
WebConfig的主要作用是替代web.xml和spring-mvc.xml進行一些基礎配置。
- 關於web.xml
傳統的Spring專案都有配置一個web.xml檔案,這個檔案的作用是:當我們把war包放入應用容器(例如tomcat)中執行時,容器會根據web.xml去載入filter(過濾器)、servlet、error-page、welcome-file-list、listener(監聽器)、context-param(上下文引數)、resource-ref(資源配置)等配置。
包括ContextLoaderListener這個監聽器,就是在這裡載入進去,用於在啟動容器的時候,自動裝配ApplicationContext的配置資訊。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
複製程式碼
這個ApplicationContext是Spring IOC的核心(繼承自BeanFactory),所有單例的Bean會在這個時候就被例項化。
以及,SpringMVC中很重要的一個DispatcherServlet也是在這裡載入進去,並制定根據哪個xml檔案來配置DispatcherServlet。
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<!--<async-supported>true</async-supported>-->
</servlet>
複製程式碼
- 關於spring-mvc.xml
spring-mvc.xml是SpringMVC的配置檔案,在這裡可以配置我們引入的、需要定製化的bean,例如ViewResolver、multipartResolver、HTTP訊息轉換器、自定義的攔截器等等。
以上都與Springboot無關,主要是為了知其然也知其所以然,如果不感興趣的可以不看。
再講回Springboot的配置。Springboot有一個說法叫“約定優於配置”,就是儘量用約定的方式,而不是特地去針對性地配置(需要特殊配置的時候再去配置)。
引入spring-boot-starter-web這個“開箱即用”的依賴之後,spring-boot-starter-web下包含了一個spring-boot-autoconfigure。
有了這個依賴之後,就可以使用@EnableAutoCongiguration註解。這個註解就會根據引入的依賴來猜測你需要的Spring配置並幫你配置好。因為已經引入了spring-boot-starter-web的話,這個註解就會將web相關的配置配置好。
另外,@SpringBootApplication這個註解中已經包含了@EnableAutoCongiguration註解。所以只要在啟動類ExampleServerApplication上註解@SpringBootApplication就可以自動把web配置給配置好了。
當然,我們可能還有一些特殊的配置,這時候就可以建立一個WebConfig去定製
/**
* Created by WuTaoyu on 2017/12/8.
*/
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(marshallingHttpMessageConverter());
}
public MarshallingHttpMessageConverter marshallingHttpMessageConverter(){
MarshallingHttpMessageConverter marshallingHttpMessageConverter = new MarshallingHttpMessageConverter();
List<MediaType> mediaTypes = new ArrayList<MediaType>();
mediaTypes.add(MediaType.TEXT_XML);
mediaTypes.add(MediaType.APPLICATION_XML);
XStreamMarshaller xStreamMarshaller=new XStreamMarshaller();
marshallingHttpMessageConverter.setSupportedMediaTypes(mediaTypes);
marshallingHttpMessageConverter.setMarshaller(xStreamMarshaller);
marshallingHttpMessageConverter.setUnmarshaller(xStreamMarshaller);
return marshallingHttpMessageConverter;
}
//配置檔案上傳
@Bean(name = {"multipartResolver"})
public MultipartResolver multipartResolver(){
CommonsMultipartResolver commonsMultipartResolver=new CommonsMultipartResolver();
commonsMultipartResolver.setDefaultEncoding("utf-8");
commonsMultipartResolver.setMaxUploadSize(10485760000L);
commonsMultipartResolver.setMaxInMemorySize(40960);
return commonsMultipartResolver;
}
//異常處理
@Bean
public ExceptionHandler exceptionResolver(){
ExceptionHandler exceptionHandler = new ExceptionHandler();
return exceptionHandler;
}
//攔截器
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(new LogInterceptor()).addPathPatterns("/**");
super.addInterceptors(registry);
}
}
複製程式碼
我寫的這個示例檔案裡面做了幾件事情:
- 引入一個XML的Http訊息轉換器
- 引入multipartResolver
- 引入自定義的異常處理器
- 引入自定義攔截器
去掉多餘的bean注入
這個算是一個題外話,但也是我實際遇到的問題之一。
在實際執行的Springboot專案的時候,我發現了一些在傳統Spring專案中沒有報錯的問題,就是多餘的bean注入。
在傳統Spring專案中,這是沒有報錯的,但是在Springboot專案中就報錯了。我猜測是因為要注入bean的類方法名取的比較精簡的時候,與Springboot本身自動配置的一些bean重複了,就會報錯。
所以,把有些不需要注入的bean去掉吧。