java学习基地

微信扫一扫 分享朋友圈

已有 1368 人浏览分享

SpringBoot 注解原理,自动装配原理,图文并茂,万字长文!

[复制链接]
1368 0
起首,先看SpringBoot的主设置类:
  1. @SpringBootApplication
  2. public class StartEurekaApplication
  3. {
  4.     public static void main(String[] args)
  5.     {
  6.         SpringApplication.run(StartEurekaApplication.class, args);
  7.     }
  8. }
赶钙代码


面进@SpringBootApplication去看,发明@SpringBootApplication是一个组开注解。
  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @SpringBootConfiguration
  6. @EnableAutoConfiguration
  7. @ComponentScan(excludeFilters = {
  8.       @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  9.       @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
  10. public @interface SpringBootApplication {
  11. }
赶钙代码


起首我们先去看 @SpringBootConfiguration:
  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Configuration
  5. public @interface SpringBootConfiguration {
  6. }
赶钙代码


能够看到那个注消除了元注解之外,便只要一个@Configuration,那也便是道那个注解相称于@Configuration,以是那两个注解感化是一样的,它让我们可以来注册一些分外的Bean,而且导进一些分外的设置。

那@Configuration另有一个感化便是把负绵酿成一个设置类,没有需求分外的XML停止设置。以是@SpringBootConfiguration便相称于@Configuration。进进@Configuration,发明@Configuration中心是@Component,阐明Spring的设置类也是Spring的一个组件。

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Component
  5. public @interface Configuration {
  6.     @AliasFor(
  7.         annotation = Component.class
  8.     )
  9.     String value() default "";
  10. }
赶钙代码

持续去看现位个@EnableAutoConfiguration,那个注解是开启主动设置的功用。

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @AutoConfigurationPackage
  6. @Import({AutoConfigurationImportSelector.class})
  7. public @interface EnableAutoConfiguration {
  8.     String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
  9.     Class<?>[] exclude() default {};
  10.     String[] excludeName() default {};
  11. }
赶钙代码


能够看到它是由 @AutoConfigurationPackage,@Import(EnableAutoConfigurationImportSelector.class)那两个而构成的,我们先道@AutoConfigurationPackage,他是道:让包中的类和子包中的类可以被主动扫描到spring容器中。

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @Import({Registrar.class})
  6. public @interface AutoConfigurationPackage {
  7. }
赶钙代码


利用@Import去给Spring容器中导进一个组件 ,那里导进的是Registrar.class。去看下那个Registrar:

  1. static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
  2.         Registrar() {
  3.         }
  4.         public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
  5.             AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
  6.         }
  7.         public Set<Object> determineImports(AnnotationMetadata metadata) {
  8.             return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
  9.         }
  10.     }
赶钙代码


便是经由过程以上那个办法获得扫描的包途径,能够debug检察详细的值:

那metadata是甚么呢,能够看到是标注正在@SpringBootApplication注解上的DemosbApplication,也便是我们的主设置类Application:

实在便是将主设置类(即@SpringBootApplication标注的类)的地点包己谟包内里一切组件扫描减载到Spring容器。因而我们要把DemoApplication放正在项目标第一流中(最中层目次)。

吭哟注解@Import(AutoConfigurationImportSelector.class),@Import注解便史狲Spring容器中导进一些组件,那里传进了一个组件狄住择器:AutoConfigurationImportSelector。

能够从图中看出AutoConfigurationImportSelector 担当了 DeferredImportSelector 担当了 ImportSelector,ImportSelector有一个办法为:selectImports。将一切需求导进的组件以齐类名的方法返回,那些组件便会被增加到容器中。

  1. public String[] selectImports(AnnotationMetadata annotationMetadata) {
  2.     if (!this.isEnabled(annotationMetadata)) {
  3.         return NO_IMPORTS;
  4.     } else {
  5.         AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
  6.         AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry =
  7.         this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
  8.         return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
  9.     }
  10. }
赶钙代码

会给容器中导进十分多的主动设置类(xxxAutoConfiguration);便史狲容器中导进那个场景需求的一切组件,并设置好那些组件。

有裂沤设置类,免除了我们脚动编写设置注进功用组件等的事情。那是怎样获得到那些设置类的呢,吭哟上面那个办法:

  1. protected AutoConfigurationImportSelector.AutoConfigurationEntry
  2.   getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
  3.     if (!this.isEnabled(annotationMetadata)) {
  4.         return EMPTY_ENTRY;
  5.     } else {
  6.         AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
  7.         List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
  8.         configurations = this.removeDuplicates(configurations);
  9.         Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
  10.         this.checkExcludedClasses(configurations, exclusions);
  11.         configurations.removeAll(exclusions);
  12.         configurations = this.filter(configurations, autoConfigurationMetadata);
  13.         this.fireAutoConfigurationImportEvents(configurations, exclusions);
  14.         return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
  15.     }
  16. }
