浅谈SpringBoot核心注解原理

谁借莪1个温暖的怀抱¢ 2022-01-29 04:17 531阅读 0赞

SpringBoot核心注解原理

今天跟大家来探讨下SpringBoot的核心注解@SpringBootApplication以及run方法,理解下springBoot为什么不需要XML,达到零配置

首先我们先来看段代码

  1. @SpringBootApplication
  2. public class StartEurekaApplication
  3. {
  4. public static void main(String[] args)
  5. {
  6. SpringApplication.run(StartEurekaApplication.class, args);
  7. }
  8. }

我们点进@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 @EnableAutoConfiguration 这两个注解,到这里我们知道 SpringBootApplication注解里除了元注解,我们可以看到又是@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan的组合注解,官网上也有详细说明,那我们现在把目光投向这三个注解。

首先我们先来看 @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。

那我们继续来看下一个@EnableAutoConfiguration,这个注解官网说是 让Spring自动去进行一些配置,那我们点进来看

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @AutoConfigurationPackage
  6. @Import(EnableAutoConfigurationImportSelector.class)
  7. public @interface EnableAutoConfiguration {
  8. }

可以看到它是由 @AutoConfigurationPackage,@Import(EnableAutoConfigurationImportSelector.class)这两个而组成的,我们先说@AutoConfigurationPackage,他是说:让包中的类以及子包中的类能够被自动扫描到spring容器中。 我们来看@Import(EnableAutoConfigurationImportSelector.class)这个是核心,之前我们说自动配置,那他到底帮我们配置了什么,怎么配置的?就和@Import(EnableAutoConfigurationImportSelector.class)息息相关,程序中默认使用的类就自动帮我们找到。我们来看EnableAutoConfigurationImportSelector.class

  1. public class EnableAutoConfigurationImportSelector
  2. extends AutoConfigurationImportSelector {
  3. @Override
  4. protected boolean isEnabled(AnnotationMetadata metadata) {
  5. if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {
  6. return getEnvironment().getProperty(
  7. EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
  8. true);
  9. }
  10. return true;
  11. }
  12. }

可以看到他继承了AutoConfigurationImportSelector我们继续来看AutoConfigurationImportSelector,这个类有一个方法

  1. public String[] selectImports(AnnotationMetadata annotationMetadata) {
  2. if (!isEnabled(annotationMetadata)) {
  3. return NO_IMPORTS;
  4. }
  5. try {
  6. AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
  7. .loadMetadata(this.beanClassLoader);
  8. AnnotationAttributes attributes = getAttributes(annotationMetadata);
  9. List<String> configurations = getCandidateConfigurations(annotationMetadata,
  10. attributes);
  11. configurations = removeDuplicates(configurations);
  12. configurations = sort(configurations, autoConfigurationMetadata);
  13. Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  14. checkExcludedClasses(configurations, exclusions);
  15. configurations.removeAll(exclusions);
  16. configurations = filter(configurations, autoConfigurationMetadata);
  17. fireAutoConfigurationImportEvents(configurations, exclusions);
  18. return configurations.toArray(new String[configurations.size()]);
  19. }
  20. catch (IOException ex) {
  21. throw new IllegalStateException(ex);
  22. }
  23. }

这个类会帮你扫描那些类自动去添加到程序当中。我们可以看到getCandidateConfigurations()这个方法,他的作用就是引入系统已经加载好的一些类,到底是那些类呢,我们点进去看一下

  1. protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
  2. AnnotationAttributes attributes) {
  3. List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
  4. getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
  5. Assert.notEmpty(configurations,
  6. "No auto configuration classes found in META-INF/spring.factories. If you "
  7. + "are using a custom packaging, make sure that file is correct.");
  8. return configurations;
  9. }

这个类回去寻找的一个目录为META-INF/spring.factories,也就是说他帮你加载让你去使用也就是在这个META-INF/spring.factories目录装配的,他在哪里?

我们点进spring.factories来看

我们可以发现帮我们配置了很多类的全路径,比如你想整合activemq,或者说Servlet

可以看到他都已经帮我们引入了进来,我看随便拿几个来看

  1. org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
  2. org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
  3. org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
  4. 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:就是说,他已经把很多东西准备好,具体是否使用取决于我们的程序或者说配置,那我们到底用不用?那我们继续来看一行代码

  1. public static void main(String[] args)
  2. {
  3. SpringApplication.run(StartEurekaApplication.class, args);
  4. }

那们来看下在执行run方法到底有没有用到哪些自动配置的东西,比如说内置的Tomcat,那我们来找找内置Tomcat,我们点进run

  1. public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
  2. return new SpringApplication(sources).run(args);
  3. }

