由@Value注解引发的一次对bean的思考,值得一看
lipiwang 2024-11-17 13:26 14 浏览 0 评论
目录
- 读取配置文件属性
- @Value注解
- 在配置文件中编写一些属性(application.yml)
- FileConfig类
- 单元测试
- @ConfigurationProperties
- Environment对象
- Properties读取
- @PropertySource
- 静态变量读取配置文件属性
- Bean内部代码加载顺序
- Bean与Bean之前的执行顺序
- @Configuration注解与@Component注解的区别
- 为什么@Configuration注解与@Component注解装配的bean执行顺序不一样?
- 总结
读取配置文件属性
前不久刚优化了一个关于文件上传这块的代码,这里面就涉及到图片的路径问题,我将某些属性配置到了配置文件,但是在优化过程中,让我对bean有了新的认识,既然牵扯到配置文件属性的读取,那么我们先来看看怎么获取配置文件的属性。
@Value注解
在配置文件中编写一些属性(application.yml)
server:
port: 9199
file:
server-name: oss.file.com/ #服务器的域名
mapper: file/ #映射的名字
images: images/ #存储图片的文件夹
video: video/ #存放视频的文件夹
FileConfig类
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() {
//比如images目录下存放着一张123.jpg的图片;video目录下存放着123.mp4
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对象还需要注入,能不能不通过注入就能拿到配置文件的属性呢?这个我等会再说,我们来看看还有没有其他读取配置文件的方式呢?答案肯定是有的,接着往下看。
@ConfigurationProperties
它的实现方式很简单,只需要改动一个地方即可,请看代码:
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 {
/** * 服务器名称 */
//@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;
}
我们在属性上去掉了@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这个对象吗?
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 FileConfig fileConfig;
@Autowired
private Environment environment;
// @Test
// void valueTest() {
// //比如images目录下存放着一张123.jpg的图片;video目录下存放着123.mp4
// 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");
// }
@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/
没错,就是这么简单,是不是感觉到上面的三种获取配置文件的方式很丝滑?其实还有几种实现方式,我这里做一下代码的展示,具体的测试已经结果就不做展示了,为了开篇说的问题能尽快和我们见面,下面我直接进行重点的代码展示。
Properties读取
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 {
/** * 读取配置文件 * @param relpath 相对路径 classpath目录下 * @return */
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/,所以使用这种方式读取配置文件的时候需要注意。
@PropertySource
这种读取方式会有一种弊端,那就是会将注释一起读取出来,所以在使用这种方式读取配置文件信息的时候一定要注意,话不多说,直接上代码
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 {
/** * 服务器名称 */
//@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;
}
@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;
/** * 存放apk的文件夹 */
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;
}
/** * 根据图片的相对路径查询展示的绝对路径 * @param relPath * @return */
public static String getImagePath(String relPath){
return getlocation(relPath,FileTypeEnum.IMG.value());
}
/** * 根据视频的相对路径查询展示的绝对路径 * @param relPath * @return */
public static String getVideoPath(String relPath){
return getlocation(relPath,FileTypeEnum.VIDEO.value());
}
/** * 根据apk的相对路径查询展示的绝对路径 * @param relPath * @return */
public static String getApkPath(String relPath){
return getlocation(relPath,FileTypeEnum.APK.value());
}
/** * 根据相对路径与类型获取到对应的绝对路径 * @param relPath 相对路径 * @param type 类型 * @return */
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 */
APK(3);
private Integer index;
private FileTypeEnum(Integer value) { // 必须是private的,否则编译错误
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内部有静态代码块、代码块、构造函数,还有一些方法,但是只有前三个在实例化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与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注解的区别。
@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注解与@Component注解装配的bean执行顺序不一样?
那是因为@Configuration注解是被动态代理的(CGLIB),很容易理解,执行完静态代码块之后,真正的实例代码块这些都会在代理中执行,而不是它本身,所以我们会看到所有被@Configuration装配的bean都是先执行了静态代码块,而后面才分别执行他们的构造函数,而@Component注解却没有,所以它严格的按照这bean的执行顺序执行,这也就是我们看到的为什么@Configuration注解与@Component注解装配的bean执行顺序不一样。如果你们对spring的注解感兴趣的话,我这里有一个spring的中文官方文档:spring中文官方文档。
总结
写到这里就结束了,spring还是那么的强大,我们对它的了解还是那么的细微,本人最近在努力的加深对spring的理解,希望以后会写出更漂亮的文章,如果不是这次的小优化,我可能一直还是以为@Configuration注解与@Component注解就是一个东西,真的是活到老,学到老啊。
相关推荐
- 前端入门——css 网格轨道详细介绍
-
上篇前端入门——cssGrid网格基础知识整体大概介绍了cssgrid的基本概念及使用方法,本文将介绍创建网格容器时会发生什么?以及在网格容器上使用行、列属性如何定位元素。在本文中,将介绍:...
- Islands Architecture(孤岛架构)在携程新版首页的实践
-
一、项目背景2022,携程PC版首页终于迎来了首次改版,完成了用户体验与技术栈的全面升级。作为与用户连接的重要入口,旧版PC首页已经陪伴携程走过了22年,承担着重要使命的同时,也遇到了很多问题:维护/...
- HTML中script标签中的那些属性
-
HTML中的<script>标签详解在HTML中,<script>标签用于包含或引用JavaScript代码,是前端开发中不可或缺的一部分。通过合理使用<scrip...
- CSS 中各种居中你真的玩明白了么
-
页面布局中最常见的需求就是元素或者文字居中了,但是根据场景的不同,居中也有简单到复杂各种不同的实现方式,本篇就带大家一起了解下,各种场景下,该如何使用CSS实现居中前言页面布局中最常见的需求就是元...
- CSS样式更改——列表、表格和轮廓
-
上篇文章主要介绍了CSS样式更改篇中的字体设置Font&边框Border设置,这篇文章分享列表、表格和轮廓,一起来看看吧。1.列表List1).列表的类型<ulstyle='list-...
- 一文吃透 CSS Flex 布局
-
原文链接:一文吃透CSSFlex布局教学游戏这里有两个小游戏,可用来练习flex布局。塔防游戏送小青蛙回家Flexbox概述Flexbox布局也叫Flex布局,弹性盒子布局。它决定了...
- css实现多行文本的展开收起
-
背景在我们写需求时可能会遇到类似于这样的多行文本展开与收起的场景:那么,如何通过纯css实现这样的效果呢?实现的难点(1)位于多行文本右下角的展开收起按钮。(2)展开和收起两种状态的切换。(3)文本...
- css 垂直居中的几种实现方式
-
前言设计是带有主观色彩的,同样网页设计中的css一样让人摸不头脑。网上列举的实现方式一大把,或许在这里你都看到过,但既然来到这里我希望这篇能让你看有所收获,毕竟这也是前端面试的基础。实现方式备注:...
- WordPress固定链接设置
-
WordPress设置里的最后一项就是固定链接设置,固定链接设置是决定WordPress文章及静态页面URL的重要步骤,从站点的SEO角度来讲也是。固定链接设置决定网站URL,当页面数少的时候,可以一...
- 面试发愁!吃透 20 道 CSS 核心题,大厂 Offer 轻松拿
-
前端小伙伴们,是不是一想到面试里的CSS布局题就发愁?写代码时布局总是对不齐,面试官追问兼容性就卡壳,想跳槽却总被“多列等高”“响应式布局”这些问题难住——别担心!从今天起,咱们每天拆解一...
- 3种CSS清除浮动的方法
-
今天这篇文章给大家介绍3种CSS清除浮动的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。首先,这里就不讲为什么我们要清楚浮动,反正不清除浮动事多多。下面我就讲3种常用清除浮动的...
- 2025 年 CSS 终于要支持强大的自定义函数了?
-
大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!1.什么是CSS自定义属性CSS自...
- css3属性(transform)的一个css3动画小应用
-
闲言碎语不多讲,咱们说说css3的transform属性:先上效果:效果说明:当鼠标移到a标签的时候,从右上角滑出二维码。实现方法:HTML代码如下:需要说明的一点是,a链接的跳转需要用javasc...
- CSS基础知识(七)CSS背景
-
一、CSS背景属性1.背景颜色(background-color)属性值:transparent(透明的)或color(颜色)2.背景图片(background-image)属性值:none(没有)...
- CSS 水平居中方式二
-
<divid="parent"><!--定义子级元素--><divid="child">居中布局</div>...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- maven镜像 (69)
- undefined reference to (60)
- zip格式 (63)
- oracle over (62)
- date_format函数用法 (67)
- 在线代理服务器 (60)
- shell 字符串比较 (74)
- x509证书 (61)
- localhost (65)
- java.awt.headless (66)
- syn_sent (64)
- settings.xml (59)
- 弹出窗口 (56)
- applicationcontextaware (72)
- my.cnf (73)
- httpsession (62)
- pkcs7 (62)
- session cookie (63)
- java 生成uuid (58)
- could not initialize class (58)
- beanpropertyrowmapper (58)
- word空格下划线不显示 (73)
- jar文件 (60)
- jsp内置对象 (58)
- makefile编写规则 (58)