最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用Spring實(shí)現(xiàn)@Value注入靜態(tài)字段

 更新時(shí)間:2024年05月24日 15:25:16   作者:NaTook  
這篇文章主要介紹了使用Spring實(shí)現(xiàn)@Value注入靜態(tài)字段方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

1. 前言

在開發(fā) spring 應(yīng)用時(shí),不可避免會(huì)有讀取配置文件,注入到靜態(tài)變量或者常量字段的場(chǎng)景。

我們最常用的是 @Value 注解,但是 @Value 不支持靜態(tài)字段的注入。

本文搜索了常見的解決方案,發(fā)現(xiàn)或多或少都有一定的限制。

于是結(jié)合自己對(duì) spring 的了解,增強(qiáng) @Value 的功能,實(shí)現(xiàn)靜態(tài)字段的直接注入。

代碼實(shí)現(xiàn)沒有經(jīng)過嚴(yán)格測(cè)試,有問題請(qǐng)批評(píng)指正。

2. 注入靜態(tài)變量常規(guī)方案

2.1. @Value 標(biāo)記 set 方法

示例代碼如下:

  • 類必須是 spring bean
  • @Value 標(biāo)記在 set 方法上,方法名沒有要求
@Component
public class TestConfig {

    private static String name;

    @Value("${test.name}")
    public void inject(String s) {
        name = s;
    }
}

2.2. @ConfigurationProperties 結(jié)合 set 方法

示例代碼如下:

  • 類必須是 spring bean
  • set 方法名必須符合 spring 命名規(guī)范
@ConfigurationProperties(prefix = "test")
@Component
public class TestConfig {

    private static String name;

    public void setName(String n) {
        name = n;
    }
}

2.3. @Value 結(jié)合 @PostConstruct 間接注入

示例代碼如下:

@Component
public class TestConfig {

    @Value("${test.name}")
    private String name;
    private static String staticName;

    @PostConstruct
    public void init() {
        staticName = name;
        System.out.println("staticName = " + staticName);
    }
}

3. 擴(kuò)展 @Value 實(shí)現(xiàn)靜態(tài)字段的注入

前面幾種常規(guī)方案,都不太方便,而且有一定的限制。

所以筆者考慮擴(kuò)展 @Value 注解,實(shí)現(xiàn)以下功能:

  • 可以注入靜態(tài)字段,包括變量和常量
  • 所在類不一定是 spring bean,沒有限制
  • 仍然支持 spel 表達(dá)式
  • 作用的類范圍是指定包及其子包下的所有類

比如可以直接這么使用

public class Foo {

    @Value("${test.string}")
    private static String s;
}

接下來介紹如何實(shí)現(xiàn)

3.1. 自定義標(biāo)識(shí)注解

首先自定義一個(gè)注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectStaticField {
}

這個(gè)注解沒有實(shí)際功能,只是給需要注入靜態(tài)字段的類加上標(biāo)識(shí),使用示例如下:

@InjectStaticField
public class Foo {

    @Value("${test.string}")
    private static String s;
}

為什么要聲明一個(gè)沒有實(shí)際功能的注解呢?這里簡(jiǎn)單解釋一下。

要實(shí)現(xiàn) @Value 注入靜態(tài)變量,不可避免要加載 class,然后遍歷所有靜態(tài)字段

這種方式會(huì)導(dǎo)致項(xiàng)目在初始化時(shí),就把所有的 class 都加載一遍,不管你實(shí)際有沒有用到。

加載類的時(shí)候,靜態(tài)代碼段、靜態(tài)屬性初始化都會(huì)被執(zhí)行,這可能會(huì)導(dǎo)致意料不到的后果。

但這還是存在一樣的問題,判斷一個(gè)類是否標(biāo)識(shí)該自定義注解,還是得加載這個(gè)類再進(jìn)行判斷,這不是多此一舉嗎?

這里有個(gè)小細(xì)節(jié),Spring 提供了一種機(jī)制,可以在不加載類的前提下,讀取類的元信息,包括注解信息。

所以我們可以利用這個(gè)機(jī)制,避免加載不必要的類

3.2. 在 Spring 應(yīng)用啟動(dòng)時(shí)實(shí)現(xiàn)注入

