美文网首页
springboot启动方式解析

springboot启动方式解析

作者: John_Phil | 来源:发表于2019-07-31 04:38 被阅读0次

    springboot启动类 有一个注解@SpringBootApplication

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

    我们点进去@SpringBootApplication 我们发现里面有许多注解

    @SpringBootApplication

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    package org.springframework.boot.autoconfigure;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import org.springframework.boot.SpringBootConfiguration;
    import org.springframework.boot.context.TypeExcludeFilter;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.FilterType;
    import org.springframework.context.annotation.ComponentScan.Filter;
    import org.springframework.core.annotation.AliasFor;
    
    
    @Target(ElementType.TYPE)            // 注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明
    @Retention(RetentionPolicy.RUNTIME)  // 注解的生命周期,保留到class文件中(三个生命周期)
    @Documented                          // 表明这个注解应该被javadoc记录
    @Inherited                           // 子类可以继承该注解
    //以上为元注解
    @SpringBootConfiguration             // ***继承了Configuration,表示当前是注解类
    @EnableAutoConfiguration             // ***开启springboot的注解功能,springboot的四大神器之一,其借助@import的帮助
    @ComponentScan(excludeFilters = {@Filter(    // ***扫描路径设置(具体使用待确认)
        type = FilterType.CUSTOM,
        classes = {TypeExcludeFilter.class}
    ), @Filter(
        type = FilterType.CUSTOM,
        classes = {AutoConfigurationExcludeFilter.class}
    )}
    )
    public @interface SpringBootApplication {
        @AliasFor(
            annotation = EnableAutoConfiguration.class
        )
        Class<?>[] exclude() default {};
    
        @AliasFor(
            annotation = EnableAutoConfiguration.class
        )
        String[] excludeName() default {};
    
        @AliasFor(
            annotation = ComponentScan.class,
            attribute = "basePackages"
        )
        String[] scanBasePackages() default {};
    
        @AliasFor(
            annotation = ComponentScan.class,
            attribute = "basePackageClasses"
        )
        Class<?>[] scanBasePackageClasses() default {};
    }
    
    

    @SpringBootApplication-----》@SpringBootConfiguration

    点进去发现本质还是一个@Configuration 即标记当前类为配置文件类可以使用@bean注解将对应的配置文件内容注入到对象中 使用@bean注解 注入详见https://www.jianshu.com/p/9043fa02d570

    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import org.springframework.context.annotation.Configuration;
    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration //配置注解,当前类为配置类,可以配置bean 
    public @interface SpringBootConfiguration {
    }
    
    

    举几个简单例子回顾下,XML跟config配置方式的区别:
    表达形式层面
    基于XML配置的方式是这样

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
           default-lazy-init="true">
        <!--bean定义-->
    </beans>
    

    基于JavaConfig的配置方式是这样:

    @Configuration
    public class MockConfiguration{
        //bean定义
    }
    

    任何一个标注了@Configuration的Java类定义都是一个JavaConfig配置类。
    注册bean定义层面
    基于XML的配置形式是这样:

    <bean id="mockService" class="..MockServiceImpl">
        ...
    </bean>
    

    而基于JavaConfig的配置形式是这样的:

    @Configuration
    public class MockConfiguration{
        @Bean
        public MockService mockService(){
            return new MockServiceImpl();
        }
    }
    

    任何一个标注了@Bean的方法,其返回值将作为一个bean定义注册到Spring的IoC容器,方法名将默认成该bean定义的id。

    表达依赖注入关系层面
    为了表达bean与bean之间的依赖关系,在XML形式中一般是这样:

    <bean id="mockService" class="..MockServiceImpl">
        <propery name ="dependencyService" ref="dependencyService" />
    </bean>
    
    <bean id="dependencyService" class="DependencyServiceImpl"></bean>
    

    而基于JavaConfig的配置形式是这样的:

    @Configuration
    public class MockConfiguration{
        @Bean
        public MockService mockService(){
            return new MockServiceImpl(dependencyService());
        }
        
        @Bean
        public DependencyService dependencyService(){
            return new DependencyServiceImpl();
        }
    }
    

    SpringBootApplication---》@EnableAutoConfiguration

    借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器
    比较重要的两个注解@AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class})

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    package org.springframework.boot.autoconfigure;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import org.springframework.context.annotation.Import;
    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage //自动配置的包
    @Import({AutoConfigurationImportSelector.class}) //指定读取beans路径
    public @interface EnableAutoConfiguration {
        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
        Class<?>[] exclude() default {};
    
        String[] excludeName() default {};
    }
    
    

    SpringBootApplication---》@EnableAutoConfiguration ------》@AutoConfigurationPackage的作用就是自动配置的包,@Import导入需要自动配置的组件。

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    package org.springframework.boot.autoconfigure;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar;
    import org.springframework.context.annotation.Import;
    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import({Registrar.class})
    public @interface AutoConfigurationPackage {
    }
    

    SpringBootApplication---》@EnableAutoConfiguration ------》@AutoConfigurationPackage----》Registrar 中决定了springboot启动类位置应该是其他待扫描包的上级文件路径

    new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()
    new AutoConfigurationPackages.PackageImport(metadata)
    这两句代码的作用就是加载启动类所在的包下的主类与子类的所有组件注册到spring容器,这就是前文所说的springboot默认扫描启动类所在的包下的主类与子类的所有组件。

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
            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));
            }
        }
    

    SpringBootApplication---》@EnableAutoConfiguration ------》@Import({AutoConfigurationImportSelector.class}) ***

        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            if (!this.isEnabled(annotationMetadata)) {
                return NO_IMPORTS;
            } else {
    // //1.从META-INF/spring-autoconfigure-metadata.properties文件中载入683条(数量取决版本)配置属性(有一些有默认值)
                AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
                AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
                return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
            }
        }
    

    loadMetadata

        public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
            return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties");
    
    spring-autoconfigure-metadata.properties

    SpringBootApplication---》@EnableAutoConfiguration ------》@Import({AutoConfigurationImportSelector.class}) -----》getCandidateConfigurations 方法

    此处是去获取真正自动配置类的集合

        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;
        }
    

    SpringBootApplication---》@EnableAutoConfiguration ------》@Import({AutoConfigurationImportSelector.class}) -----》getCandidateConfigurations ------》loadFactoryNames

        public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
            String factoryClassName = factoryClass.getName();
            return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
        }
    

    SpringBootApplication---》@EnableAutoConfiguration ------》@Import({AutoConfigurationImportSelector.class}) -----》getCandidateConfigurations ------》loadFactoryNames-----》loadSpringFactories 从此处可以看到获取真正自动配置类的集合 META-INF/spring.factories

    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()) {
                            Entry<?, ?> entry = (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);
                }
            }
        }
    
    META-INF/spring.factories

    举例:auto configure中有AopAutoConfiguration

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    package org.springframework.boot.autoconfigure.aop;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.reflect.Advice;
    import org.aspectj.weaver.AnnotatedElement;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    @Configuration
    @ConditionalOnClass({EnableAspectJAutoProxy.class, Aspect.class, Advice.class, AnnotatedElement.class})
    @ConditionalOnProperty(
        prefix = "spring.aop",
        name = {"auto"},
        havingValue = "true",
        matchIfMissing = true
    )
    public class AopAutoConfiguration {
        public AopAutoConfiguration() {
        }
    
        @Configuration
        @EnableAspectJAutoProxy(
            proxyTargetClass = true
        )
        @ConditionalOnProperty(
            prefix = "spring.aop",
            name = {"proxy-target-class"},
            havingValue = "true",
            matchIfMissing = true
        )
        public static class CglibAutoProxyConfiguration {
            public CglibAutoProxyConfiguration() {
            }
        }
    
        @Configuration
        @EnableAspectJAutoProxy(
            proxyTargetClass = false
        )
        @ConditionalOnProperty(
            prefix = "spring.aop",
            name = {"proxy-target-class"},
            havingValue = "false",
            matchIfMissing = false
        )
        public static class JdkDynamicAutoProxyConfiguration {
            public JdkDynamicAutoProxyConfiguration() {
            }
        }
    }
    
    

    可以在spring-configuration-metadata.json找到配置文件

        {
          "name": "spring.aop.proxy-target-class",
          "type": "java.lang.Boolean",
          "description": "Whether subclass-based (CGLIB) proxies are to be created (true), as opposed to standard Java interface-based proxies (false).",
          "defaultValue": true
        },
    
    AopAutoConfiguration
    spring-configuration-metadata.json

    @SpringBootApplication---》@ComponentScan

    @ComponentScan:spring的自动扫描注解,可定义扫描范围,加载到IOC容器
    我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。
    注:所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。


    image.png

    Spring是一个依赖注入(dependency injection)框架。所有的内容都是关于bean的定义及其依赖关系。
    定义Spring Beans的第一步是使用正确的注解-@Component或@Service或@Repository.
    但是,Spring不知道你定义了某个bean除非它知道从哪里可以找到这个bean.
    ComponentScan做的事情就是告诉Spring从哪里找到bean

    Spring Boot项目

    如果你的其他包都在使用了@SpringBootApplication注解的main app所在的包及其下级包,则你什么都不用做,SpringBoot会自动帮你把其他包都扫描了
    如果你有一些bean所在的包,不在main app的包及其下级包,那么你需要手动加上@ComponentScan注解并指定那个bean所在的包

    如果你项目中所有的类都定义在上面的包及其子包下,那你不需要做任何事。
    但假如你一个类定义在包com.neusoft.springboot.somethingelse下,则你需要将这个新包也纳入扫描的范围,有两个方案可以达到这个目的。

    方案1

    定义@ComponentScan(“com.neusoft.springboot”)
    这么做扫描的范围扩大到整个父包com.neusoft.springboot

    @ComponentScan(“com.neusoft.springboot”)
    @SpringBootApplication
    public class SpringbootInaaaApplication {
    

    方案2

    定义分别扫描两个包
    @ComponentScan({“com.neusoft.springboot.basics.springbootinaaa”,”com.neusoft.springboot.somethingelse”})

    @ComponentScan({"com.neusoft.springboot.basics.springbootinaaa","com.neusoft.springboot.somethingelse"})
    @SpringBootApplication
    public class SpringbootInaaaApplication {
    

    遇到这些错误你应该检查:

    • 你是否给类加了正确的注解@Controller,@Repository,@Service或@Component
    • 你是否在应用上下文定义了Component Scan
    • 报错类所在的包是否在Component Scan中指定的包的范围

    @Component and @ComponentScan 的区别

    @Component 和 @ComponentScan的使用目的不一样

    • 在某个类上使用@Component注解,表明当需要创建类时,这个被注解的类是一个候选类。就像是举手。
    • @ComponentScan 用于扫描指定包下的类。就像看都有哪些举手了。

    自动配置原理

    先判断当前类型是否符合servlet
    在判断是否有Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class依赖
    如果都满足再自动装载,如果不满足则不装载
    但是装载后 内容并不一定都生效


    以WebMvcAutoConfiguration为例
    @Configuration
    @ConditionalOnWebApplication(
        type = Type.SERVLET
    )
    @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
    @ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
    @AutoConfigureOrder(-2147483638)
    @AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
    public class WebMvcAutoConfiguration {
        public static final String DEFAULT_PREFIX = "";
        public static final String DEFAULT_SUFFIX = "";
        private static final String[] SERVLET_LOCATIONS = new String[]{"/"};
    
    
    以视图解析方法为例

    但是装载后 内容并不一定都生效 以下注解要求 @ConditionalOnBean({ViewResolver.class}) 必须有ViewResolver.class的依赖 并且 @ConditionalOnMissingBean(
    name = {"viewResolver"},
    value = {ContentNegotiatingViewResolver.class}
    )
    没有viewResolver对象 即我们自己没有配置过viewResolver (application.properties中可以配置对应对象,使用自定对象)
    想要覆盖当前 bean 方式两种 一自定义bean覆盖,二.application.properties配置文件修改里面的属性

       @Bean
            @ConditionalOnBean({ViewResolver.class})
            @ConditionalOnMissingBean(
                name = {"viewResolver"},
                value = {ContentNegotiatingViewResolver.class}
            )
            public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
                ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
                resolver.setContentNegotiationManager((ContentNegotiationManager)beanFactory.getBean(ContentNegotiationManager.class));
                resolver.setOrder(-2147483648);
                return resolver;
            }
    

    以下为视图解析器前后缀可以在配置文件中对前后缀进行属性覆盖,使用自定义后缀


    application.yml
    覆盖原有属性
            @Bean
            @ConditionalOnMissingBean
            public InternalResourceViewResolver defaultViewResolver() {
                InternalResourceViewResolver resolver = new InternalResourceViewResolver();
                resolver.setPrefix(this.mvcProperties.getView().getPrefix());
                resolver.setSuffix(this.mvcProperties.getView().getSuffix());
                return resolver;
            }
    

    相关文章

      网友评论

          本文标题:springboot启动方式解析

          本文链接:https://www.haomeiwen.com/subject/lnbirctx.html