赶钙代码

我们能够看到getCandidateConfigurations()那个办法,他的感化便是引进体系曾经减载好的一些类,究竟是那些类呢:

  1. protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  2.     List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
  3.     Assert.notEmpty(configurations,
  4.     "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
  5.     return configurations;
  6. }
赶钙代码
  1. public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
  2.     String factoryClassName = factoryClass.getName();
  3.     return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
  4. }
赶钙代码

会从META-INF/spring.factories挚与资本,然后经由过程Properties减载资本:

  1. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  2.     MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
  3.     if (result != null) {
  4.         return result;
  5.     } else {
  6.         try {
  7.             Enumeration<URL> urls = classLoader !=
  8.           null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
  9.             LinkedMultiValueMap result = new LinkedMultiValueMap();
  10.             while(urls.hasMoreElements()) {
  11.                 URL url = (URL)urls.nextElement();
  12.                 UrlResource resource = new UrlResource(url);
  13.                 Properties properties = PropertiesLoaderUtils.loadProperties(resource);
  14.                 Iterator var6 = properties.entrySet().iterator();
  15.                 while(var6.hasNext()) {
  16.                     Map.Entry<?, ?> entry = (Map.Entry)var6.next();
  17.                     String factoryClassName = ((String)entry.getKey()).trim();
  18.                     String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
  19.                     int var10 = var9.length;
  20.                     for(int var11 = 0; var11 < var10; ++var11) {
  21.                         String factoryName = var9[var11];
  22.                         result.add(factoryClassName, factoryName.trim());
  23.                     }
  24.                 }
  25.             }
  26.             cache.put(classLoader, result);
  27.             return result;
  28.         } catch (IOException var13) {
  29.             throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
  30.         }
  31.     }
  32. }
赶钙代码

能够明白SpringBoot正在启动的时分从类途径下的META-INF/spring.factories挚与EnableAutoConfiguration指定的值,将那些值做为主动设置类导进到容器中,主动设置类便见效,帮我们停止主动设置事情。从前我们需求本人设置的工具,主动设置类皆帮我们完成了。以下图能够发明Spring常睹的一些类曾经主动导进。

接下去看@ComponentScan注解,@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }),那个注解便是扫描包,然后放进spring容器。

  1. @ComponentScan(excludeFilters = {
  2.   @Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}),
  3.   @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class})})
  4. public @interface SpringBootApplication {}
赶钙代码

总结下@SpringbootApplication:便是道,他曾经把许多工具筹办好,详细能否利用与决于我们的法式大概道设置。

接下去持续看run办法:

  1. public static void main(String[] args) {
  2.         SpringApplication.run(Application.class, args);
  3.     }
赶钙代码

去看下正在施行run办法到底有无用到哪些主动设置的工具,我们面进run:

  1. public ConfigurableApplicationContext run(String... args) {
  2.     //计时器
  3.     StopWatch stopWatch = new StopWatch();
  4.     stopWatch.start();
  5.     ConfigurableApplicationContext context = null;
  6.     Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
  7.     this.configureHeadlessProperty();
  8.     //监听器
  9.     SpringApplicationRunListeners listeners = this.getRunListeners(args);
  10.     listeners.starting();
  11.     Collection exceptionReporters;
  12.     try {
  13.         ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  14.         ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
  15.         this.configureIgnoreBeanInfo(environment);
  16.         Banner printedBanner = this.printBanner(environment);
  17.         //筹办高低文
  18.         context = this.createApplicationContext();
  19.         exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class,                       new Class[]{ConfigurableApplicationContext.class}, context);
  20.         //预革新context
  21.         this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
  22.         //革新context
  23.         this.refreshContext(context);
  24.         //革新以后的context
  25.         this.afterRefresh(context, applicationArguments);
  26.         stopWatch.stop();
  27.         if (this.logStartupInfo) {
  28.             (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
  29.         }
  30.         listeners.started(context);
  31.         this.callRunners(context, applicationArguments);
  32.     } catch (Throwable var10) {
  33.         this.handleRunFailure(context, var10, exceptionReporters, listeners);
  34.         throw new IllegalStateException(var10);
  35.     }
  36.     try {
  37.         listeners.running(context);
  38.         return context;
  39.     } catch (Throwable var9) {
  40.         this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
  41.         throw new IllegalStateException(var9);
  42.     }
  43. }
