前不久刚优化了一个关于文件上传这块的代码,这里面就涉及到图片的路径问题,我将某些属性配置到了配置文件,但是在优化过程中,让我对bean有了新的认识,既然牵扯到配置文件属性的读取,那么我们先来看看怎么获取配置文件的属性。
server: port: 9199 file: server-name: oss.file.com/ #服务器的域名 mapper: file/ #映射的名字 images: images/ #存储图片的文件夹 video: video/ #存放视频的文件夹 复制代码
package com.ymy.config; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; @Data @Configuration public class FileConfig { @Value("${file.server-name}") private String serverName; @Value("${file.mapper}") private String mapper; @Value("${file.images}") private String images; @Value("${file.video}") private String video; } 复制代码
@Data:lombok依赖中的注解,他会生成对应的get、Set、toString方法,简便了我们的代码量。 @Configuration 装配,被它装配的bean,回被spring的上下文扫描,这个有点类似springmvc的xml配中的bean,这个注解一定要加,否者读取不到配置文件,当然也可以替换成@Component。 @Value:用于读取配置文件的属性,格式 ${对应配置文件中的属性}。
package com.ymy; import com.ymy.config.FileConfig; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest @Slf4j class StaticConfigVarApplicationTests { @Autowired private FileConfig fileConfig; @Test void valueTest() { log.info("服务名:{},映射:{},存储图片的文件夹:{},存放视频的文件夹:{}",fileConfig.getServerName(),fileConfig.getMapper(),fileConfig.getImages(),fileConfig.getVideo()); log.info("图片目录:{}",fileConfig.getServerName()+fileConfig.getMapper()+fileConfig.getImages()+"123.jpg"); log.info("视频目录:{}",fileConfig.getServerName()+fileConfig.getMapper()+fileConfig.getVideo()+"123.mp4"); } } 复制代码
测试结果如下:
2020-03-19 15:56:42.227 INFO 19884 --- [ main] com.ymy.StaticConfigVarApplicationTests : 服务名:oss.file.com/,映射:file/,存储图片的文件夹:images/,存放视频的文件夹:video/ 2020-03-19 15:56:42.229 INFO 19884 --- [ main] com.ymy.StaticConfigVarApplicationTests : 图片目录:oss.file.com/file/images/123.jpg 2020-03-19 15:56:42.230 INFO 19884 --- [ main] com.ymy.StaticConfigVarApplicationTests : 视频目录:oss.file.com/file/video/123.mp4 复制代码
看到这样的结果,说明读取配之文件已经成功了,但是这个还有一点让我很不爽,那就是FileConfig对象还需要注入,能不能不通过注入就能拿到配置文件的属性呢?这个我等会再说,我们来看看还有没有其他读取配置文件的方式呢?答案肯定是有的,接着往下看。
它的实现方式很简单,只需要改动一个地方即可,请看代码:
package com.ymy.config; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; @Data @Slf4j @Configuration @ConfigurationProperties(prefix = "file") public class FileConfig { private String serverName; private String mapper; private String images; private String video; } 复制代码
我们在属性上去掉了@Value注解,在bean中加入了:@ConfigurationProperties(prefix = "file")。 @ConfigurationProperties():这个注解可以让bean的属性与配置文件的属性一一对应,prefix:配置文件的前缀,格式要求:bean属性要与配置文件的属性名相同。
其他代码都不需要改动,我们直接运行单元测试:
2020-03-19 16:21:00.271 INFO 1456 --- [ main] com.ymy.StaticConfigVarApplicationTests : 服务名:oss.file.com/,映射:file/,存储图片的文件夹:images/,存放视频的文件夹:video/ 2020-03-19 16:21:00.273 INFO 1456 --- [ main] com.ymy.StaticConfigVarApplicationTests : 图片目录:oss.file.com/file/images/123.jpg 2020-03-19 16:21:00.273 INFO 1456 --- [ main] com.ymy.StaticConfigVarApplicationTests : 视频目录:oss.file.com/file/video/123.mp4 复制代码
我们发现同样读取到了配置文件的信息,这种方式要求bean的属性名与配置文件的属性名相同,否者将读取不到,不知道你们有没有发现一个问题,那就是FileConfig定义的服务器名称是:serverName,配置文件配置的是:server-name,很神奇的是他居然找到了,为什么呢? @ConfigurationProperties会默认去掉属性名中间的特殊符号,并且不区分大小写
上面的两种读取方式可能很多人都知道,但是有人了解过Environment这个对象吗?
Environment是集成在容器中的抽象,它为 application 环境的两个 key 方面建模:profiles和properties。
profile 是 bean 定义的命名逻辑 group,仅当给定的 profile 为 active 时才向容器注册。 Beans 可以分配给 profile,无论是用 XML 定义还是通过 annotations 定义。与 profiles 相关的Environment object 的作用是确定哪些 profiles(如果有)当前是 active,以及哪些 profiles(如果有)默认情况下应该 active。
Properties 在几乎所有 applications 中都发挥着重要作用,可能源自各种来源:properties files,JVM 系统 properties,系统环境变量,JNDI,servlet context 参数,ad-hoc Properties objects,Maps 等。与 properties 相关的Environment object 的作用是为用户提供方便的服务接口,用于配置 property 源和从中解析 properties。
这是官网的介绍,看着是否很晕?我们只需要抓中重点即可,那就是Environment管理着所有的配置文件。
我们一起来看看Environment是如何获取配置文件属性的,上代码:
package com.ymy; import com.ymy.config.FileConfig; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.core.env.Environment; @SpringBootTest @Slf4j class StaticConfigVarApplicationTests { @Autowired private Environment environment; @Test void environmentTest() { log.info("服务名:{}",environment.getProperty("file.server-name")); log.info("映射:{}",environment.getProperty("file.mapper")); log.info("存放图片的文件夹:{}",environment.getProperty("file.imagese")); log.info("存放视频的文件夹:{}",environment.getProperty("file.video")); } } 复制代码
我们运行单元测试查看结果
2020-03-19 21:20:19.855 INFO 1680 --- [ main] com.ymy.StaticConfigVarApplicationTests : 服务名:oss.file.com/ 2020-03-19 21:20:19.857 INFO 1680 --- [ main] com.ymy.StaticConfigVarApplicationTests : 映射:file/ 2020-03-19 21:20:19.857 INFO 1680 --- [ main] com.ymy.StaticConfigVarApplicationTests : 存放图片的文件夹:images/ 2020-03-19 21:20:19.857 INFO 1680 --- [ main] com.ymy.StaticConfigVarApplicationTests : 存放视频的文件夹:video/ 复制代码
没错,就是这么简单,是不是感觉到上面的三种获取配置文件的方式很丝滑?其实还有几种实现方式,我这里做一下代码的展示,具体的测试已经结果就不做展示了,为了开篇说的问题能尽快和我们见面,下面我直接进行重点的代码展示。
package com.ymy.config; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.support.EncodedResource; import org.springframework.core.io.support.PropertiesLoaderUtils; import java.io.IOException; import java.util.Properties; @Slf4j public class MyPropertiesConfig { public static Properties get(String relpath){ Properties p = null; try { p = PropertiesLoaderUtils.loadProperties(new EncodedResource(new ClassPathResource(relpath),"utf-8")); } catch (IOException e) { e.printStackTrace(); } return p; } } @Test void PeopertiesTest() { Properties properties = MyPropertiesConfig.get("application.yml"); System.out.println(properties.getProperty("server-name")); } 复制代码
Properties读取的配置文件属性和之前的几种有点不一样,它不需要前缀,因为Properties是将配置文件的每一行做处理,比如file下面的server-name被拆分成了两个键值对,file="" servername=oss.file.com/,所以使用这种方式读取配置文件的时候需要注意。
这种读取方式会有一种弊端,那就是会将注释一起读取出来,所以再使用这种方式读取配置文件信息的时候一定要注意,话不多说,直接上代码
package com.ymy.config; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; @Data @Slf4j @Configuration @ConfigurationProperties @PropertySource(value = "classpath:application.yml",encoding = "utf-8") public class FileConfigByPropertisSource { private String servername; private String mapper; private String images; private String video; } @Test void fileConfigByPropertisSourceTest() { log.info("服务名:{}",fileConfigByPropertisSource.getServername()); } 复制代码
这种读取方式还是借助了@ConfigurationProperties,比较好的一点是它可以加载指定的配置文件,这里我没有指定前缀,所以这里肯定是读取不到的,要想得到我们在配置文件中配置的那几个属性,我们还是需要@ConfigurationProperties(prefix = "file")这个玩意,@ConfigurationProperties配合@PropertySource的优势可以读取指定的配置文件,如果没有@PropertySource,那么只能读取到application/yml/application.properties文件。
读取配置文件的方式就先介绍到这里,下面我们一起来看看我遇到的是什么问题?是一个很诡异的问题。
没错,我是在使用静态变量读取配置文件属性的时候发生的诡异问题,我先说说是怎么发生的,然后在用代码走一遍。
我在优化文件上传所以来的配置文件的时候发现使用@Value注解获取的配置文件属性,需要在使用的地方注入管理配置文件的Bean,这样我看着很不爽,所以我想到使用静态变量去读取这些配置属性,请看代码。
package com.ymy.config; import com.ymy.enums.FileTypeEnum; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; @Slf4j @Configuration public class FileConfig { private static String servername; private static String mapper; private static String images; private static String video; private static String apk; @Value("${file.server-name}") public void setServername(String servername) { this.servername = servername; } @Value("${file.mapper}") public void setMapper(String mapper) { this.mapper = mapper; } @Value("${file.images}") public void setImages(String images) { this.images = images; } @Value("${file.video}") public void setVideo(String video) { this.video = video; } @Value("${file.apk}") public void setApk(String apk) { FileConfig.apk = apk; } public static String getImages() { return images; } public static String getVideo() { return video; } public static String getApk() { return apk; } public static String getImagePath(String relPath){ return getlocation(relPath,FileTypeEnum.IMG.value()); } public static String getVideoPath(String relPath){ return getlocation(relPath,FileTypeEnum.VIDEO.value()); } public static String getApkPath(String relPath){ return getlocation(relPath,FileTypeEnum.APK.value()); } private static String getlocation(String relPath, Integer type) { return servername+mapper+FileTypeFactory.map.get(type)+relPath; } } 复制代码
解释一下上面的代码 1.定义静态变量接收配置文件属性 2.分别给上set方法(set方法必须是静态的)。 3.在set方法上使用@Value注解。 4.装配bean,在类中加上@Configuration注解
这样,就能让静态变量获取到配置文件的属性了,而我有继续做了一个骚操作,那就是使用一个枚举类维护文件的类型,比如:1=图片;2=视频;3=apk,然后又创建了一个工厂,用于管理这三种类型,代码如下:
枚举类
package com.ymy.enums; public enum FileTypeEnum { IMG(1), VIDEO(2), APK(3); private Integer index; private FileTypeEnum(Integer value) { this.index = value; } public Integer value() { return this.index; } } 复制代码
工厂类
package com.ymy.config; import com.ymy.enums.FileTypeEnum; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; @Configuration @Slf4j public class FileTypeFactory { public static final Map<Integer,String> map = new HashMap<Integer, String>(); static { map.put(FileTypeEnum.IMG.value(), FileConfig.getImages()); map.put(FileTypeEnum.VIDEO.value(),FileConfig.getVideo()); map.put(FileTypeEnum.APK.value(),FileConfig.getApk()); } } 复制代码
然而问题也就随着到来了,请看单元测试
@Test void pathTest() { log.info("图片文件夹:{},视频文件夹:{},apk文件夹:{}",FileConfig.getImages(),FileConfig.getVideo(),FileConfig.getApk()); String relImgPath = "123.png"; String relVideoPath = "123.mp4"; String relApkPath = "123.apk"; log.info("图片的绝对路径:{}",FileConfig.getImagePath(relImgPath)); log.info("视频的绝对路径:{}",FileConfig.getVideoPath(relVideoPath)); log.info("apk的绝对路径:{}",FileConfig.getApkPath(relApkPath)); } 复制代码
这个时候我们会发现一个神奇的问题,那就是获取文件绝对路径中有null值,但是分别获取他们的文件夹名称却有值,请看结果:
2020-03-20 15:19:44.545 INFO 16956 --- [ main] com.ymy.StaticConfigVarApplicationTests : 图片文件夹:images/,视频文件夹:video/,apk文件夹:apk/ 2020-03-20 15:19:44.547 INFO 16956 --- [ main] com.ymy.StaticConfigVarApplicationTests : 图片的绝对路径:oss.file.com/file/null123.png 2020-03-20 15:19:44.547 INFO 16956 --- [ main] com.ymy.StaticConfigVarApplicationTests : 视频的绝对路径:oss.file.com/file/null123.mp4 2020-03-20 15:19:44.547 INFO 16956 --- [ main] com.ymy.StaticConfigVarApplicationTests : apk的绝对路径:oss.file.com/file/null123.apk 复制代码
我就是用工厂代理了一下,就找不到目录了?
FileConfig.getImages()能拿到值说明静态变量images已经获取到了配置文件的属性,但为什么被工厂代理一下这个值就没了呢?
这心态崩了啊,这能忍?我决定要深挖到底,我要搞明白是什么原因让我拿不到这三个值,如何分析问题?那我们就需要从bean的执行顺序讲起了。
bean内部有静态代码块、代码块、构造函数,还有一些方法,但是只有前三个在实例化Bean的时候都会被执行,那么他们的执行顺序是什么样的?
我们直接用代码接证明它们的执行顺序,我们就改造一下FileTypeFactory这个工厂
package com.ymy.config; import com.ymy.enums.FileTypeEnum; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; @Slf4j public class FileTypeFactory { public FileTypeFactory(){ log.info("我是文件类型工厂的构造函数,我被加载了"); } public static final Map<Integer,String> map = new HashMap<Integer, String>(); static { log.info("我是文件类型工厂的静态代码块,我被加载了"); map.put(FileTypeEnum.IMG.value(), FileConfig.getImages()); map.put(FileTypeEnum.VIDEO.value(),FileConfig.getVideo()); map.put(FileTypeEnum.APK.value(),FileConfig.getApk()); } { log.info("我是文件类型工厂的代码块,我被加载了"); } } 复制代码
单元测试
@Test void loadTest() { FileTypeFactory f1 = new FileTypeFactory(); FileTypeFactory f2 = new FileTypeFactory(); } 复制代码
FileTypeFactory对象被我实例化了两次,至于为什么要实例化两次,一次不就能看到它们的执行顺序了吗?我们先来看执行结果
2020-03-20 15:52:02.700 INFO 12940 --- [ main] com.ymy.config.FileTypeFactory : 我是文件类型工厂的静态代码块,我被加载了 2020-03-20 15:52:02.701 INFO 12940 --- [ main] com.ymy.config.FileTypeFactory : 我是文件类型工厂的代码块,我被加载了 2020-03-20 15:52:02.701 INFO 12940 --- [ main] com.ymy.config.FileTypeFactory : 我是文件类型工厂的构造函数,我被加载了 2020-03-20 15:52:02.701 INFO 12940 --- [ main] com.ymy.config.FileTypeFactory : 我是文件类型工厂的代码块,我被加载了 2020-03-20 15:52:02.701 INFO 12940 --- [ main] com.ymy.config.FileTypeFactory : 我是文件类型工厂的构造函数,我被加载了 复制代码
初始化第一个实例:静态代码块最先加载 -->代码块被加载 —>构造函数被加载
所以我们基本上可以得出一个结论:静态代码块 >代码块>构造函数
现在请看初始化第二个实例,我们发现居然只执行了代码块与构造函数,所以在这里我们又可以得到一个结论,那就是:静态代码块只会在第一次实例化bean的时候被加载。
我现在想知道bean与bean之前的执行顺序,我新建了三个bean:user1、user2、user3,并且分别在它们内部写上了静态代码块、代码块以及构造函数,然后我们运行主程序看看这三个bean的加载顺序。
package com.ymy.test; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; @Slf4j @Configuration public class User1 { public User1(){ log.info("我是User1的构造函数"); } { log.info("我是User1的代码块"); } static { log.info("我是User1的静态代码块"); } } package com.ymy.test; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; @Slf4j @Configuration public class User2 { public User2(){ log.info("我是User2的构造函数"); } { log.info("我是User2的代码块"); } static { log.info("我是User2的静态代码块"); } } package com.ymy.test; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; @Slf4j @Configuration public class User3 { public User3(){ log.info("我是User3的构造函数"); } { log.info("我是User3的代码块"); } static { log.info("我是User3的静态代码块"); } } 复制代码
请注意,我这里使用了@Configuration注解,由于我这里新建的是springboot项目,所以我直接启动mian方法,我们一起来看执行结果:
2020-03-20 16:11:40.885 INFO 8932 --- [ main] com.ymy.StaticConfigVarApplication : Starting StaticConfigVarApplication on LAPTOP-3GLHJRE9 with PID 8932 (D:/springboot/static-config-var/target/classes started by admin in D:/springboot) 2020-03-20 16:11:40.887 INFO 8932 --- [ main] com.ymy.StaticConfigVarApplication : No active profile set, falling back to default profiles: default 2020-03-20 16:11:41.447 INFO 8932 --- [ main] com.ymy.test.User1 : 我是User1的静态代码块 2020-03-20 16:11:41.448 INFO 8932 --- [ main] com.ymy.test.User2 : 我是User2的静态代码块 2020-03-20 16:11:41.451 INFO 8932 --- [ main] com.ymy.test.User3 : 我是User3的静态代码块 2020-03-20 16:11:41.692 INFO 8932 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 9199 (http) 2020-03-20 16:11:41.699 INFO 8932 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2020-03-20 16:11:41.699 INFO 8932 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.31] 2020-03-20 16:11:41.778 INFO 8932 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2020-03-20 16:11:41.779 INFO 8932 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 858 ms 2020-03-20 16:11:41.831 INFO 8932 --- [ main] com.ymy.test.User1 : 我是User1的代码块 2020-03-20 16:11:41.831 INFO 8932 --- [ main] com.ymy.test.User1 : 我是User1的构造函数 2020-03-20 16:11:41.831 INFO 8932 --- [ main] com.ymy.test.User2 : 我是User2的代码块 2020-03-20 16:11:41.831 INFO 8932 --- [ main] com.ymy.test.User2 : 我是User2的构造函数 2020-03-20 16:11:41.832 INFO 8932 --- [ main] com.ymy.test.User3 : 我是User3的代码块 2020-03-20 16:11:41.832 INFO 8932 --- [ main] com.ymy.test.User3 : 我是User3的构造函数 2020-03-20 16:11:41.958 INFO 8932 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor' 2020-03-20 16:11:42.172 INFO 8932 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9199 (http) with context path '' 2020-03-20 16:11:42.176 INFO 8932 --- [ main] com.ymy.StaticConfigVarApplication : Started StaticConfigVarApplication in 1.764 seconds (JVM running for 2.908) 复制代码
程序加载完成之后,我们发现user1、user2、user3的静态代码块被最先执行,在分别执行了他们各自的代码块以及构造函数,我们还发现这三个bean的执行顺序是从上往下,所以在多个bean之间使用@Configuration装配的bean之间的执行顺序:
user1静态代码块 > user2静态代码块 > user3静态代码块 > user1代码块 > user1构造函数 > user2代码块 > user2构造函数 > user3代码块 > user3构造函数
看到这种执行顺序我好像想通了为什么工厂拿不到FileConfig的属性,我们一起来看
我在FileConfig bean中添加了构造函数,因为只有被实例化了属性才会被赋值
FileConfig(){ log.info("这是文件配置类的构造函数"); } 复制代码
FileTypeFactory工厂没有做修改,我们再此启动项目查看结果
2020-03-20 16:30:43.965 INFO 11920 --- [ main] com.ymy.StaticConfigVarApplicationTests : Starting StaticConfigVarApplicationTests on LAPTOP-3GLHJRE9 with PID 11920 (started by admin in D:/springboot/static-config-var) 2020-03-20 16:30:43.966 INFO 11920 --- [ main] com.ymy.StaticConfigVarApplicationTests : No active profile set, falling back to default profiles: default 2020-03-20 16:30:44.731 INFO 11920 --- [ main] com.ymy.config.FileTypeFactory : 我是文件类型工厂的静态代码块,我被加载了 2020-03-20 16:30:44.909 INFO 11920 --- [ main] com.ymy.config.FileConfig : 这是文件配置类的构造函数 2020-03-20 16:30:44.939 INFO 11920 --- [ main] com.ymy.config.FileTypeFactory : 我是文件类型工厂的代码块,我被加载了 2020-03-20 16:30:44.940 INFO 11920 --- [ main] com.ymy.config.FileTypeFactory : 我是文件类型工厂的构造函数,我被加载了 复制代码
看到这个证实了我的想法,工厂类的静态代码块被先加载,配置类的构造被后加载,所以导致了读取属性为空的情况,由于静态代码块是对象被初始化的时候执行,那我只需要将他放到配置类后执行就可以了啊,最简单有效的方式就是去掉FileTypeFactory工厂类的@Configuration注解,这样,程序启动的时候就不会加载工厂,而是在被调用的时候加载,这个时候配置类早就加载完成了,之前为啥要手贱去加一个注解呢?可能是脑子进水了,不过幸亏加了这个注解让我学习了一波@Configuration注解与@Component注解的区别。
刚刚分析的代码中我们并没有牵扯到@Component注解,为什么会学习了一波他们的区别呢?我之前一直认为他们的作用是一致的,知道我测试的时候使用了他们两个之后,我发现我错了。
从表面看@Configuration属于@Component的派生类,所以他们两个@Component能干的事情@Configuration应该也能干,这样的认知虽然没错,但是不严谨,我们还是使用刚刚新建的user1、2、3来说明他们的区别,我们将他们的装配注解替换成@Component
其他都不需要修改,我们直接来看他们的执行顺序
2020-03-20 16:42:40.274 INFO 21048 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 725 ms 2020-03-20 16:42:40.311 INFO 21048 --- [ main] com.ymy.test.User1 : 我是User1的静态代码块 2020-03-20 16:42:40.311 INFO 21048 --- [ main] com.ymy.test.User1 : 我是User1的代码块 2020-03-20 16:42:40.311 INFO 21048 --- [ main] com.ymy.test.User1 : 我是User1的构造函数 2020-03-20 16:42:40.311 INFO 21048 --- [ main] com.ymy.test.User2 : 我是User的静态代码块 2020-03-20 16:42:40.311 INFO 21048 --- [ main] com.ymy.test.User2 : 我是User的代码块 2020-03-20 16:42:40.311 INFO 21048 --- [ main] com.ymy.test.User2 : 我是User的构造函数 2020-03-20 16:42:40.311 INFO 21048 --- [ main] com.ymy.test.User3 : 我是User3的静态代码块 2020-03-20 16:42:40.311 INFO 21048 --- [ main] com.ymy.test.User3 : 我是User3的代码块 2020-03-20 16:42:40.311 INFO 21048 --- [ main] com.ymy.test.User3 : 我是User3的构造函数 复制代码
还记得被@Configuration装配的bean的执行顺序吗?
user1静态代码块 > user2静态代码块 > user3静态代码块 > user1代码块 > user1构造函数 > user2代码块 > user2构造函数 > user3代码块 > user3构造函数
发现什么了吗?被@Component装配的bean执行顺序居然发生改变了,执行顺序如下:
user1静态代码块 > user1代码块 > user1构造函数 > user2静态代码块 > user2代码块 user2构造函数 > user3静态代码块 > user3代码块 > user3构造函数
可以看出被这两个装配的bean执行顺序发生了很大的改变,这是为什么呢?
那是因为@Configuration注解是被动态代理的(CGLIB),很容易理解,执行完静态代码块之后,真正的实例代码块这些都会在代理中执行,而不是它本身,所以我们会看到所有被@Configuration装配的bean都是先执行了静态代码块,而后面猜分别执行他们的构造函数,而@Component注解却没有,所以它严格的按照这bean的执行顺序执行,这也就是我们看到的为什么@Configuration注解与@Component注解装配的bean执行顺序不一样。如果你们对spring的注解感兴趣的话,我这里有一个spring的中文官方文档: spring中文官方文档 。
写到这里就结束了,spring还是那么的强大,我们对它的了解还是那么的细微,本人最近在努力的加深对spring的理解,希望以后会写出更漂亮的文章,如果不是这次的小优化,我可能一直还是以为@Configuration注解与@Component注解就是一个东西,真的是活到老,学到老啊。