• Spring Boot的自动装配原理


            Spring Boot的“开箱即用”的原则,使得企业应用开发中各种场景的Spring开发更加快速,更加高效,由于配置大量减少,开发效率相得益彰。

      启动原理:SpringBoot项目会有一个启动类,这个启动类会使用@SpringBootApplication声明。

    下面是@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{
        @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude")
        Class<?>[] exclude() default{};
    
        @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")
        String[] excludeName() default{};
    
        @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "scanBasePackageClasses")
        Class<?>[] scanBasePackageClasses() default{};
    }

    @SpringbootApplication其实是一个组合注解,注意高亮的三个注解:

      @EnableAutoConfiguration:启动注解,该注解会让SpringBoot根据当前项目所依赖的jar包自动配置到项目中;

      @ComponentScan:自动扫描,SpringBoot默认会扫描@SpringbootApplication所在类的同级包,以及它的子包,因此建议将@SpringbootApplication修饰的入口类放在项目包(Group Id + Artifact Id)下,这样可以保证SpringBoot项目可以自动扫描所有依赖的包;

      @SpringBootConfiguration:源码如下:

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

    @SpringBootConfiguration其实又是一个组合注解,注意又一个高亮注解:@Configuration,

      使用@Configuration声明的类,这个类就相当于一个xml配置文件。这样就容易理解了,我们使用Spring,springMVC时都会使用xml配置文件去加载相关依赖jar包的类,而SpringBoot使用@SpringBootConfiguration(推荐使用,来代替@Configuration),然后自动扫描(@ComponentScan),自动配置(@EnableAutoConfiguration)。

      下面就来讲述其自动配置原理:

      上面讲到一个使用了@SpringBootApplication声明的项目启动类,这个启动类有一个main方法,是程序的入口,main方法下面会创建SpringApplication类的run()方法(即SpringApplication.run(App.class,args),App.class是项目启动类)

    run()方法源码:

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

      可以看到,run方法实际上是创建SpringApplication实例,然后又调用run方法,重点在于创建SpringApplication对象,下面是SpringApplication的构造方法源码:

    public SpringApplication(Object... sources){
        initialize(sources);
    }

      以启动类作为参数,调用初始化方法initialize(sources),initialize(sources)源码:

    @SuppressWarnings({"unchecked", "rawtypes"})
    private void initialize(Object[] sources){
        if(sources != null && sources.length>0){
            this.sources.addAll(Arrays.asList(sources));
        }
        .
        .
        .
        setListeners(Collection) getSpringFactoriesInstances(ApplicationListener.class);
        .
        .
        .
    }

    其他源码不是这里的重点,关注高亮代码,接着再进入getSpringFactoriesInstances方法的源码:

    private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args){
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Set<String> names = new LinkedHashSet<String>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        .
        .
        .
    }

    再进入SpringFactoriesLoader的loadFactoryNames方法的源码: 

    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader){
        String factoryClassName = factoryClass.getName();
        try{
            Enumeration<URL> urls = (classLoader != null ? classLoader.getSources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            List<String> result = new ArrayList<String>();
            while(urls.hasMoreElements()){
                URL url = urls.nextElement();
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                String factoryClassNames = properties.getProperty(factoryClassName);
                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassName)));
            }
            return result;
        }catch (IOException ex){
            throw new IllegalArgumentException("Unable to load ["+factoryClass.getName() + "]", ex);
        }
    }        

     上面源码用到了一个常量:FACTORIES_RESOURCE_LOCATION,这个常量源码如下:

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

    到目前为止,已经知道是怎样的了,最终SpringBoot是通过加载META-INF/spring.factories文件进行自动配置,这个文件是放在spring-boot-autoconfigure包下面的META-INF/spring.factories,该 文件时官方文件,里面写了很多相关类名,供其扫描,自动配置。譬如spring.factories文件下面有一行代码,这行代码是一个配置类的类名,叫做org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,它会根据这个全路径类名,找到这个类,进行加载。

    可以看一下org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration的源代码: 

    @Configuration
    @ConditionalOnWebApplication
    @ConditionOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class})
    @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
    @AutoConfigureAfter({DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class})
    public class WebMvcAutoConfiguration{
        ...
    }

       一开始也讲过了,使用了@Configuration的类,相当于一个xml文件,@ContionalOnClass是一个条件注解,意思是只有当当前项目运行环境中有Servlet类,并且有DispatcherServlet类,以及WebMvcConfigurerAdapter类,SpringBoot才会初始化加载这个类,说白了这个类,就相当于spring-servlet.xml文件。既然是spring-servlet.xml文件,那么肯定也会找到很多<bean/>配置类,这里拿视图配置类InternalResourceViewResolver,举个例子,

      在spring-servlet.xml文件里面,我们通常是这样配置声明InternalResourceViewResolver的:

    <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/view"/>
        <property name="suffix" value=".jsp"/>
    </bean>

      到了WebMvcAutoConfiguration类,下面可以找到一个defaultViewResolver方法,可以看一下源码:

    @Bean
    @ContionalOnMissingBean
    public InternalResourceViewResolver defaultViewResolver(){
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix(this.mvcProperties.getView().getPrefix());
        resolver.setSuffix(this.mvcProperties.getView().getSuffix());
        return resolver;
    }

    @Bean,其实就相当于<bean/>标签,defaultViewResolver方法得返回值类型就是InternalResourceViewResolver类,里面的代码就是负责创建InternalResourceViewResolver对象,前后对比一下,清晰了很多,SpringBoot就是把Spring技术的配置换成了加载类,直接封装起来。这里有个问题,prefix和suffix的值怎么确定呢?很简单,就是到application.properties配置,对于springboot2.x,配置信息如下:

    spring.mvc.view.prefix = /WEB-INF/view
    spring.mvc.view.suffix= .jsp

      总结:自动配置原理:从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的配置项,通过反射机制实例化为IOC容器配置类(这些配置类都是使用了@Configuration注解声明的),然后汇总并加载到Spring框架的IOC容器。

  • 相关阅读:
    mysql性能优化
    jdbc connectoin timeout
    java thread dump
    sso实现原理
    api的防重放机制
    java各版本新特性总结
    sql区分大小写的查询
    按分数排名
    MySql常用语句
    mysql之explain用法
  • 原文地址:https://www.cnblogs.com/SysoCjs/p/9781764.html
Copyright © 2020-2023  润新知