1. 程式人生 > 其它 >springboot原始碼(一)

springboot原始碼(一)

1.先來談談spring的發展史
  • spring的註解發展史
    • 2004--》spring1.0誕生,完全基於xml的形式,此時只有一個註解@Transcation
    • 2006--》spring2.0誕生,新增了很多重要的註解 eg:@Controller @Service @Repository @Component @RequestMapping @Autowired @Required @Aspect等 此時雖然可以簡化xml配置,但是不能完全脫離xml。
    • 2009--》spring3.0誕生,新增了@import @ComponentScan , @ComponentScan這個註解可以取代之前的<context:component-scan>標籤 ,真正的可以脫離xml。
    • 2013--》spring4.0誕生,新增了@Conditional ,這個註解實現了可以根據條件來決定是否載入類。
    • 2017--》spirng5.0誕生,新增了@Indexed,隨著使用@ComponentScan的增多,要檢索的配置越來越多,速度慢,所以這個註解可以把所有的Component標誌的類放到一個檔案中,提高效率。
  2.spring常用的核心註解
  • 首先了解一個前置知識,java的4種元註解 (具體參考部落格:https://blog.csdn.net/weixin_29010003/article/details/114768402
    • @Target --》表示該註解可以被用到什麼地方
    • @Retention --》表示該註解保留的時間段
    • @Documented --》表示可以被javadoc工具提取成文件
    • @Inherited --》表示可以被子類繼承
 
  • @Autowired、@Component、@Controller、@Service、@Repository、@RequestMapping
@Autowired 自動注入
@Component 宣告元件
@Controller 宣告控制層元件
@Service 宣告服務層元件
@Repository 宣告持久層元件
@RequestMapping 宣告請求對應的處理方法
這些註解的意義在於可以不用再xml檔案中宣告<bean>標籤,直接就可以在類上加入響應的註解,就可以納入springIOC容器管理。簡化了配置和維護工作。 只需要在xml中新增掃描的路徑就ok了。 xml中仍需配置:<context:component-scan base-package="com.xxx.xxx"/> ,沒有完全擺脫xml。  
  • @Import 可以快速的把類加到springIOC容器中 類似的註解有@Bean @Import可以用於引入第三方包 (springboot自動裝配會用到)
    • 直接引入
      • 優點:簡單,直接。
      • 缺點:如果需要匯入很多包,不靈活。
@Service
public class Sleep{ 
}

@Configuration
@Import(value={Sleep.class})
public class PersonConfig{
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);
        for (String beanDefinitionName : ac.getBeanDefinitionNames()) {
            System.out.println("beanDefinitionName = " + beanDefinitionName);
        }
    }
}
  • 實現ImportSelector

    • 把需要新增到IOC容器的物件對應的全類路徑加到字串陣列中,可以根據不同的業務需求新增不同的型別,更加靈活。
@Service
public class Sleep{}
@Service
public class Play{}

public class MyImportSelector implements ImportSelect{
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{Sleep.class.getName(),Play.class.getName()};  //將需要新增進來的bean,放到String[]中
    }
}

@Configuration
@Import(value={MyImportSelector.class})
public class PersonConfig{
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);
        for (String beanDefinitionName : ac.getBeanDefinitionNames()) {
            System.out.println("beanDefinitionName = " + beanDefinitionName);
        }
    }
}
    • 實現ImportBeanDefinitionRegistrar
      • 在方法中提供了BeanDefinitionRegistry,自己在方法中實現註冊。

 

@Service
public class Sleep{}
@Service
public class Play{}

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 將需要註冊的物件封裝為 RootBeanDefinition 物件
        RootBeanDefinition cache = new RootBeanDefinition(Sleep.class);
        registry.registerBeanDefinition("cache",cache);

        RootBeanDefinition logger = new RootBeanDefinition(Play.class);
        registry.registerBeanDefinition("logger",logger);
    }
}

