前不久刚优化了一个关于文件上传这块的代码,这里面就涉及到图片的路径问题,我将某些属性配置到了配置文件,但是在优化过程中,让我对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注解就是一个东西,真的是活到老,学到老啊。
