1. 程式人生 > >Spring Boot的自動裝配原理

Spring Boot的自動裝配原理

        Spring Boot的“開箱即用”的原則,使得企業應用開發中各種場景的Spring開發更加快速,更加高效,由於配置大量減少,開發效率相得益彰。

  啟動原理:SpringBoot專案會有一個啟動類,這個啟動類會使用@SpringBootApplication宣告。

下面是@SpringBootApplication的原始碼:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy,RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters={
            @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
            @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)})
public @interface SpringBootApplication{
    @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude")
    Class<?>[] exclude() default{};

    @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")
    String[] excludeName() default{};

    @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "scanBasePackageClasses")
    Class<?>[] scanBasePackageClasses() default{};
}

        @SpringbootApplication其實是一個組合註解,注意三個註解:(@EnableAutoConfiguration、@ComponentScan和@SpringBootConfiguration)

  1.   @EnableAutoConfiguration:啟動註解,該註解會讓SpringBoot根據當前專案所依賴的jar包自動配置到專案中;
  2.   @ComponentScan:自動掃描,SpringBoot預設會掃描@SpringbootApplication所在類的同級包,以及它的子包,因此建議將@SpringbootApplication修飾的入口類放在專案包(Group Id + Artifact Id)下,這樣可以保證SpringBoot專案可以自動掃描所有依賴的包;
  3.   @SpringBootConfiguration:原始碼如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration{

}

       @SpringBootConfiguration其實又是一個組合註解,又注意一個註解:@Configuration,

  使用@Configuration宣告的類,這個類就相當於一個xml配置檔案。這樣就容易理解了,我們使用Spring,springMVC時都會使用xml配置檔案去載入相關依賴jar包的類,而SpringBoot使用@SpringBootConfiguration(推薦使用,來代替@Configuration),然後自動掃描(@ComponentScan),自動配置(@EnableAutoConfiguration)。

  下面就來講述其自動配置原理:

  上面講到一個使用了@SpringBootApplication宣告的專案啟動類,這個啟動類有一個main方法,是程式的入口,main方法下面會建立SpringApplication類的run()方法(即SpringApplication.run(App.class,args),App.class是專案啟動類)

run()方法原始碼:

public static ConfigurableApplicationContext run(Object[] sources, String[] args){
    return new SpringApplication(sources).run(args);
}
  可以看到,run方法實際上是建立SpringApplication例項,然後又呼叫run方法,重點在於建立SpringApplication物件,下面是SpringApplication的構造方法原始碼:
public SpringApplication(Object... sources){
    initialize(sources);
}

  以啟動類作為引數,呼叫初始化方法initialize(sources),initialize(sources)原始碼:

@SuppressWarnings({"unchecked", "rawtypes"})
private void initialize(Object[] sources){
    if(sources != null && sources.length>0){
        this.sources.addAll(Arrays.asList(sources));
    }
    .
    .
    .
    setListeners(Collection) getSpringFactoriesInstances(ApplicationListener.class);
    .
    .
    .
}

其他原始碼不是這裡的重點,接著再進入getSpringFactoriesInstances方法的原始碼:

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args){
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    Set<String> names = new LinkedHashSet<String>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    .
    .
    .
}

再進入SpringFactoriesLoader的loadFactoryNames方法的原始碼: 

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader){
    String factoryClassName = factoryClass.getName();
    try{
        Enumeration<URL> urls = (classLoader != null ? classLoader.getSources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        List<String> result = new ArrayList<String>();
        while(urls.hasMoreElements()){
            URL url = urls.nextElement();
            Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
            String factoryClassNames = properties.getProperty(factoryClassName);
            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassName)));
        }
        return result;
    }catch (IOException ex){
        throw new IllegalArgumentException("Unable to load ["+factoryClass.getName() + "]", ex);
    }
} 

 上面原始碼用到了一個常量:FACTORIES_RESOURCE_LOCATION,這個常量原始碼如下:

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

到目前為止,已經知道是怎樣的了,最終SpringBoot是通過載入META-INF/spring.factories檔案進行自動配置,這個檔案是放在spring-boot-autoconfigure包下面的META-INF/spring.factories,該 檔案時官方檔案,裡面寫了很多相關類名,供其掃描,自動配置。譬如spring.factories檔案下面有一行程式碼,這行程式碼是一個配置類的類名,叫做org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,它會根據這個全路徑類名,找到這個類,進行載入。

可以看一下org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration的原始碼: 

@Configuration
@ConditionalOnWebApplication
@ConditionOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class})
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration{
    ...
}

   一開始也講過了,使用了@Configuration的類,相當於一個xml檔案,@ContionalOnClass是一個條件註解,意思是隻有當當前專案執行環境中有Servlet類,並且有DispatcherServlet類,以及WebMvcConfigurerAdapter類,SpringBoot才會初始化載入這個類,說白了這個類,就相當於spring-servlet.xml檔案。既然是spring-servlet.xml檔案,那麼肯定也會找到很多<bean/>配置類,這裡拿檢視配置類InternalResourceViewResolver,舉個例子,

  在spring-servlet.xml檔案裡面,我們通常是這樣配置宣告InternalResourceViewResolver的:

<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/view"/>
    <property name="suffix" value=".jsp"/>
</bean>

  到了WebMvcAutoConfiguration類,下面可以找到一個defaultViewResolver方法,可以看一下原始碼:

@Bean
@ContionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver(){
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix(this.mvcProperties.getView().getPrefix());
    resolver.setSuffix(this.mvcProperties.getView().getSuffix());
    return resolver;
}

@Bean,其實就相當於<bean/>標籤,defaultViewResolver方法得返回值型別就是InternalResourceViewResolver類,裡面的程式碼就是負責建立InternalResourceViewResolver物件,前後對比一下,清晰了很多,SpringBoot就是把Spring技術的配置換成了載入類,直接封裝起來。這裡有個問題,prefix和suffix的值怎麼確定呢?很簡單,就是到application.properties配置,對於springboot2.x,配置資訊如下:

spring.mvc.view.prefix = /WEB-INF/view
spring.mvc.view.suffix= .jsp

  總結:自動配置原理:從classpath中搜尋所有的META-INF/spring.factories配置檔案,並將其中org.springframework.boot.autoconfigure.EnableAutoConfiguration對應的配置項,通過反射機制例項化為IOC容器配置類(這些配置類都是使用了@Configuration註解宣告的),然後彙總並載入到Spring框架的IOC容器。