@Configuration
@Import(value={MyImportBeanDefinitionRegistrar.class})
public class PersonConfig{
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);
        for (String beanDefinitionName : ac.getBeanDefinitionNames()) {
            System.out.println("beanDefinitionName = " + beanDefinitionName);
        }
    }
}
  • @ComponentScan
    • 取代了<context:component-scan base-package="com.xxx.xxx"/>
    • 預設掃描當前包以其子包下的類
package com.syc.demo
@Service
public class Demo{}

package com.syc.test
@Configuration
@ComponentScan(value={"com.syc.demo"})
public class PersonConfig{
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);
        System.out.println("ac.getBean(Demo.class) = " + ac.getBean(Demo.class));
    }
}

 

package com.syc.test
@Service
public class Sleep{}

package com.syc.test.lala
public class Play{}

package com.syc.test
@Configuration
@ComponentScan
public class PersonConfig{
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);
        System.out.println("ac.getBean(Play.class) = " + ac.getBean(Play.class));
        System.out.println("ac.getBean(Sleep.class) = " + ac.getBean(Sleep.class));
    }
}
  • @EnableXXXX
    • 系統中有很多定義好的功能獨立的模組,與@Import 一起使用開啟該模組
    • 自定義@EnableXXXX

 

@Configuration
public class PersonConfig{
    public Person person(){
        return new Person();
    }
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(PersonConfig.class)
public @interface EnableCreatePerson{
    
}


@Configuration
@EnableCreatePerson
public class Test{
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaMian.class);
        Person gouren = ac.getBean("person", Person.class);
        System.out.println("hello = " + gouren.getName());
    }
}

 

 

  • @Conditional (springboot自動裝配會用到)
    • 按照條件給容器註冊bean例項
Conditional原始碼:

 

// 該註解可以在 類和方法中使用
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

    /**
     * 註解中新增的型別必須是 實現了 Condition 介面的型別
     */
    Class<? extends Condition>[] value();

}

 

 例子:

/**
 * 定義一個 Condition 介面的類,實現matches方法
 * 返回true注入bean,返回false不注入
 */
public class MyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return false; // 預設返回false
    }
}


@Configuration
public class JavaConfig {
    @Bean
    // 條件註解,新增的型別必須是 實現了 Condition 介面的型別
    // MyCondition的 matches 方法返回true 則注入,返回false 則不注入
    @Conditional(MyCondition.class)
    public StudentService studentService(){
        return new StudentService();
    }

    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);
        for (String beanDefinitionName : ac.getBeanDefinitionNames()) {
            System.out.println("beanDefinitionName = " + beanDefinitionName);
        }
    }
}

輸出結果中沒有StudentService

如果把MyConditional中的matches方法中的結果返回為true
輸出結果中有StudentService
  • @Indexed
    • 為什麼要引入@Indexed註解?
      • 在Springboot應用場景中,大量使用@ComponentScan掃描,使得Spring模式的註解解析時間變得很長,因此引入@Indexed,為Spring模式註解新增索引。
    • 引入@Indexed之後會怎樣?
      • 在編譯的過程中會自動生成META-INT/spring.components檔案,當Spring執行ComponentScan掃描時,META-INT/spring.components檔案將會被CadidateComponentsIndexLoader讀取並載入,轉換為CadidateComponentsIndex物件,這樣就不需要在掃描@ComponentScan指定的package,而是直接從CadidateComponentsIndex物件中讀取,從而提升效能。

 

 

 

3.什麼是SPI?
  • Springboot自動裝配機制中有用到了SPI,所以瞭解一下SPI對後面學習Springboot原始碼很有幫助。
  • SPI就是Service Provider Interface,是一種服務發現機制。
    • 通過在指定的路徑中查詢檔案,自動載入檔案裡所定義的類。
  • 舉例說明
    • JDBC
有Mysql、也有Oracle,各自的實現不同,我只需要提供一個介面,實現由Mysql或者Oracle來幫我實現,然後把實現的類的全路徑名放到META-INF.services下,檔名為我提供的介面的全路徑名的檔案裡。

 

 

