SpringBoot核心註解原理java
今天跟你們來探討下SpringBoot的核心註解@SpringBootApplication以及run方法,理解下springBoot爲何不須要XML,達到零配置git
首先咱們先來看段代碼github
@SpringBootApplication public class StartEurekaApplication { public static void main(String[] args) { SpringApplication.run(StartEurekaApplication.class, args); } }
咱們點進@SpringBootApplication來看web
@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 { }
上面的元註解咱們在這裏不在作解釋,相信你們在開發當中確定知道,咱們要來講@SpringBootConfiguration @EnableAutoConfiguration 這兩個註解,到這裏咱們知道 SpringBootApplication註解裏除了元註解,咱們能夠看到又是@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan的組合註解,官網上也有詳細說明,那咱們如今把目光投向這三個註解。redis
首先咱們先來看 @SpringBootConfiguration,那咱們點進來看spring
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { }
咱們能夠看到這個註解除了元註解之外,就只有一個@Configuration,那也就是說這個註解至關於@Configuration,因此這兩個註解做用是同樣的,那他是幹嗎的呢,相信不少人都知道,它是讓咱們可以去註冊一些額外的Bean,而且導入一些額外的配置。那@Configuration還有一個做用就是把該類變成一個配置類,不須要額外的XML進行配置。因此@SpringBootConfiguration就至關於@Configuration。tomcat
那咱們繼續來看下一個@EnableAutoConfiguration,這個註解官網說是 讓Spring自動去進行一些配置,那咱們點進來看springboot
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(EnableAutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { }
能夠看到它是由 @AutoConfigurationPackage,@Import(EnableAutoConfigurationImportSelector.class)這兩個而組成的,咱們先說@AutoConfigurationPackage,他是說:讓包中的類以及子包中的類可以被自動掃描到spring容器中。 咱們來看@Import(EnableAutoConfigurationImportSelector.class)這個是核心,以前咱們說自動配置,那他到底幫咱們配置了什麼,怎麼配置的?就和@Import(EnableAutoConfigurationImportSelector.class)息息相關,程序中默認使用的類就自動幫咱們找到。咱們來看EnableAutoConfigurationImportSelector.classapp
public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector { @Override protected boolean isEnabled(AnnotationMetadata metadata) { if (getClass().equals(EnableAutoConfigurationImportSelector.class)) { return getEnvironment().getProperty( EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true); } return true; } }
能夠看到他繼承了AutoConfigurationImportSelector咱們繼續來看AutoConfigurationImportSelector,這個類有一個方法less
public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } try { AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); configurations = sort(configurations, autoConfigurationMetadata); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return configurations.toArray(new String[configurations.size()]); } catch (IOException ex) { throw new IllegalStateException(ex); } }
這個類會幫你掃描那些類自動去添加到程序當中。咱們能夠看到getCandidateConfigurations()這個方法,他的做用就是引入系統已經加載好的一些類,究竟是那些類呢,咱們點進去看一下
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames( getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; }
這個類回去尋找的一個目錄爲META-INF/spring.factories,也就是說他幫你加載讓你去使用也就是在這個META-INF/spring.factories目錄裝配的,他在哪裏?
咱們點進spring.factories來看
咱們能夠發現幫咱們配置了不少類的全路徑,好比你想整合activemq,或者說Servlet
能夠看到他都已經幫咱們引入了進來,我看隨便拿幾個來看
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\
好比咱們常常用的security,能夠看到已經幫你配置好,因此咱們的EnableAutoConfiguration主要做用就是讓你自動去配置,但並非全部都是建立好的,是根據你程序去進行決定。 那咱們繼續來看
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
這個註解你們應該都不陌生,掃描包,放入spring容器,那他在springboot當中作了什麼策略呢?咱們能夠點跟煙去思考,幫咱們作了一個排除策略,他在這裏結合SpringBootConfiguration去使用,爲何是排除,由於不可能一上來所有加載,由於內存有限。
那麼咱們來總結下@SpringbootApplication:就是說,他已經把不少東西準備好,具體是否使用取決於咱們的程序或者說配置,那咱們到底用不用?那咱們繼續來看一行代碼
public static void main(String[] args) { SpringApplication.run(StartEurekaApplication.class, args); }
那們來看下在執行run方法到底有沒有用到哪些自動配置的東西,好比說內置的Tomcat,那咱們來找找內置Tomcat,咱們點進run
public static ConfigurableApplicationContext run(Object[] sources, String[] args) { return new SpringApplication(sources).run(args); }
而後他調用又一個run方法,咱們點進來看
public ConfigurableApplicationContext run(String... args) { //計時器 StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; FailureAnalyzers analyzers = null; configureHeadlessProperty(); //監聽器 SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); Banner printedBanner = printBanner(environment); //準備上下文 context = createApplicationContext(); analyzers = new FailureAnalyzers(context); //預刷新context prepareContext(context, environment, listeners, applicationArguments, printedBanner); //刷新context refreshContext(context); //刷新以後的context afterRefresh(context, applicationArguments); listeners.finished(context, null); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } return context; } catch (Throwable ex) { handleRunFailure(context, listeners, analyzers, ex); throw new IllegalStateException(ex); } }
那咱們關注的就是 refreshContext(context); 刷新context,咱們點進來看
private void refreshContext(ConfigurableApplicationContext context) { refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } }
咱們繼續點進refresh(context);
protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext); ((AbstractApplicationContext) applicationContext).refresh(); }
他會調用 ((AbstractApplicationContext) applicationContext).refresh();方法,咱們點進來看
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }
這點代碼似曾相識啊 沒錯,就是一個spring的bean的加載過程我在,解析springIOC加載過程的時候介紹過這裏面的方法,若是你看過Spring源碼的話 ,應該知道這些方法都是作什麼的。如今咱們不關心其餘的,咱們來看一個方法叫作 onRefresh();方法
protected void onRefresh() throws BeansException { // For subclasses: do nothing by default. }
他在這裏並無實現,可是咱們找他的其餘實現,咱們來找
咱們既然要找Tomcat那就確定跟web有關,咱們能夠看到有個ServletWebServerApplicationContext
@Override protected void onRefresh() { super.onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } }
咱們能夠看到有一個createWebServer();方法他是建立web容器的,而Tomcat不就是web容器,那他是怎麼建立的呢,咱們繼續看
private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { ServletWebServerFactory factory = getWebServerFactory(); this.webServer = factory.getWebServer(getSelfInitializer()); } else if (servletContext != null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); }
factory.getWebServer(getSelfInitializer());他是經過工廠的方式建立的
public interface ServletWebServerFactory { WebServer getWebServer(ServletContextInitializer... initializers); }
能夠看到 它是一個接口,爲何會是接口。由於咱們不止是Tomcat一種web容器。
咱們看到還有Jetty,那咱們來看TomcatServletWebServerFactory
@Override public WebServer getWebServer(ServletContextInitializer... initializers) { Tomcat tomcat = new Tomcat(); File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); configureEngine(tomcat.getEngine()); for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } prepareContext(tomcat.getHost(), initializers); return getTomcatWebServer(tomcat); }
那這塊代碼,就是咱們要尋找的內置Tomcat,在這個過程中,咱們能夠看到建立Tomcat的一個流程。由於run方法裏面加載的東西不少,因此今天就淺談到這裏。若是不明白的話, 咱們在用另外一種方式來理解下,你們要應該都知道stater舉點例子
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency>
因此咱們不防不定義一個stater來理解下,咱們作一個需求,就是定製化不一樣的人跟你們說大家好,咱們來看
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath/> </parent> <groupId>com.zgw</groupId> <artifactId>gw-spring-boot-srater</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> </dependencies>
咱們先來看maven配置寫入版本號,若是自定義一個stater的話必須依賴spring-boot-autoconfigure這個包,咱們先看下項目目錄
public class GwServiceImpl implements GwService{ @Autowired GwProperties properties; @Override public void Hello() { String name=properties.getName(); System.out.println(name+"說:大家好啊"); } }
咱們作的就是經過配置文件來定製name這個是具體實現,
@Component @ConfigurationProperties(prefix = "spring.gwname") public class GwProperties { String name="zgw"; public String getName() { return name; } public void setName(String name) { this.name = name; } }
這個類能夠經過@ConfigurationProperties讀取配置文件
@Configuration @ConditionalOnClass(GwService.class) //掃描類 @EnableConfigurationProperties(GwProperties.class) //讓配置類生效 public class GwAutoConfiguration { /** * 功能描述 託管給spring * @author zgw * @return */ @Bean @ConditionalOnMissingBean public GwService gwService() { return new GwServiceImpl(); } }
這個爲配置類,爲何這麼寫由於,spring-boot的stater都是這麼寫的,咱們能夠參照他仿寫stater,以達到自動配置的目的,而後咱們在經過spring.factories也來進行配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gw.GwAutoConfiguration
而後這樣一個簡單的stater就完成了,而後能夠進行maven的打包,在其餘項目引入就可使用,在這裏列出代碼地址
https://github.com/zgw1469039806/gwspringbootsrater
到此本文結束,歡迎轉載,請標註出處和做者,謝謝!