springboot原始碼(一)
阿新 • • 發佈:2022-03-24
1.先來談談spring的發展史
這些註解的意義在於可以不用再xml檔案中宣告<bean>標籤,直接就可以在類上加入響應的註解,就可以納入springIOC容器管理。簡化了配置和維護工作。
只需要在xml中新增掃描的路徑就ok了。
xml中仍需配置:<context:component-scan base-package="com.xxx.xxx"/> ,沒有完全擺脫xml。
- 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標誌的類放到一個檔案中,提高效率。
- 首先了解一個前置知識,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 | 宣告請求對應的處理方法 |
- @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例項
// 該註解可以在 類和方法中使用 @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
建立一個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.MysqlDataoracle引入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; } }
看到這兒,我想已經很清楚了。獲取到類的例項,我們自然就可以對它為所欲為了!