千家信息网

Springboot如何配置自动加载

发表于:2024-11-19 作者:千家信息网编辑
千家信息网最后更新 2024年11月19日,小编给大家分享一下Springboot如何配置自动加载,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1、springboo
千家信息网最后更新 2024年11月19日Springboot如何配置自动加载

小编给大家分享一下Springboot如何配置自动加载,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

1、springboot自动配置的原理初探

以下注解都在springboot的自动化配置包中:spring-boot-autoconfigure。读者朋友可以跟着一下步骤走一遍,应该对自动配置就有一定的认知了。

1.springboot程序的入口是在启动类,该类有个关键注解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 {    //略……}

2.打开SpringBootApplication注解,上面有个关键注解EnableAutoConfiguration

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration {    //……}

3.EnableAutoConfiguration上有个@Import(AutoConfigurationImportSelector.class),注意AutoConfigurationImportSelector,

@Import作用创建一个AutoConfigurationImportSelector的bean对象,并且加入IoC容器

        //org.springframework.boot.autoconfigure.AutoConfigurationImportSelector//此处只贴了关键方法protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {                List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),                                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;        }

4.AutoConfigurationImportSelector类中的getCandidateConfigurations方法代码如上,其调用了SpringFactoriesLoader的loadFactoryNames方法,来获取

configurations,此configurations列表其实就是要被自动花配置的类。SpringFactoriesLoader的两个重要方法如下:

//org.springframework.core.io.support.SpringFactoriesLoader//只贴了两个关键方法        public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";//此方法返回的是即将要被自动化配置的类的全限定类名,是从META-INF/spring.factories配置的,配置文件中有个org.springframework.boot.autoconfigure.EnableAutoConfiguration 其后面可配置多个想被自动花配置的类    public static List loadFactoryNames(Class factoryType, @Nullab等le ClassLoader classLoader) {            String factoryTypeName = factoryType.getName();            return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());     }        private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {                MultiValueMap result = cache.get(classLoader);                if (result != null) {                        return result;                }                try {                        Enumeration urls = (classLoader != null ?                                        classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :                                        ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));//META-INF/spring.factories                        result = new LinkedMultiValueMap<>();                        while (urls.hasMoreElements()) {                                URL url = urls.nextElement();                                UrlResource resource = new UrlResource(url);                                Properties properties = PropertiesLoaderUtils.loadProperties(resource);                                for (Map.Entry entry : properties.entrySet()) {                                        String factoryTypeName = ((String) entry.getKey()).trim();                                        for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {                                                result.add(factoryTypeName, factoryImplementationName.trim());                                        }                                }                        }                        cache.put(classLoader, result);                        return result;                }                catch (IOException ex) {                        throw new IllegalArgumentException("Unable to load factories from location [" +                                        FACTORIES_RESOURCE_LOCATION + "]", ex);                }        }

5.举例分析,我们在spring.factories中可以看到org.springframework.boot.autoconfigure.EnableAutoConfiguration后有一个org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,说明springboot希望redis能够自动化配置。接着我们打开RedisAutoConfiguration源码查看。此处我故意没复制源码,用的截图,可以看到截图直接有报错,编译错误,错误的原因是我们还没添加spring-boot-starter-data-redis的依赖。**这里有个问题,为什么明明代码都报错,Cannot resolve symbol xxx(未找到类),但是我们的项目依然可以启动?不信你建立一个简单的springboot项目,只添加web依赖,手动打开RedisAutoConfiguration,发现是报红错的,但是你启动项目,发现没任何问题,why??**这个问题后面再解答,先接着看自动配置的问题。

6.先把RedisAutoConfiguration源码复制出来方便我写注释,上面用截图主要是让大家看到报错

@Configuration(proxyBeanMethods = false)@ConditionalOnClass(RedisOperations.class)@EnableConfigurationProperties(RedisProperties.class)@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })public class RedisAutoConfiguration {        @Bean        @ConditionalOnMissingBean(name = "redisTemplate")        public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory)                        throws UnknownHostException {                RedisTemplate template = new RedisTemplate<>();                template.setConnectionFactory(redisConnectionFactory);                return template;        }        @Bean        @ConditionalOnMissingBean        public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)                        throws UnknownHostException {                StringRedisTemplate template = new StringRedisTemplate();                template.setConnectionFactory(redisConnectionFactory);                return template;        }}

