1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        10000 字講清楚 Spring Boot 注解原理

        共 29665字,需瀏覽 60分鐘

         ·

        2021-09-24 19:25

        首先,先看SpringBoot的主配置類:
        @SpringBootApplication
        public class StartEurekaApplication
        {
            public static void main(String[] args)
            
        {
                SpringApplication.run(StartEurekaApplication.classargs);
            }
        }

        點進@SpringBootApplication來看,發(fā)現(xiàn)@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:

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

        可以看到這個注解除了元注解以外,就只有一個@Configuration,那也就是說這個注解相當于@Configuration,所以這兩個注解作用是一樣的,它讓我們能夠去注冊一些額外的Bean,并且導入一些額外的配置。

        那@Configuration還有一個作用就是把該類變成一個配置類,不需要額外的XML進行配置。所以@SpringBootConfiguration就相當于@Configuration。進入@Configuration,發(fā)現(xiàn)@Configuration核心是@Component,說明Spring的配置類也是Spring的一個組件。

        @Target({ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        @Component
        public @interface Configuration {
            @AliasFor(
                annotation = Component.class
            )
            String value() default ""
        ;
        }

        繼續(xù)來看下一個@EnableAutoConfiguration,這個注解是開啟自動配置的功能。

        @Target({ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        @Inherited
        @AutoConfigurationPackage
        @Import({AutoConfigurationImportSelector.class})
        public @interface EnableAutoConfiguration 
        {
            String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

            Class<?>[] exclude() default {};

            String[] excludeName() default {};
        }

        可以看到它是由 @AutoConfigurationPackage,@Import(EnableAutoConfigurationImportSelector.class)這兩個而組成的,我們先說@AutoConfigurationPackage,他是說:讓包中的類以及子包中的類能夠被自動掃描到spring容器中。

        @Target({ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        @Inherited
        @Import({Registrar.class})
        public @interface AutoConfigurationPackage 
        {
        }

        使用@Import來給Spring容器中導入一個組件 ,這里導入的是Registrar.class。來看下這個Registrar:

        static class Registrar implements ImportBeanDefinitionRegistrarDeterminableImports {
                Registrar() {
                }

                public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
                    AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
                }

                public Set<Object> determineImports(AnnotationMetadata metadata) {
                    return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
                }
            }

        就是通過以上這個方法獲取掃描的包路徑,可以debug查看具體的值:

        那metadata是什么呢,可以看到是標注在@SpringBootApplication注解上的DemosbApplication,也就是我們的主配置類Application:

        其實就是將主配置類(即@SpringBootApplication標注的類)的所在包及子包里面所有組件掃描加載到Spring容器。因此我們要把DemoApplication放在項目的最高級中(最外層目錄)。

        看看注解@Import(AutoConfigurationImportSelector.class),@Import注解就是給Spring容器中導入一些組件,這里傳入了一個組件的選擇器:AutoConfigurationImportSelector。

        可以從圖中看出AutoConfigurationImportSelector 繼承了 DeferredImportSelector 繼承了 ImportSelector,ImportSelector有一個方法為:selectImports。將所有需要導入的組件以全類名的方式返回,這些組件就會被添加到容器中。

        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            if (!this.isEnabled(annotationMetadata)) {
                return NO_IMPORTS;
            } else {
                AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
                AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = 
                this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
                return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
            }
        }

        會給容器中導入非常多的自動配置類(xxxAutoConfiguration);就是給容器中導入這個場景需要的所有組件,并配置好這些組件。

        有了自動配置類,免去了我們手動編寫配置注入功能組件等的工作。那是如何獲取到這些配置類的呢,看看下面這個方法:

        protected AutoConfigurationImportSelector.AutoConfigurationEntry 
          getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) 
        {
            if (!this.isEnabled(annotationMetadata)) {
                return EMPTY_ENTRY;
            } else {
                AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
                List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
                configurations = this.removeDuplicates(configurations);
                Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
                this.checkExcludedClasses(configurations, exclusions);
                configurations.removeAll(exclusions);
                configurations = this.filter(configurations, autoConfigurationMetadata);
                this.fireAutoConfigurationImportEvents(configurations, exclusions);
                return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
            }
        }

        我們可以看到getCandidateConfigurations()這個方法,他的作用就是引入系統(tǒng)已經加載好的一些類,到底是那些類呢:

        protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
            List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
        }
        public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
            String factoryClassName = factoryClass.getName();
            return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
        }

        會從META-INF/spring.factories中獲取資源,然后通過Properties加載資源:

        private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
            MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
            if (result != null) {
                return result;
            } else {
                try {
                    Enumeration<URL> urls = classLoader != 
                  null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                    LinkedMultiValueMap result = new LinkedMultiValueMap();

                    while(urls.hasMoreElements()) {
                        URL url = (URL)urls.nextElement();
                        UrlResource resource = new UrlResource(url);
                        Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                        Iterator var6 = properties.entrySet().iterator();

                        while(var6.hasNext()) {
                            Map.Entry<?, ?> entry = (Map.Entry)var6.next();
                            String factoryClassName = ((String)entry.getKey()).trim();
                            String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                            int var10 = var9.length;

                            for(int var11 = 0; var11 < var10; ++var11) {
                                String factoryName = var9[var11];
                                result.add(factoryClassName, factoryName.trim());
                            }
                        }
                    }

                    cache.put(classLoader, result);
                    return result;
                } catch (IOException var13) {
                    throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
                }
            }
        }

        可以知道SpringBoot在啟動的時候從類路徑下的META-INF/spring.factories中獲取EnableAutoConfiguration指定的值,將這些值作為自動配置類導入到容器中,自動配置類就生效,幫我們進行自動配置工作。以前我們需要自己配置的東西,自動配置類都幫我們完成了。如下圖可以發(fā)現(xiàn)Spring常見的一些類已經自動導入。

        接下來看@ComponentScan注解,@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }),這個注解就是掃描包,然后放入spring容器。

        @ComponentScan(excludeFilters = {
          @Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}), 
          @Filter(type 
        = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class})})
        public @interface SpringBootApplication 
        {}

        總結下@SpringbootApplication:就是說,他已經把很多東西準備好,具體是否使用取決于我們的程序或者說配置。

        接下來繼續(xù)看run方法:

        public static void main(String[] args) {
                SpringApplication.run(Application.classargs);
            }

        來看下在執(zhí)行run方法到底有沒有用到哪些自動配置的東西,我們點進run:

        public ConfigurableApplicationContext run(String... args) {
            //計時器
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            ConfigurableApplicationContext context = null;
            Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
            this.configureHeadlessProperty();
            //監(jiān)聽器
            SpringApplicationRunListeners listeners = this.getRunListeners(args);
            listeners.starting();

            Collection exceptionReporters;
            try {
                ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
                this.configureIgnoreBeanInfo(environment);
                Banner printedBanner = this.printBanner(environment);
                //準備上下文
                context = this.createApplicationContext();
                exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class,                       new Class[]{ConfigurableApplicationContext.class}, context);
                //預刷新context
                this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
                //刷新context
                this.refreshContext(context);
                //刷新之后的context
                this.afterRefresh(context, applicationArguments);
                stopWatch.stop();
                if (this.logStartupInfo) {
                    (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
                }

                listeners.started(context);
                this.callRunners(context, applicationArguments);
            } catch (Throwable var10) {
                this.handleRunFailure(context, var10, exceptionReporters, listeners);
                throw new IllegalStateException(var10);
            }

            try {
                listeners.running(context);
                return context;
            } catch (Throwable var9) {
                this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
                throw new IllegalStateException(var9);
            }
        }

        那我們關注的就是 refreshContext(context); 刷新context,我們點進來看。

        private void refreshContext(ConfigurableApplicationContext context) {
           refresh(context);
           if (this.registerShutdownHook) {
              try {
                 context.registerShutdownHook();
              }
              catch (AccessControlException ex) {
                 // Not allowed in some environments.
              }
           }
        }

        我們繼續(xù)點進refresh(context);

        protected void refresh(ApplicationContext applicationContext) {
           Assert.isInstanceOf(AbstractApplicationContext.classapplicationContext);
           ((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的加載過程。繼續(xù)來看一個方法叫做 onRefresh():

        protected void onRefresh() throws BeansException {
           // For subclasses: do nothing by default.
        }

        他在這里并沒有直接實現(xiàn),但是我們找他的具體實現(xiàn):

        比如Tomcat跟web有關,我們可以看到有個ServletWebServerApplicationContext:

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

        可以看到有一個createWebServer();方法他是創(chuàng)建web容器的,而Tomcat不就是web容器,那是如何創(chuàng)建的呢,我們繼續(xù)看:

        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());他是通過工廠的方式創(chuàng)建的。

        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,在這個過程當中,我們可以看到創(chuàng)建Tomcat的一個流程。

        如果不明白的話, 我們在用另一種方式來理解下,大家要應該都知道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-starter</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這個是具體實現(xiàn)。

        @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的打包,在其他項目引入就可以使用。
        來自:博客園

        鏈接:cnblogs.com/cmt/p/14553189.html


        PS:如果覺得我的分享不錯,歡迎大家隨手點贊、在看。

        (完)




        加我"微信獲取一份 最新Java面試題資料

        請備注:666,不然不通過~


        最近好文


        1、Spring Boot 實現(xiàn)掃碼登錄,這種方式太香了?。?/a>

        2、SpringSecurity + JWT 實現(xiàn)單點登錄

        3、基于 Vue+Spring 前后端分離管理系統(tǒng)ELAdmin

        4、Spring Boot 接入支付寶完整流程實戰(zhàn)

        5、Spring Boot 實現(xiàn)多圖片上傳并回顯,漲姿勢了~



        最近面試BAT,整理一份面試資料Java面試BAT通關手冊,覆蓋了Java核心技術、JVM、Java并發(fā)、SSM、微服務、數(shù)據(jù)庫、數(shù)據(jù)結構等等。
        獲取方式:關注公眾號并回復 java 領取,更多內容陸續(xù)奉上。
        明天見(??ω??)??
        瀏覽 58
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            黄色电影免费看 | 国产欧美在线观看视频 | 女男成人 免费视频在线播放 | 极品在线视频 | 性感一区二区三区 | 一区无码高清 | 午夜666 | 成人在线视频网址 | 日逼大全 | 蜜桃在线码无精品秘 入口九色 |