常規(guī)的Java項目,很多都是基于SSH/SSM三大框架搭建,開發(fā)起來顯得格外的笨重:繁多的配置、低下的開發(fā)效率、復(fù)雜的部署流程以及第三方技術(shù)集成難度大。在上述環(huán)境下,Sping boot應(yīng)運而生,它使用“習(xí)慣優(yōu)于配置”(項目中存在大量的配置,此外還內(nèi)置一個習(xí)慣性的配置,自動配置機制可以讀取默認(rèn)的配置,讓你無需手動配置)的理念讓你的項目快速運行起來,Spring boot可以不用或者只用很少的spring配置。但是由于Spring boot將配置封裝了起來,就喪失了SSH配置的靈活性,造成維護(hù)的困難。可是如果我們熟悉Spring boot的自動配置運作原理,修改Spring boot參數(shù)配置將和SSH一樣。
在講解Spring boot自動配置運作原理之前,有必要先溫習(xí)一下Spring的知識
Spring boot常用注解
- @configuation
聲明當(dāng)前類是一個配置類 - @ComponentScan
自動掃描包名下所使用的@Controller、@Service、@Repository、@Component的類,并注冊為Bean - @PropertySource
注入配置文件 - @Conditional
根據(jù)特定條件控制Bean的創(chuàng)建行為,這樣我們可以利用這個特性進(jìn)行一些自動的配置(敲黑板,Spring的自動配置就是利用了這個注解)
Java配置
Java配置是Java 4.x推薦的配置方式,可以完全替代xml配置,Java配置也是Spring boot推薦的配置方式。
Java是通過@Configuation和@Bean來實現(xiàn)
- @Configuation聲明當(dāng)前類是一個配置文件,相當(dāng)于一個spring配置的xml文件
- @Bean注解在方法上,聲明當(dāng)前方法的返回值為一個Bean
關(guān)于何時使用Java配置或者注解配置呢?一般全局配置使用Java配置(如數(shù)據(jù)庫相關(guān)配置),業(yè)務(wù)Bean配置使用注解(如@Controller、@Service、@Component)
@Enable*注解的工作原理
如@EnableScheduling、@EnableWebMvc注解,來開啟一項功能的支持,從而避免自己配置大量的代碼,大大降低使用難度。那么這個神奇功能的原理是什么呢?通過觀察@Enable*注解的源碼,這些注解里面都有一個@Import注解,@Import注解是用來導(dǎo)入配置類的,這也意味著這些自動開啟的實現(xiàn)其實是導(dǎo)入了一些自動配置的Bean。
組合注解
隨著注解的大量使用,尤其相同的注解越來越多,會顯得很啰嗦,這就所謂的模板代碼,在Spring設(shè)計原則中是要消除的代碼。把注解注解到別的注解上形成的新的注解,就叫做組合注解。
用一個例子結(jié)束上面的內(nèi)容,例子盡可以覆蓋以上的內(nèi)容
#annotation.properties
program.type=python
#JavaConditional.java
@PropertySource("classpath:annotation.properties")
public class JavaConditional implements Condition{
@Value("${program.type}")
private String programType;
@Override
public boolean matches(ConditionContext arg0, AnnotatedTypeMetadata arg1) {
//return "java".equals(programType);
//不知道為什么獲取不到programType,為了能夠繼續(xù)調(diào)試,先寫死
return true;
}
}
#PythonConditional .java
@PropertySource("classpath:demo.properties")
public class PythonConditional implements Condition{
@Value("${program.type}")
private String programType;
@Override
public boolean matches(ConditionContext arg0, AnnotatedTypeMetadata arg1) {
return "python".equals(programType);
}
}
#JavaConfiguation.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Bean
@Conditional(JavaConditional.class)
public @interface JavaConfiguation {
}
#PythonConfiguation.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Bean
@Conditional(PythonConditional.class)
public @interface PythonConfiguation {
}
#ConditionConfig.java
@Configuration
public class ConditionConfig {
@JavaConfiguation
public ProgramLearnService javaLearnService() {
return new JavaLearnServiceImpl();
}
@PythonConfiguation
public ProgramLearnService pythonLearnService() {
return new PythonLearnServiceImpl();
}
}
#ProgramLearnService.java
public interface ProgramLearnService {
public String learn();
}
#JavaLearnServiceImpl.java
public class JavaLearnServiceImpl implements ProgramLearnService {
@Override
public String learn() {
return "I learn java";
}
}
#PythonLearnServiceImpl.java
public class PythonLearnServiceImpl implements ProgramLearnService {
@Override
public String learn() {
return "I learn python";
}
}
#App.java
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
ConditionConfig.class);
ProgramLearnService pl = context.getBean(ProgramLearnService.class);
System.out.println(pl.learn());
context.close();
}
}
分界線 -----------,以下開始Spring boot部分
類型安全的配置(基于properties)
在常規(guī)spring環(huán)境下,注入properties文件里值的方式,通過@propertiesSource指明值的位置,然后通過@Value注入值,在Spring boot中只需要把值寫到application.properties, 直接用@Value注入值即可。
盡管方便了一點,可是在實際項目中,配置有很多,就要配置很多的@Value,顯得格外麻煩,所以Spring boot提供了基于類型安全的配置方式,通過@ConfiguationProperties將propertis屬性和一個bean及其屬性關(guān)聯(lián)。
#application.properties
author.name=jack
author.age=18
#AuthorSettings.java
@Component
@ConfigurationProperties(prefix="author")
public class AuthorSettings {
private String name;
private Integer age;
//setter、getter
}
#通過@ConfigurationProperties加載Propertis文件內(nèi)的配置,通過prefix屬性指定properties內(nèi)配置的前綴,通過locations屬性指定properties文件的位置,將AuthorSettings這個Bean注入到其他內(nèi)即可使用,方便吧
Spring boot運作原理
關(guān)于Spring boot的運作原理,得從@SpringBootApplication講起,這個注解是一個組合注解,它的核心功能是由@EnableAutoConfiguration注解提供的。來看看@EnableAutoConfiguration源碼
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
這里的關(guān)鍵功能是@Import注解導(dǎo)入的配置功能,EnableAutoConfigurationImportSelector使用SpringFactoriesLoader.loadFactoryNames方法來掃描具有MEAT-INF/spring.factories文件的jar包(1.5版本以前使用EnableAutoConfigurationImportSelector類,1.5以后這個類廢棄了使用的是AutoConfigurationImportSelector類),而我們的spring-boot-autoconfigure-1.5.3.RELEASE.jar下面就有spring.factories文件,此文件中聲明了有哪些自動配置。
spring.factories文件:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
核心注解
打開任意*AutoConfiguration文件,一般都有下面的條件注解,在spring-boot-autoconfigure-1.5.3.RELEASE.jar的org.springframework.boot.autoconfigure.condition包下條件注解如下:
@ConditionalOnBean:當(dāng)前容器有指定Bean的條件下。
@ConditionalOnClass:當(dāng)前類路徑下有指定的類的條件下。
@ConditionalOnExpression:基于SpEL表達(dá)式作為判斷條件。
@ConditionalOnJava:基于JVM版本作為判斷條件。
@ConditionalOnJndi:在JNDI存在的條件下查找指定的位置。
@ConditionalOnMissingBean:當(dāng)容器里沒有指定Bean的情況下。
@ConditionalOnMissingClass:當(dāng)類路徑下沒有指定的類的條件下。
@ConditionalOnNotWebApplication:當(dāng)前項目不是WEB項目的條件下。
@ConditionalOnProperty:指定屬性是否有指定的值。
@ConditionalOnResource:類路徑是否有指定的值。
@ConditionalOnSingleCandidate:當(dāng)指定Bean在容器中只有一個,或者雖然有多個但 是指定首選的Bean。
@ConditionalOnWebApplication:當(dāng)前項目是WEB項目的條件下。
這些注解都組合了@Conditional元注解,只是使用了不同的條件(Conditional),Spring 條件注解(@Conditional)我們介紹過根據(jù)不同條件創(chuàng)建不同Bean
實戰(zhàn)
最后,寫一個例子,實現(xiàn)自動裝載,而且寫一個starter pom
spring-boot-starter-hello工程
# pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springwork.boot</groupId>
<artifactId>spring-boot-starter-hello</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>1.3.0.M1</version>
</dependency>
</dependencies>
</project>
# HelloServiceProperties.java
package hello;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix="hello")
public class HelloServiceProperties {
private String msg = "world";
//setter、getter
}
# HelloService.java
package hello;
public class HelloService {
private String msg;
public String sayHello() {
return "hello " + msg;
}
//setter、getter
}
# HelloServiceAutoConfiguation.java
package hello;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(HelloServiceProperties.class)
@ConditionalOnClass(HelloService.class)
//@ConditionalOnProperty(prefix="hello", value="enabled", matchIfMissing=true)
public class HelloServiceAutoConfiguation {
@Autowired
private HelloServiceProperties helloServiceProperties;
@Bean
@ConditionalOnMissingBean(HelloService.class)
public HelloService helloService() {
HelloService helloService = new HelloService();
helloService.setMsg(helloServiceProperties.getMsg());
return helloService;
}
}
# spring.factories(放在src.main.resource/META-INF路徑下)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
hello.HelloServiceAutoConfiguation
其他工程,依賴spring-boot-starter-hello工程
# pom.xml
<dependency>
<groupId>org.springwork.boot</groupId>
<artifactId>spring-boot-starter-hello</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
#SpringbootController.java
package base.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import hello.HelloService;
@RestController
@RequestMapping("springboot")
public class SpringbootController {
@Autowired
HelloService helloService;
@RequestMapping("autoConfigTest")
public String autoConfigTest() {
return helloService.sayHello();
}
}

參考文獻(xiàn)《JavaEE開發(fā)的顛覆者 Spring Boot實戰(zhàn)》汪云飛編著