這里通過實(shí)現(xiàn) SpringApplicationRunListener 接口

在 Spring 應(yīng)用啟動(dòng)時(shí)實(shí)現(xiàn)注入靜態(tài)變量的邏輯

@Slf4j
public class RunListener implements SpringApplicationRunListener {

    private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    /**
     * 根據(jù) SpringApplicationRunListener 規(guī)范,必須要定義以下構(gòu)造方法
     */
    public RunListener(SpringApplication application, String[] args) {

    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        initInjectStaticField(context);
    }

    /**
     * 注入靜態(tài)字段
     */
    private static void initInjectStaticField(ConfigurableApplicationContext context) {
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();

        BeanExpressionResolver expressionResolver = beanFactory.getBeanExpressionResolver();
        if (expressionResolver == null) {
            expressionResolver = new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader());
        }

        ConfigurableEnvironment env = context.getEnvironment();

        TypeConverter converter = beanFactory.getTypeConverter();
        CachingMetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(context);

        // 獲取啟動(dòng)類所在包路徑
        String packagePath = ClassUtils.classPackageAsResourcePath(App.class);
        // 配置掃描包 pattern
        String searchPattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                packagePath + '/' + DEFAULT_RESOURCE_PATTERN;

        try {
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);

            // 掃描包
            Resource[] resources = context.getResources(searchPattern);
            for (Resource resource : resources) {
                // 獲取類的元信息,這里不會(huì)觸發(fā)類的加載
                MetadataReader reader = readerFactory.getMetadataReader(resource);
                AnnotationMetadata metadata = reader.getAnnotationMetadata();

                // 只有聲明指定注解的類,才支持靜態(tài)注入
                if (!metadata.isAnnotated(InjectStaticField.class.getName())) {
                    continue;
                }

                // 加載類
                Class<?> clazz = Class.forName(metadata.getClassName());
                for (Field field : clazz.getDeclaredFields()) {
                    // 只注入靜態(tài)字段
                    if (!Modifier.isStatic(field.getModifiers())) {
                        continue;
                    }

                    // 獲取 Value 注解
                    Value anno = field.getDeclaredAnnotation(Value.class);
                    if (anno == null) {
                        continue;
                    }

                    // 讀取配置文件數(shù)據(jù)
                    String strValue = env.resolveRequiredPlaceholders(anno.value());
                    // 解析 spel
                    Object value = expressionResolver.evaluate(strValue,
                            new BeanExpressionContext(beanFactory, null));

                    Class<?> type = field.getType();
                    TypeDescriptor descriptor = new TypeDescriptor(field);
                    // 類型轉(zhuǎn)換
                    Object result = converter.convertIfNecessary(value, type, descriptor);

                    // 確保 private 和 final 修飾的靜態(tài)字段也可以注入
                    field.setAccessible(true);
                    if (Modifier.isFinal(field.getModifiers())) {
                        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
                    }
                    // 注入值
                    field.set(null, result);
                }
            }
        } catch (Exception ex) {
            log.error("Inject static field failed", ex);
            throw new RuntimeException(ex);
        }
        log.info("Inject static field done");
    }
}

要記得在 META-INF/spring.factories 聲明 SpringApplicationRunListener 的實(shí)現(xiàn)類,否則不會(huì)生效

org.springframework.boot.SpringApplicationRunListener=demo.spring.listener.RunListener

3.3. 測(cè)試

要注入的類

@InjectStaticField
public class Foo {

    @Value("${test.string}")
    public static String string;
    @Value("${test.int:100}")
    public static Integer i;
    @Value("#{${test.map}}")
    public static final Map<Object, Object> MAP = null;
}

application.properties

test.name=jack
test.map={key1: 'value1', key2: 'value2'}

測(cè)試打印

@SpringBootApplication
public class App {

    @PostConstruct
    public void init() {
        System.out.println("Foo.string = " + Foo.string);
        System.out.println("Foo.i = " + Foo.i);
        System.out.println("Foo.MAP = " + Foo.MAP);
    }

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Base64加解密的實(shí)現(xiàn)方式實(shí)例詳解

    Base64加解密的實(shí)現(xiàn)方式實(shí)例詳解