然后他调用又一个run方法,我们点进来看

  1. public ConfigurableApplicationContext run(String... args) {
  2. //计时器
  3. StopWatch stopWatch = new StopWatch();
  4. stopWatch.start();
  5. ConfigurableApplicationContext context = null;
  6. FailureAnalyzers analyzers = null;
  7. configureHeadlessProperty();
  8. //监听器
  9. SpringApplicationRunListeners listeners = getRunListeners(args);
  10. listeners.starting();
  11. try {
  12. ApplicationArguments applicationArguments = new DefaultApplicationArguments(
  13. args);
  14. ConfigurableEnvironment environment = prepareEnvironment(listeners,
  15. applicationArguments);
  16. Banner printedBanner = printBanner(environment);
  17. //准备上下文
  18. context = createApplicationContext();
  19. analyzers = new FailureAnalyzers(context);
  20. //预刷新context
  21. prepareContext(context, environment, listeners, applicationArguments,
  22. printedBanner);
  23. //刷新context
  24. refreshContext(context);
  25. //刷新之后的context
  26. afterRefresh(context, applicationArguments);
  27. listeners.finished(context, null);
  28. stopWatch.stop();
  29. if (this.logStartupInfo) {
  30. new StartupInfoLogger(this.mainApplicationClass)
  31. .logStarted(getApplicationLog(), stopWatch);
  32. }
  33. return context;
  34. }
  35. catch (Throwable ex) {
  36. handleRunFailure(context, listeners, analyzers, ex);
  37. throw new IllegalStateException(ex);
  38. }
  39. }

那我们关注的就是 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. }
  29. catch (BeansException ex) {
  30. if (logger.isWarnEnabled()) {
  31. logger.warn("Exception encountered during context initialization - " +
  32. "cancelling refresh attempt: " + ex);
  33. }
  34. // Destroy already created singletons to avoid dangling resources.
  35. destroyBeans();
  36. // Reset 'active' flag.
  37. cancelRefresh(ex);
  38. // Propagate exception to caller.
  39. throw ex;
  40. }
  41. finally {
  42. // Reset common introspection caches in Spring's core, since we
  43. // might not ever need metadata for singleton beans anymore...
  44. resetCommonCaches();
  45. }
  46. }
  47. }

这点代码似曾相识啊 没错,就是一个spring的bean的加载过程我在,解析springIOC加载过程的时候介绍过这里面的方法,如果你看过Spring源码的话 ,应该知道这些方法都是做什么的。现在我们不关心其他的,我们来看一个方法叫做 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. @Override
  2. public WebServer getWebServer(ServletContextInitializer... initializers) {
  3. Tomcat tomcat = new Tomcat();
  4. File baseDir = (this.baseDirectory != null) ? this.baseDirectory
  5. : createTempDir("tomcat");
  6. tomcat.setBaseDir(baseDir.getAbsolutePath());
  7. Connector connector = new Connector(this.protocol);
  8. tomcat.getService().addConnector(connector);
  9. customizeConnector(connector);
  10. tomcat.setConnector(connector);
  11. tomcat.getHost().setAutoDeploy(false);
  12. configureEngine(tomcat.getEngine());
  13. for (Connector additionalConnector : this.additionalTomcatConnectors) {
  14. tomcat.getService().addConnector(additionalConnector);
  15. }
  16. prepareContext(tomcat.getHost(), initializers);
  17. return getTomcatWebServer(tomcat);
  18. }

那这块代码,就是我们要寻找的内置Tomcat,在这个过程当中,我们可以看到创建Tomcat的一个流程。因为run方法里面加载的东西很多,所以今天就浅谈到这里。如果不明白的话, 我们在用另一种方式来理解下,大家要应该都知道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-srater</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的打包,在其他项目引入就可以使用,在这里列出代码地址

https://github.com/zgw1469039806/gwspringbootsrater

到此本文结束,欢迎转载,请标注出处和作者,谢谢

原文 浅谈SpringBoot核心注解原理

发表评论

表情:
评论列表 (有 0 条评论,531人围观)

还没有评论,来说两句吧...

相关阅读

    相关 SpringBoot核心注解

    SpringBoot核心注解 SpringBoot项目通常有一个\Application的入口类,这个类里面有main方法即项目的主方法,启动项目,在入口类上有一个注解

    相关 Java注解

           自Java5.0版本引入注解之后,它就成为了Java平台中非常重要的一部分。注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记。注解使得Java源代码

    相关 Spring注解

    Spring目前的趋势是使用注解结合Java代码而不是配置来定义行为、属性、功能、规则和扩展点,因此梳理注解也是梳理Spring功能点的很好的方式,全面的梳理可以补足我们知识点

    相关 搜索引擎的核心算法

    外链是搜索引擎算法中,判断网站权重高低的重要指标,当用户在搜索框中输入关键时,搜索引擎面对大量拥有相同内容的网页,首先需要解决的就是,通过哪些参数觉得网页排名的高低。今天,笔者