美文网首页
关于springboot自动加载的那点事儿

关于springboot自动加载的那点事儿

作者: 绝色天龙 | 来源:发表于2019-03-18 22:42 被阅读0次

    背景

    惯例要讲一下背景,毕竟问题来源于生活,困难滋生于工作,要是每天吃吃喝喝、无忧无虑,我相信我也没什么问题好写了^_^
    公司架构组在推新的基础框架,主要是嫌以前的框架用起来太啰嗦了,做了很多感觉多余的工作(思想也是在进步滴)。正好我这边也在做中台服务的下沉和重构,用到了索性就直接接新的框架了,老是用陈年的东西不肯升级,都快觉得自己已经七老八十了,咱要有互联网人的与时俱进的思维,出了问题大不了删库跑路~~~开玩笑哈。

    问题

    进入正题,问题就来源于这里,引了新的jar包后,大致浏览了一遍对接文档,发现确实要做的工作很少,把核心bean用spring的注入方式引进去就可以拿来用了。类似于下面这样:

    @Autowired
    private Template template;
    

    然后就可以拿着template做一些不可描述的事情

    三下五除二我写完了代码,想着总不能干坐着吧,显得工作不饱满呀,研究一下架构组的框架吧。看着看着还真看出了问题(我以为的问题),我的工程是springboot工程,这个Template为啥可以自动注入进来,所在的包路径明明没有被我配置啊。我们知道,springboot是有自动加载bean到context的配置的,前提是标注@SpringBootApplication的启动类所在的包是bean所在包的父包。有点绕口,总结来说,一般我们把启动类放在根包下,其他的类都在子包下,那其他所有自己写的类就都能自动加载了。但是三方jar的包路径明显跟自己定义的不一样,为啥也能自动加载呢?翻了一遍jar包中的所有类也没找到什么特殊的配置,百思不得其解。
    说来也巧,问题都是扎推来的,老项目在啥都没改的情况突然不能启动了,报spring的bean命名冲突,也就是容器内存在同名类,一查两个不同三方包内的bean重名了,纳尼?这种问题为啥现在突然爆出来,而且三方包如果所有的bean都自动加载的话,重名根本不是自己能控制的呀。
    感觉跟前一个是同一问题,就是为啥spring能自动加载三方jar中的bean,各种查资料,终于被我找到了答案,一看也没多高深,就是自己以前没接触过。。。

    原因

    我们知道,java有一个spi机制,在jar包的META-INF/services/目录里创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader。

    这里spring的实现其实与java的spi类似(不知道是不是参考了),在META-INF下创建一个spring.factories文件,像下面这样,然后spring-core包里定义了SpringFactoriesLoader类,这个类实现了检索META-INF/spring.factories文件,并获取指定接口的配置的功能。

    // spring实现的检索META-INF/spring.factories文件的方法
    public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
        Assert.notNull(factoryClass, "'factoryClass' must not be null");
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
        }
        List<T> result = new ArrayList<T>(factoryNames.size());
        for (String factoryName : factoryNames) {
            result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
        }
        AnnotationAwareOrderComparator.sort(result);
        return result;
    }
    
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        try {
            Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(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(factoryClassNames)));
            }
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                    "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }
    

    注意,这里是会查找所有的classpath下的jar包中的spring.factories文件的,spring自己本身的starter机制就是以此为基础实现的。至于这个文件内容到底是怎样的,我们需要怎么配置呢?以spring-boot-autoconfigure为例,下面就是其中的spring.factories文件,这个文件还有其他的作用,我们主要关注Auto Configure模块。

    # Initializers
    org.springframework.context.ApplicationContextInitializer=\
    org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
    
    # Application Listeners
    org.springframework.context.ApplicationListener=\
    org.springframework.boot.autoconfigure.BackgroundPreinitializer
    
    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
    org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
    org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\
    org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
    org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
    org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
    org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
    org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
    org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
    org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
    org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
    org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
    org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
    org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
    org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
    org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
    org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
    org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
    org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
    org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
    org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
    org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
    org.springframework.boot.autoconfigure.jms.hornetq.HornetQAutoConfiguration,\
    org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
    org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
    org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
    org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
    org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
    org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
    org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
    org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\
    org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\
    org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\
    org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
    org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
    org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
    org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
    org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\
    org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
    org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
    org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
    org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\
    org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\
    org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\
    org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
    org.springframework.boot.autoconfigure.velocity.VelocityAutoConfiguration,\
    org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
    org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
    org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
    org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
    org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration
    
    # Template availability providers
    org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
    org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.velocity.VelocityTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.web.JspTemplateAvailabilityProvider
    
    

    如果想要自己实现的jar包被别人依赖的时候能够自动配置,可以仿照该文件写一个,第一行是自动配置的一个注解,然后每一行都是自己定义的一个配置类,比如开头我提到的包自动扫描的功能,只要我在新建一个配置类

    @ComponentScan("cn.jjs.demo")
    public class DemoAutoConfiguration {
    }
    

    然后把这个类加到META/INF下的spring.factories文件中去,像下面这样,这样就可以自动扫描cn.jjs.demo包了,不需要引用方额外配置

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    cn.jjs.demo.DemoAutoConfiguration
    

    结语

    不探索还真发现不了编程之美,有时候刨根问题能够带来不一样的收货,说不定爱上了代码,然后就是步步高升,迎娶白富美,走上人生巅峰。

    针对这种三方jar能够自动配置bean的情况,如果重名了咋办呢?因为这也不是自己能控制的,所以需要我们手动排除,如果确实两个bean都需要用到,那就另外取一个名字,类似下面这样:

    @SpringBootApplication
    // 这里通过类型在扫描中排除了A这个类
    @ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = A.class)})
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    
    // 这里通过配置A的别名解决重名冲突,spring默认会拿方法名作为beanName
    @Configuration
    public class AutoConfig {
        @Bean
        public A anotherA() {
            return new A();
        }
    }
    

    相关文章

      网友评论

          本文标题:关于springboot自动加载的那点事儿

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