赶钙代码

那我们存眷的便是 refreshContext(context); 革新context,我们面出去看。

保举:

  1. private void refreshContext(ConfigurableApplicationContext context) {
  2.    refresh(context);
  3.    if (this.registerShutdownHook) {
  4.       try {
  5.          context.registerShutdownHook();
  6.       }
  7.       catch (AccessControlException ex) {
  8.          // Not allowed in some environments.
  9.       }
  10.    }
  11. }
赶钙代码

我们持续面进refresh(context);

  1. protected void refresh(ApplicationContext applicationContext) {
  2.    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
  3.    ((AbstractApplicationContext) applicationContext).refresh();
  4. }
赶钙代码

他会挪用 ((AbstractApplicationContext) applicationContext).refresh();办法,我们面出去看:

  1. public void refresh() throws BeansException, IllegalStateException {
  2.    synchronized (this.startupShutdownMonitor) {
  3.       // Prepare this context for refreshing.
  4.       prepareRefresh();
  5.       // Tell the subclass to refresh the internal bean factory.
  6.       ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
  7.       // Prepare the bean factory for use in this context.
  8.       prepareBeanFactory(beanFactory);
  9.       try {
  10.          // Allows post-processing of the bean factory in context subclasses.
  11.          postProcessBeanFactory(beanFactory);
  12.          // Invoke factory processors registered as beans in the context.
  13.          invokeBeanFactoryPostProcessors(beanFactory);
  14.          // Register bean processors that intercept bean creation.
  15.          registerBeanPostProcessors(beanFactory);
  16.          // Initialize message source for this context.
  17.          initMessageSource();
  18.          // Initialize event multicaster for this context.
  19.          initApplicationEventMulticaster();
  20.          // Initialize other special beans in specific context subclasses.
  21.          onRefresh();
  22.          // Check for listener beans and register them.
  23.          registerListeners();
  24.          // Instantiate all remaining (non-lazy-init) singletons.
  25.          finishBeanFactoryInitialization(beanFactory);
  26.          // Last step: publish corresponding event.
  27.          finishRefresh();
  28.       }catch (BeansException ex) {
  29.          if (logger.isWarnEnabled()) {
  30.             logger.warn("Exception encountered during context initialization - " +
  31.                   "cancelling refresh attempt: " + ex);
  32.          }
  33.          // Destroy already created singletons to avoid dangling resources.
  34.          destroyBeans();
  35.          // Reset 'active' flag.
  36.          cancelRefresh(ex);
  37.          // Propagate exception to caller.
  38.          throw ex;
  39.       }finally {
  40.          // Reset common introspection caches in Spring's core, since we
  41.          // might not ever need metadata for singleton beans anymore...
  42.          resetCommonCaches();
  43.       }
  44.    }
  45. }
赶钙代码

由此可知,便是一个spring的bean的减载历程。持续去看一个办法叫做 onRefresh():

  1. protected void onRefresh() throws BeansException {
  2.    // For subclasses: do nothing by default.
  3. }
赶钙代码

他正在那里并出有间接完成,可是我们找他的详细完成:

好比Tomcat跟web庸呢,我们能够看到有个ServletWebServerApplicationContext:

  1. @Override
  2. protected void onRefresh() {
  3.    super.onRefresh();
  4.    try {
  5.       createWebServer();
  6.    }
  7.    catch (Throwable ex) {
  8.       throw new ApplicationContextException("Unable to start web server", ex);
  9.    }
  10. }
赶钙代码

能够看到有一个createWebServer();办法他是创立web容器的,而Tomcat没有便是web容器,那是怎样创立的呢,我们持续看:

  1. private void createWebServer() {
  2.    WebServer webServer = this.webServer;
  3.    ServletContext servletContext = getServletContext();
  4.    if (webServer == null && servletContext == null) {
  5.       ServletWebServerFactory factory = getWebServerFactory();
  6.       this.webServer = factory.getWebServer(getSelfInitializer());
  7.    }
  8.    else if (servletContext != null) {
  9.       try {
  10.          getSelfInitializer().onStartup(servletContext);
  11.       }
  12.       catch (ServletException ex) {
  13.          throw new ApplicationContextException("Cannot initialize servlet context",
  14.                ex);
  15.       }
  16.    }
  17.    initPropertySources();
  18. }
赶钙代码

factory.getWebServer(getSelfInitializer());他是经由过程工场的方法创立的。

  1. public interface ServletWebServerFactory {
  2.    WebServer getWebServer(ServletContextInitializer... initializers);
  3. }