建立一個A工程,先自定義一個公共介面
package com.syc.spi
public interface BaseData{
    public void baseURL();
}
打成一個jar包
mysql引入jar包,實現BaseData接口裡的baseURL方法

 

建立一個mysql工程
package com.syc.spi.mysql
public class MysqlData implements BaseData{
    public void baseURL(){
        System.out.println("mysql.......");
    }
}

在MysqlData這個工程中的resources目錄中建立META-INF.services/com.syc.spi.BaseData檔案,檔案中寫入com.syc.spi.mysql.MysqlData
oracle引入jar包,實現BaseData接口裡的baseURL方法

 

建立一個oracle工程
package com.syc.spi.oracle
public class OracleData implements BaseData{
    public void baseURL(){
        System.out.println("oracle.......");
    }
}

在OracleData這個工程中的resources目錄中建立META-INF.services/com.syc.spi.BaseData檔案,檔案中寫入com.syc.spi.oracle.OracleData

 

 A工程測試:

public static void main(String[] args) {
        ServiceLoader<BaseData> providers = ServiceLoader.load(BaseData.class);
        Iterator<BaseData> iterator = providers.iterator();
        while(iterator.hasNext()){
            BaseData next = iterator.next();
            next.baseURL();
        }
    }
    
    如果引入的依賴是mysql的,執行結果為mysql......
    如果引入的依賴是oracle的,執行結果為oracle......

 

 ServiceLoader原始碼:

首先看下ServiceLoader類的結構
    // 配置檔案的路徑
    private static final String PREFIX = "META-INF/services/";

    // 載入的服務  類或者介面
    private final Class<S> service;

    // 類載入器
    private final ClassLoader loader;

    // 訪問許可權的上下文物件
    private final AccessControlContext acc;

    // 儲存已經載入的服務類
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // 內部類,真正載入服務類
    private LazyIterator lookupIterator;

 

 

load   load方法建立了一些屬性,重要的是例項化了內部類,LazyIterator。
public final class ServiceLoader<S> implements Iterable<S>
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        //要載入的介面
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        //類載入器
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        //訪問控制器
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
         reload();
        
    }
    public void reload() {
        //先清空
        providers.clear();
        //例項化內部類 
        LazyIterator lookupIterator = new LazyIterator(service, loader);
    }
}

 

查詢實現類和建立實現類的過程,都在LazyIterator完成。當我們呼叫iterator.hasNext和iterator.next方法的時候,實際上呼叫的都是LazyIterator的相應方法。

 

private class LazyIterator implements Iterator<S>{
    Class<S> service;
    ClassLoader loader;
    Enumeration<URL> configs = null;
    Iterator<String> pending = null;
    String nextName = null; 
    private boolean hasNextService() {
        //第二次呼叫的時候,已經解析完成了,直接返回
        if (nextName != null) {
            return true;
        }
        if (configs == null) {
            //META-INF/services/ 加上介面的全限定類名,就是檔案服務類的檔案
            //META-INF/services/com.viewscenes.netsupervisor.spi.SPIService
            String fullName = PREFIX + service.getName();
            //將檔案路徑轉成URL物件
            configs = loader.getResources(fullName);
        }
        while ((pending == null) || !pending.hasNext()) {
            //解析URL檔案物件,讀取內容,最後返回
            pending = parse(service, configs.nextElement());
        }
        //拿到第一個實現類的類名
        nextName = pending.next();
        return true;
    }
}
建立例項物件,當然,呼叫next方法的時候,實際呼叫到的是,lookupIterator.nextService。它通過反射的方式,建立實現類的例項並返回。

 

private class LazyIterator implements Iterator<S>{
    private S nextService() {
        //全限定類名
        String cn = nextName;
        nextName = null;
        //建立類的Class物件
        Class<?> c = Class.forName(cn, false, loader);
        //通過newInstance例項化
        S p = service.cast(c.newInstance());
        //放入集合,返回例項
        providers.put(cn, p);
        return p; 
    }
}

 

 

看到這兒,我想已經很清楚了。獲取到類的例項,我們自然就可以對它為所欲為了!