約定優於配置的這種做法在如今越來越流行了,它的特點是簡單、快速、便捷。但是這是建立在程序員熟悉這些約定的前提上。而 Spring 擁有一個龐大的生態體系,剛開始轉到 Spring Boot 完全舍棄 XML 時肯定是不習慣的,所以也會造成一些困擾。
運行方式
spring-boot-starter-web 包含了 Spring MVC 的相關依賴(包括 Json 支持的 Jackson 和數據校驗的 Hibernate Validator)和一個內置的 Tomcat 容器,這使得在開發階段可以直接通過 main方法或是 JAR 包獨立運行一個 WEB 項目。而在部署階段也可以打成 WAR 包放到生產環境運行。
@SpringBootApplication public class Application extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(Application.class); } public static void main(String[] args) throws Exception { SpringApplication.run(Application.class, args); } }
在擁有 @SpringBootApplication 注解的類中,使用 SpringApplication 的 run 方法可以通過JAR啟動項目。
繼承 SpringBootServletInitializer 類並實現 configure 方法,使用 application 的 sources 方法可以通過WAR啟動項目。
配置文件
Spring boot 的默認配置文件是 resources 下的 application.properties 和 application.yml。
配置文件
Spring boot 的默認配置文件是 resources 下的 application.properties 和 application.yml。
我曾在項目中遇到過 application.properties 出現中文亂碼問題,當時嘗試了很多辦法都沒有解決。Spring Boot 總是會以 iso-8859 的編碼方式讀取該文件,后來改用 YAML 了就再也沒有出現過亂碼了。並且它也擁有更簡潔的語法,所以在此也更推薦使用 application.yml 作為默認的配置文件。
配置文件中可以定義一個叫做 spring.profiles.active 的屬性,該屬性可以根據運行環境自動讀取不同的配置文件。例如將該屬性定義為 dev 的話,Spring Boot 會額外從 application-dev.yml 文件中讀取該環境的配置。
Spring Boot 注入配置文件屬性的方法有兩種,一種是通過 @Value 注解接受配置文件中的屬性,另外一種是通過 @ConfigurationProperties 注解通過 set 方法自動為Bean注入對應的屬性。
通過 @Value 注入屬性,接收者既可以是方法參數,也可以是成員變量。例如配置文件為:
dataSource: url: jdbc:mysql://127.0.0.1:3306/test username: test password: test filters: stat,slf4j redis: host: 192.168.1.222 port: 6379
通過 @Value 接受方法參數初始化Bean:
@Bean public JedisPool jedisPool(@Value("${redis.host}") String host, @Value("${redis.port}") int port) { return new JedisPool(host, port); }
注入配置文件屬性
注入配置文件屬性
注入配置文件屬性
通過 @ConfigurationProperties 讀取配置初始化Bean,會直接調用對應的 set 方法注入:
@Bean(initMethod="init",destroyMethod="close") @ConfigurationProperties(prefix="dataSource") public DataSource dataSource() { return new DruidDataSource(); }
Spring Boot 目前還無法直接注入的靜態變量。我目前使用的方法是專門建立一個讀取配置文件的Bean,然后使用 @PostConstruct 注解修飾的方法對這些靜態屬性進行初始化,例如:
@Configuration public class ConstantsInitializer { @Value("${paging_size}") private String pagingSize; @PostConstruct public void initConstants() { Constants.PAGING_SIZE = this.pagingSize; } }
Servlet
Servlet 中最重要的配置文件就是 web.xml ,它的主要用途是配置Servlet映射和過濾器。而在 Spring Boot 中這將簡單很多,只需要將對應的 Servlet 和 Filter 定義為 Bean 即可。
聲明一個映射根路徑的 Servlet ,例如 Spring MVC 的 DispatcherServlet :
Spring MVC
Spring MVC 主要的配置都可以通過繼承 WebMvcConfigurerAdapter (或者 WebMvcConfigurationSupport )類進行修改,這兩個類的主要方法有:
Spring MVC 主要的配置都可以通過繼承 WebMvcConfigurerAdapter (或者 WebMvcConfigurationSupport )類進行修改,這兩個類的主要方法有:
Spring MVC 主要的配置都可以通過繼承 WebMvcConfigurerAdapter (或者 WebMvcConfigurationSupport )類進行修改,這兩個類的主要方法有:
addFormatters:增加格式化工具(用於接收參數)configureMessageConverters:配置消息轉換器(用於@RequestBody和@ResponseBody)configurePathMatch:配置路徑映射addArgumentResolvers:配置參數解析器(用於接收參數)addInterceptors:添加攔截器
總之幾乎所有關於 Spring MVC 都可以在這個類中配置。之后只需要將其設為 @Configuration,Spring Boot 就會在運行時加載這些配置。
只需要將其設為 @Configuration,Spring Boot 就會在運行時加載這些配置。
只需要將其設為 @Configuration,Spring Boot 就會在運行時加載這些配置。
只需要將其設為 @Configuration,Spring Boot 就會在運行時加載這些配置。
還有一些常用的 Bean 默認會自動創建,但是可以通過自定義進行覆蓋,例如負責 @RequestBody 和 @RequestBody 進行轉換的 MappingJackson2HttpMessageConverter 和 ObjectMapper ,可以直接這樣覆蓋掉:
@Bean public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() { return new CustomMappingJackson2HttpMessageConverter(); } @Bean public ObjectMapper jsonMapper(){ ObjectMapper objectMapper = new ObjectMapper(); //null輸出空字符串 objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() { @Override public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeString(""); } }); return objectMapper; }
DataSource
如果使用了 spring-boot-starter-data-jpa ,Spring Boot將會自動創建一個 DataSource Bean。可以直接在配置文件中定義它的屬性,前綴是 spring.datasource 。並且無需指定數據庫的方言,這個 Bean 會自動根據項目中依賴的數據庫驅動判斷使用的哪種數據庫。
同樣的,如果使用了 spring-boot-starter-data-redis ,也會自動創建 RedisTemplate 、 ConnectionFactory 等 Bean。也同樣可以在配置文件中定義屬性,前綴是 spring.redis 。
還有一些常用的 Bean 默認會自動創建,但是可以通過自定義進行覆蓋,例如負責 @RequestBody 和 @RequestBody 進行轉換的 MappingJackson2HttpMessageConverter 和 ObjectMapper ,可以直接這樣覆蓋掉:
@Bean public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() { return new CustomMappingJackson2HttpMessageConverter(); } @Bean public ObjectMapper jsonMapper(){ ObjectMapper objectMapper = new ObjectMapper(); //null輸出空字符串 objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() { @Override public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeString(""); } }); return objectMapper; }
DataSource
如果使用了 spring-boot-starter-data-jpa ,Spring Boot將會自動創建一個 DataSource Bean。可以直接在配置文件中定義它的屬性,前綴是 spring.datasource 。並且無需指定數據庫的方言,這個 Bean 會自動根據項目中依賴的數據庫驅動判斷使用的哪種數據庫。
同樣的,如果使用了 spring-boot-starter-data-redis ,也會自動創建 RedisTemplate 、 ConnectionFactory 等 Bean。也同樣可以在配置文件中定義屬性,前綴是 spring.redis 。
springboot用來簡化Spring框架帶來的大量XML配置以及復雜的依賴管理,讓開發人員可以更加關注業務邏輯的開發。
比如不使用springboot而使用SpringMVC作為web框架進行開發的時候,需要配置相關的SpringMVC配置以及對應的依賴,比較繁瑣;而使用springboot的話只需要以下短短的幾行代碼就可以使用SpringMVC,可謂相當地方便:
@RestController class App { @RequestMapping("/") String home() { "hello" } }
其中maven配置如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
我們以使用SpringMVC並且視圖使用freemarker為例,分析springboot內部是如何解析freemarker視圖的。
如果要在springboot中使用freemarker視圖框架,並且使用maven構建項目的時候,還需要加入以下依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
這個spring-boot-starter-freemarker依賴對應的jar包里的文件如下:
META-INF
├── MANIFEST.MF
├── maven
│ └── org.springframework.boot
│ └── spring-boot-starter-freemarker
│ ├── pom.properties
│ └── pom.xml
└── spring.provides
這個spring-boot-starter-parent的pom文件在http://central.maven.org/maven2/org/springframework/boot/spring-boot-starter-parent/1.3.5.RELEASE/spring-boot-starter-parent-1.3.5.RELEASE.pom 里。
它內部也有一個parent:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.3.5.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
這個spring-boot-dependencies的pom文件在http://central.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/1.3.5.RELEASE/spring-boot-dependencies-1.3.5.RELEASE.pom,內部有很多依賴。
其中spring-boot-starter-web內部依賴了spring的兩個spring web依賴:spring-web和spring-webmvc。
spring-boot-starter-web內部還依賴spring-boot-starter,這個spring-boot-starter依賴了spring核心依賴spring-core;還依賴了spring-boot和spring-boot-autoconfigure這兩個。
spring-boot定義了很多基礎功能類,像運行程序的SpringApplication,Logging系統,一些tomcat或者jetty這些EmbeddedServlet容器,配置屬性loader等等。
包括了這些包:
spring-boot-autoconfigure定義了很多自動配置的類,比如jpa,solr,redis,elasticsearch、mongo、freemarker、velocity,thymeleaf等等自動配置的類。
以freemarker為例,看一下它的自動化配置類:
@Configuration // 使用Configuration注解,自動構造一些內部定義的bean
@ConditionalOnClass({ freemarker.template.Configuration.class,
FreeMarkerConfigurationFactory.class }) // 需要freemarker.template.Configuration和FreeMarkerConfigurationFactory這兩個類存在在classpath中才會進行自動配置
@AutoConfigureAfter(WebMvcAutoConfiguration.class) // 本次自動配置需要依賴WebMvcAutoConfiguration這個配置類配置之后觸發。這個WebMvcAutoConfiguration內部會配置很多Wen基礎性的東西,比如RequestMappingHandlerMapping、RequestMappingHandlerAdapter等
@EnableConfigurationProperties(FreeMarkerProperties.class) // 使用FreeMarkerProperties類中的配置
public class FreeMarkerAutoConfiguration {
private static final Log logger = LogFactory
.getLog(FreeMarkerAutoConfiguration.class);
@Autowired
private ApplicationContext applicationContext;
@Autowired
private FreeMarkerProperties properties;
@PostConstruct // 構造之后調用的方法,組要檢查模板位置是否存在
public void checkTemplateLocationExists() {
if (this.properties.isCheckTemplateLocation()) {
TemplateLocation templatePathLocation = null;
List<TemplateLocation> locations = new ArrayList<TemplateLocation>();
for (String templateLoaderPath : this.properties.getTemplateLoaderPath()) {
TemplateLocation location = new TemplateLocation(templateLoaderPath);
locations.add(location);
if (location.exists(this.applicationContext)) {
templatePathLocation = location;
break;
}
}
if (templatePathLocation == null) {
logger.warn("Cannot find template location(s): " + locations
+ " (please add some templates, "
+ "check your FreeMarker configuration, or set "
+ "spring.freemarker.checkTemplateLocation=false)");
}
}
}
protected static class FreeMarkerConfiguration {
@Autowired
protected FreeMarkerProperties properties;
protected void applyProperties(FreeMarkerConfigurationFactory factory) {
factory.setTemplateLoaderPaths(this.properties.getTemplateLoaderPath());
factory.setPreferFileSystemAccess(this.properties.isPreferFileSystemAccess());
factory.setDefaultEncoding(this.properties.getCharsetName());
Properties settings = new Properties();
settings.putAll(this.properties.getSettings());
factory.setFreemarkerSettings(settings);
}
}
@Configuration
@ConditionalOnNotWebApplication // 非Web項目的自動配置
public static class FreeMarkerNonWebConfiguration extends FreeMarkerConfiguration {
@Bean
@ConditionalOnMissingBean
public FreeMarkerConfigurationFactoryBean freeMarkerConfiguration() {
FreeMarkerConfigurationFactoryBean freeMarkerFactoryBean = new FreeMarkerConfigurationFactoryBean();
applyProperties(freeMarkerFactoryBean);
return freeMarkerFactoryBean;
}
}
@Configuration // 自動配置的類
@ConditionalOnClass(Servlet.class) // 需要運行在Servlet容器下
@ConditionalOnWebApplication // 需要在Web項目下
public static class FreeMarkerWebConfiguration extends FreeMarkerConfiguration {
@Bean
@ConditionalOnMissingBean(FreeMarkerConfig.class)
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
applyProperties(configurer);
return configurer;
}
@Bean
public freemarker.template.Configuration freeMarkerConfiguration(
FreeMarkerConfig configurer) {
return configurer.getConfiguration();
}
@Bean
@ConditionalOnMissingBean(name = "freeMarkerViewResolver") // 沒有配置freeMarkerViewResolver這個bean的話,會自動構造一個freeMarkerViewResolver
@ConditionalOnProperty(name = "spring.freemarker.enabled", matchIfMissing = true) // 配置文件中開關開啟的話,才會構造
public FreeMarkerViewResolver freeMarkerViewResolver() {
// 構造了freemarker的ViewSolver,這就是一開始我們分析的為什么沒有設置ViewResolver,但是最后卻還是存在的原因
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
this.properties.applyToViewResolver(resolver);
return resolver;
}
}
}
freemarker對應的配置類:
@ConfigurationProperties(prefix = "spring.freemarker") // 使用配置文件中以spring.freemarker開頭的配置
public class FreeMarkerProperties extends AbstractTemplateViewResolverProperties {
public static final String DEFAULT_TEMPLATE_LOADER_PATH = "classpath:/templates/"; // 默認路徑
public static final String DEFAULT_PREFIX = ""; // 默認前綴
public static final String DEFAULT_SUFFIX = ".ftl"; // 默認后綴
...
}
下面是官網上的freemarker配置:
# FREEMARKER (FreeMarkerAutoConfiguration)
spring.freemarker.allow-request-override=false # Set whether HttpServletRequest attributes are allowed to override (hide) controller generated model attributes of the same name.
spring.freemarker.allow-session-override=false # Set whether HttpSession attributes are allowed to override (hide) controller generated model attributes of the same name.
spring.freemarker.cache=false # Enable template caching.
spring.freemarker.charset=UTF-8 # Template encoding.
spring.freemarker.check-template-location=true # Check that the templates location exists.
spring.freemarker.content-type=text/html # Content-Type value.
spring.freemarker.enabled=true # Enable MVC view resolution for this technology.
spring.freemarker.expose-request-attributes=false # Set whether all request attributes should be added to the model prior to merging with the template.
spring.freemarker.expose-session-attributes=false # Set whether all HttpSession attributes should be added to the model prior to merging with the template.
spring.freemarker.expose-spring-macro-helpers=true # Set whether to expose a RequestContext for use by Spring's macro library, under the name "springMacroRequestContext".
spring.freemarker.prefer-file-system-access=true # Prefer file system access for template loading. File system access enables hot detection of template changes.
spring.freemarker.prefix= # Prefix that gets prepended to view names when building a URL.
spring.freemarker.request-context-attribute= # Name of the RequestContext attribute for all views.
spring.freemarker.settings.*= # Well-known FreeMarker keys which will be passed to FreeMarker's Configuration.
spring.freemarker.suffix= # Suffix that gets appended to view names when building a URL.
spring.freemarker.template-loader-path=classpath:/templates/ # Comma-separated list of template paths.
spring.freemarker.view-names= # White list of view names that can be resolved.
所以說一開始我們加入了一個spring-boot-starter-freemarker依賴,這個依賴中存在freemarker的lib,滿足了FreeMarkerAutoConfiguration中的ConditionalOnClass里寫的freemarker.template.Configuration.class這個類存在於classpath中。
所以就構造了FreeMarkerAutoConfiguration里的ViewResolver,這個ViewResolver被自動加入到SpringMVC中。
同樣地,如果我們要使用velocity模板,springboot內部也有velocity的自動配置類VelocityAutoConfiguration,原理是跟freemarker一樣的。
其他:
Mybatis的autoconfigure是Mybatis提供的springboot的自動配置模塊,由於springboot官方沒有提供mybatis的自動化配置模塊,所以mybatis自己寫了這么一個模塊,觀察它的源碼,發現基本上跟freemarker的autoconfigure模塊一樣,只需要構造對應的實例即可。
總結:
springboot內部提供了很多自動化配置的類,這些類會判斷classpath中是否存在自己需要的那個類,如果存在則會自動配置相關的配置,否則就不會自動配置。
如果我們需要使用一些框架,只需要加入依賴即可,這些依賴內部是沒有代碼的,只是一些對應框架需要的lib,有了這些lib就會觸發自動化配置,於是就能使用框架了。
這一點跟當時看springmvc的時候對response進行json或xml渲染的原理相同。springmvc中的requestmapping注解加上responsebody注解后會返回xml或者json,如果依賴中加入jackson依賴就會轉換成json,如果依賴中加入xstream依賴就會轉換成xml。當然,前提是springmvc中有了這兩種依賴的HttpMessageConverter代碼,這個HttpMessageConverter代碼就相當於springboot中的各種AutoConfiguration。