赶钙代码

能够看到 它是一个接心,为何会是接心。由于我们没有行是Tomcat一种web容器。

我们看到另有Jetty,那我们去看TomcatServletWebServerFactory:

  1. public WebServer getWebServer(ServletContextInitializer... initializers) {
  2.    Tomcat tomcat = new Tomcat();
  3.    File baseDir = (this.baseDirectory != null) ? this.baseDirectory
  4.          : createTempDir("tomcat");
  5.    tomcat.setBaseDir(baseDir.getAbsolutePath());
  6.    Connector connector = new Connector(this.protocol);
  7.    tomcat.getService().addConnector(connector);
  8.    customizeConnector(connector);
  9.    tomcat.setConnector(connector);
  10.    tomcat.getHost().setAutoDeploy(false);
  11.    configureEngine(tomcat.getEngine());
  12.    for (Connector additionalConnector : this.additionalTomcatConnectors) {
  13.       tomcat.getService().addConnector(additionalConnector);
  14.    }
  15.    prepareContext(tomcat.getHost(), initializers);
  16.    return getTomcatWebServer(tomcat);
  17. }
赶钙代码

那那块代码,便是我们要寻觅的内置Tomcat,正在那个历程傍边,我们能够看到创立Tomcat的一个流程。

假如没有大白的话, 我玫邻用另外一种方法去了解下,各人要该当皆明白stater举面例子。

  1. <dependency>
  2.     <groupId>org.springframework.boot</groupId>
  3.     <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>
  5. <dependency>
  6.     <groupId>org.springframework.boot</groupId>
  7.     <artifactId>spring-boot-starter-freemarker</artifactId>
  8. </dependency>
赶钙代码

起首捉义一个stater。

  1. <parent>
  2.     <groupId>org.springframework.boot</groupId>
  3.     <artifactId>spring-boot-starter-parent</artifactId>
  4.     <version>2.1.4.RELEASE</version>
  5.     <relativePath/>
  6. </parent>
  7. <groupId>com.zgw</groupId>
  8. <artifactId>gw-spring-boot-starter</artifactId>
  9. <version>1.0-SNAPSHOT</version>
  10. <dependencies>
  11.     <dependency>
  12.         <groupId>org.springframework.boot</groupId>
  13.         <artifactId>spring-boot-autoconfigure</artifactId>
  14.     </dependency>
  15. </dependencies>
赶钙代码

我们先去看maven设置写进版本号,假如捉义一个stater的话必需依靠spring-boot-autoconfigure那个包,我们先看下项目目次。


  1. public class GwServiceImpl  implements GwService{
  2.     @Autowired
  3.     GwProperties properties;
  4.     @Override
  5.     public void Hello()
  6.     {
  7.         String name=properties.getName();
  8.         System.out.println(name+"道:您们好啊");
  9.     }
  10. }
赶钙代码
我们做的便是经由过程设置文件去定造name那个是详细完成。
  1. @Component
  2. @ConfigurationProperties(prefix = "spring.gwname")
  3. public class GwProperties {
  4.     String name="zgw";
  5.     public String getName() {
  6.         return name;
  7.     }
  8.     public void setName(String name) {
  9.         this.name = name;
  10.     }
  11. }
赶钙代码


那个类能够经由过程@ConfigurationProperties读与设置文件。
  1. @Configuration
  2. @ConditionalOnClass(GwService.class)  //扫描类
  3. @EnableConfigurationProperties(GwProperties.class) //让设置类见效
  4. public class GwAutoConfiguration {
  5.     /**
  6.     * 功用形貌 凸能给spring
  7.     * @author zgw
  8.     * @return
  9.     */
  10.     @Bean
  11.     @ConditionalOnMissingBean
  12.     public GwService gwService()
  13.     {
  14.         return new GwServiceImpl();
  15.     }
  16. }
赶钙代码
那个为设置类,为何那么写由于,spring-boot的stater皆是那么写的,我们能够参照他仿写stater,以到达主动设置的目标,然后我玫邻经由过程spring.factories也去停止设置。
  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gw.GwAutoConfiguration
赶钙代码
然后如许一个简朴的stater便完成了,然后能够停止maven的挨包,正在其他项目引进就能够利用。



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

举报 使用道具

回复
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

0

关注

1

粉丝

308

主题
精彩推荐
热门资讯
网友晒图
图文推荐

Archiver|手机版|java学习基地 |网站地图

GMT+8, 2021-4-18 22:40 , Processed in 0.871127 second(s), 29 queries .

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.