看源码可知RedisAutoConfiguration上有一个Configuration和ConditionalOnClass注解,先分析这两个。首先Configuration注解,代表这是个Java config配置类,和spring配置bean的xml文件是一个作用,都是用来实例化bean的,**但是注意还有个@ConditionalOnClass(RedisOperations.class)注解,这个注解的作用是当RedisOperations.class这个类被找到后才会生效,如果没找到此类,那么整个RedisAutoConfiguration就不会生效。**所以当我们引入了redis的依赖,springboot首先会通过RedisAutoConfiguration的方法redisTemplate给我们设置一个默认的redis配置,当然这个方法上也有个注解@ConditionalOnMissingBean(name = "redisTemplate"),就是当我们没有手动配redisTemplate这个bean它才会调用这个默认的方法,注入一个redisTemplate到IoC容器,所以一般情况我们都是手动配置这个redisTemplate,方便我们设置序列化器,如下:

@Configurationpublic class RedisConfig {    /**     * 设置 redisTemplate 的序列化设置     *     * @param redisConnectionFactory     * @return     */    @Bean    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {        // 1.创建 redisTemplate 模版        RedisTemplate template = new RedisTemplate<>();        // 2.关联 redisConnectionFactory        template.setConnectionFactory(redisConnectionFactory);        // 3.创建 序列化类        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);        ObjectMapper om = new ObjectMapper();        // 4.设置可见度        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);        // 5.启动默认的类型        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);        // 6.序列化类,对象映射设置        jackson2JsonRedisSerializer.setObjectMapper(om);        // 7.设置 value 的转化格式和 key 的转化格式        template.setValueSerializer(jackson2JsonRedisSerializer);        template.setKeySerializer(new StringRedisSerializer());        template.afterPropertiesSet();        return template;    }}

RedisAutoConfiguration上还有一下两个注解,作用是从配置文件读取redis相关的信息,ip、端口、密码等

@EnableConfigurationProperties(RedisProperties.class)@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })

2. 补充扩展(解释为什么引用的包都报红错了,项目还能启动)

所有的@Condition注解(包括衍生的)其实都对应一个具体的实现,这个实现类里面有个判断方法叫做matches,返回的是个布尔类型判断值。

打开ConditionalOnClass源码如下,其Conditional注解传递的是个OnClassCondition.class,这就其对应的判断类,也就是说,当我们使用ConditionalOnClass注解时,其实际上调用的是OnClassCondition来判断的

@Target({ ElementType.TYPE, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Documented@Conditional(OnClassCondition.class)public @interface ConditionalOnClass {        /**         * The classes that must be present. Since this annotation is parsed by loading class         * bytecode, it is safe to specify classes here that may ultimately not be on the         * classpath, only if this annotation is directly on the affected component and         * not if this annotation is used as a composed, meta-annotation. In order to         * use this annotation as a meta-annotation, only use the {@link #name} attribute.         * @return the classes that must be present         */        Class[] value() default {};        /**         * The classes names that must be present.         * @return the class names that must be present.         */        String[] name() default {};}

ConditionalOnClass类图如下,它继承了condition接口

打开Condition接口如下,查看注释,注释中有说明 **条件判断是在bean定义即将注册到容器之前进行的,**看过springIoC源码的同学应该知道,spring创建一个对象的过程是当服务启动后,先读取xml配置文件(或者通过注解),根据配置文件先定义一个BeanDefinition,然后把这个bean给放到容器(在spring中实际就是一个Map),然后在根据bean定义,通过反射创建真正的对象。反射会触发类加载,当condition条件不满足时,根据如下注释可知,bean定义后续都被拦截了,连注册都不行,所以自然就不可能通过反射创建对象,不反射自然不会触发类加载,不触发类加载那么RedisAutoConfiguration当然啊不会加载,它不加载,那么即使它里面引用了一个不存在的类也不会有啥问题。

以上是"Springboot如何配置自动加载"这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注行业资讯频道!

0