天天看點

spring security 注解_深入 Spring Boot 核心注解原理

spring security 注解_深入 Spring Boot 核心注解原理

作 者:小毛毛

來 源:Java知音

廣而告之:由于此訂閱号換了個皮膚,系統自動取消了讀者的公衆号置頂。導緻使用者接受文章不及時。您可以打開訂閱号,選擇置頂公衆号。重磅幹活,第一時間送達!

今天跟大家來探讨下SpringBoot的核心注解@SpringBootApplication以及run方法,了解下springBoot為什麼不需要XML,達到零配置

首先我們先來看段代碼

@SpringBootApplication              public class StartEurekaApplication              {              public static void main(String[] args)              {              SpringApplication.run(StartEurekaApplication.class, args);              }              }
           

我們點進@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 {              }
           

上面的元注解我們在這裡不在做解釋,相信大家在開發當中肯定知道,我們要來說@SpringBootConfiguration @EnableAutoConfiguration 這兩個注解,到這裡我們知道 SpringBootApplication注解裡除了元注解,我們可以看到又是@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan的組合注解,官網上也有詳細說明,那我們現在把目光投向這三個注解。

首先我們先來看 @SpringBootConfiguration,那我們點進來看

@Target(ElementType.TYPE)              @Retention(RetentionPolicy.RUNTIME)              @Documented              @Configuration              public @interface SpringBootConfiguration {              }
           

我們可以看到這個注解除了元注解以外,就隻有一個@Configuration,那也就是說這個注解相當于@Configuration,是以這兩個注解作用是一樣的,那他是幹嘛的呢,相信很多人都知道,它是讓我們能夠去注冊一些額外的Bean,并且導入一些額外的配置。

那@Configuration還有一個作用就是把該類變成一個配置類,不需要額外的XML進行配置。是以@SpringBootConfiguration就相當于@Configuration。

那我們繼續來看下一個@EnableAutoConfiguration,這個注解官網說是 讓Spring自動去進行一些配置,那我們點進來看

@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.class

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,這個類有一個方法

public String selectImports(AnnotationMetadata annotationMetadata) {              if (!isEnabled(annotationMetadata)) {              return NO_IMPORTS;              }              try {              AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader              .loadMetadata(this.beanClassLoader);              AnnotationAttributes attributes = getAttributes(annotationMetadata);              List configurations = getCandidateConfigurations(annotationMetadata,              attributes);              configurations = removeDuplicates(configurations);              configurations = sort(configurations, autoConfigurationMetadata);              Set 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 getCandidateConfigurations(AnnotationMetadata metadata,              AnnotationAttributes attributes) {              List 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 security 注解_深入 Spring Boot 核心注解原理

我們點進spring.factories來看

spring security 注解_深入 Spring Boot 核心注解原理

我們可以發現幫我們配置了很多類的全路徑,比如你想整合activemq,或者說Servlet

spring security 注解_深入 Spring Boot 核心注解原理

可以看到他都已經幫我們引入了進來,我看随便拿幾個來看

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 = ;              FailureAnalyzers analyzers = ;              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, );              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.              }
           

他在這裡并沒有實作,但是我們找他的其他實作,我們來找

spring security 注解_深入 Spring Boot 核心注解原理

我們既然要找Tomcat那就肯定跟web有關,我們可以看到有個ServletWebServerApplicationContext

@Override              protected void onRefresh {              super.onRefresh;              try {              createWebServer;              }              catch (Throwable ex) {              throw new ApplicationContextException("Unable to start web server
           

繼續閱讀