    這篇文章主要介紹了Base64加解密的實(shí)現(xiàn)方式實(shí)例詳解的相關(guān)資料,這里提供了實(shí)現(xiàn)實(shí)例,幫助大家學(xué)習(xí)理解這部分內(nèi)容,需要的朋友可以參考下
    2017-08-08
  • Java?this關(guān)鍵字的使用案例詳解

    Java?this關(guān)鍵字的使用案例詳解

    這篇文章主要為大家介紹了Java?this關(guān)鍵字的使用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-01-01
  • SpringBoot 整合 Shiro 密碼登錄與郵件驗(yàn)證碼登錄功能(多 Realm 認(rèn)證)

    SpringBoot 整合 Shiro 密碼登錄與郵件驗(yàn)證碼登錄功能(多 Realm 認(rèn)證)

    這篇文章主要介紹了SpringBoot 整合 Shiro 密碼登錄與郵件驗(yàn)證碼登錄(多 Realm 認(rèn)證),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-02-02
  • 使用Java輕松判斷一個(gè)5位數(shù)是否為回文數(shù)

    使用Java輕松判斷一個(gè)5位數(shù)是否為回文數(shù)

    在編程中,回文數(shù)是一個(gè)正讀和反讀都相同的數(shù)字,本文將介紹如何使用Java編寫一個(gè)程序來判斷一個(gè)5位數(shù)是否為回文數(shù),感興趣的小伙伴可以了解下
    2026-03-03
  • Java中List.stream()的全面使用指南(附完整示例)

    Java中List.stream()的全面使用指南(附完整示例)

    在Java中List是一種常用的集合類型,使用List的stream方法可以方便地對(duì)其中的元素進(jìn)行各種操作,這篇文章主要介紹了Java中List.stream()的全面使用指南,需要的朋友可以參考下
    2025-08-08
  • Java中的棧概述及JVM 中的棧結(jié)構(gòu)

    Java中的棧概述及JVM 中的棧結(jié)構(gòu)

    棧是一種受限的線性數(shù)據(jù)結(jié)構(gòu),只能在一端(稱為 棧頂(Top))進(jìn)行插入和刪除操作,本文給大家介紹Java中的棧概述深入理解JVM中的棧結(jié)構(gòu),感興趣的朋友跟隨小編一起看看吧
    2025-10-10
  • Springmvc加ajax實(shí)現(xiàn)上傳文件并頁(yè)面局部刷新

    Springmvc加ajax實(shí)現(xiàn)上傳文件并頁(yè)面局部刷新

    這篇文章主要介紹了Springmvc加ajax實(shí)現(xiàn)上傳文件并頁(yè)面局部刷新,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-06-06
  • Java計(jì)算百分比的各種方法總結(jié)

    Java計(jì)算百分比的各種方法總結(jié)

    這篇文章主要給大家介紹了關(guān)于Java計(jì)算百分比的各種方法,要計(jì)算百分比并保留兩位小數(shù),首先需要明確計(jì)算公式和java語(yǔ)言的數(shù)學(xué)計(jì)算方法,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-06-06
  • java 遍歷request中的所有表單數(shù)據(jù)的實(shí)例代碼

    java 遍歷request中的所有表單數(shù)據(jù)的實(shí)例代碼

    下面小編就為大家?guī)硪黄猨ava 遍歷request中的所有表單數(shù)據(jù)的實(shí)例代碼。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-09-09
  • Java文檔注釋用法+JavaDoc的使用說明

    Java文檔注釋用法+JavaDoc的使用說明

    這篇文章主要介紹了Java文檔注釋用法+JavaDoc的使用說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07

最新評(píng)論

句容市| 渭源县| 黔东| 资溪县| 金阳县| 黑水县| 普安县| 吉林省| 长沙县| 呈贡县| 昌黎县| 黎城县| 寻甸| 那曲县| 玛多县| 嫩江县| 米易县| 广宁县| 河池市| 黄平县| 枞阳县| 襄樊市| 哈巴河县| 湘阴县| 资溪县| 专栏| 山西省| 喀喇沁旗| 吐鲁番市| 垣曲县| 九龙坡区| 岳阳县| 古交市| 威远县| 万荣县| 香港 | 泰安市| 壤塘县| 平湖市| 保定市| 博乐市|