[Spring 从模拟开始学习源码]`@Value`的底层实现

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: [Spring 从模拟开始学习源码]`@Value`的底层实现

@Value 注入主要有三种场景:

  1. 注入原始值,比如说注入Value("hello")
  2. 注入变量,比如@Value("${JAVA_HOME}")
  3. 注入spel表达式,比如@Value("#{1 + 2}")


package com.example.value;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
@Configuration // 自动扫描bean3
public class ValueApplication {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context =
            new AnnotationConfigApplicationContext(ValueApplication.class);
        DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
        ContextAnnotationAutowireCandidateResolver resolver =
            new ContextAnnotationAutowireCandidateResolver();
        resolver.setBeanFactory(beanFactory);
        test1(context, resolver);
        test2(context, resolver);
        test3(context, resolver, Bean2.class.getDeclaredField("bean3"));
        System.out.println(">>>>>>>>>>>>>>>>>>>");
        test3(context, resolver, Bean4.class.getDeclaredField("value"));
    }
    private static void test1(AnnotationConfigApplicationContext context,
                              ContextAnnotationAutowireCandidateResolver resolver) throws Exception {
        DependencyDescriptor dd1 =
            new DependencyDescriptor(Bean1.class.getDeclaredField("home"), false);
        // 获取 @Value 的内容
        String value = resolver.getSuggestedValue(dd1).toString();
        System.out.println(value);
        // 解析 ${}
        value = context.getEnvironment().resolvePlaceholders(value);
        System.out.println(value);
    }
    // ${} -> 类型
    private static void test2(AnnotationConfigApplicationContext context,
                              ContextAnnotationAutowireCandidateResolver resolver) throws Exception{
        DependencyDescriptor dd1 =
            new DependencyDescriptor(Bean1.class.getDeclaredField("age"), false);
        // 获取 @Value 的内容
        String value = resolver.getSuggestedValue(dd1).toString();
        System.out.println("@Value 的 value 属性值: " + value);
        // 解析 ${}
        value = context.getEnvironment().resolvePlaceholders(value);
        System.out.println("解析得到的值: " + value);
        System.out.println("解析得到的值的类型: " + value.getClass());
        // 转成字段的类型
        Object age = context.getBeanFactory()
            .getTypeConverter()
            .convertIfNecessary(value, dd1.getDependencyType());
        System.out.println("转换后的类型: " + age.getClass());
    }
    // spel
    private static void test3(AnnotationConfigApplicationContext context,
                              ContextAnnotationAutowireCandidateResolver resolver,
                              Field field) throws Exception {
        DependencyDescriptor dd1 = new DependencyDescriptor(field, false);
        // 获取 @Value 的内容
        String value = resolver.getSuggestedValue(dd1).toString();
        System.out.println("@Value 的 value 属性值: " + value);
        // 解析 ${}
        value = context.getEnvironment().resolvePlaceholders(value);
        System.out.println("解析得到的值: " + value);
        System.out.println("解析得到的值的类型: " + value.getClass());
        // 解析 #{}
        Object bean3 = context.getBeanFactory()
            .getBeanExpressionResolver()
            .evaluate(value, new BeanExpressionContext(context.getBeanFactory(), null));
        // 类型转换
        Object result = context.getBeanFactory()
            .getTypeConverter()
            .convertIfNecessary(bean3, dd1.getDependencyType());
        System.out.println("转换后的类型: " + result.getClass());
    }
    static class Bean1 {
        @Value("${JAVA_HOME}")
        private String home;
        @Value("18")
        private int age;
    }
    static class Bean2 {
        @Value("#{@bean3}")
        private Bean3 bean3;
    }
    @Component("bean3")
    static class Bean3 {
    }
    static class Bean4 {
        @Value("#{'hello, ' + '${JAVA_HOME}'}")
        private String value;
    }
}

输出结果

${JAVA_HOME}
/Users/xx/Library/Java/JavaVirtualMachines/corretto-17.0.8.1/Contents/Home
@Value 的 value 属性值: 18
解析得到的值: 18
解析得到的值的类型: class java.lang.String
转换后的类型: class java.lang.Integer
@Value 的 value 属性值: #{@bean3}
解析得到的值: #{@bean3}
解析得到的值的类型: class java.lang.String
转换后的类型: class com.example.value.ValueApplication$Bean3
>>>>>>>>>>>>>>>>>>>
@Value 的 value 属性值: #{'hello, ' + '${JAVA_HOME}'}
解析得到的值: #{'hello, ' + '/Users/xx/Library/Java/JavaVirtualMachines/corretto-17.0.8.1/Contents/Home'}
解析得到的值的类型: class java.lang.String
转换后的类型: class java.lang.String

用到的几个工具类

获取 @Value内容

Bean2.class.getDeclaredField("bean3")
DependencyDescriptor dd1 = new DependencyDescriptor(field, false);
// 获取 @Value 的内容
String value = resolver.getSuggestedValue(dd1).toString();

解析 ${}内容

value = context.getEnvironment().resolvePlaceholders(value);

解析 SPEL

Object bean3 = context.getBeanFactory()
            .getBeanExpressionResolver()
            .evaluate(value, new BeanExpressionContext(context.getBeanFactory(), null));

类型转换

Object result = context.getBeanFactory()
    .getTypeConverter()
    .convertIfNecessary(bean3, dd1.getDependencyType());
相关文章
|
17天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
23天前
|
Java Spring
在使用Spring的`@Value`注解注入属性值时,有一些特殊字符需要注意
【10月更文挑战第9天】在使用Spring的`@Value`注解注入属性值时,需注意一些特殊字符的正确处理方法,包括空格、引号、反斜杠、新行、制表符、逗号、大括号、$、百分号及其他特殊字符。通过适当包裹或转义,确保这些字符能被正确解析和注入。
|
1天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
9 2
|
7天前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
28 9
|
27天前
|
前端开发 Java 数据库
SpringBoot学习
【10月更文挑战第7天】Spring学习
34 9
|
28天前
|
XML Java 数据格式
Spring学习
【10月更文挑战第6天】Spring学习
19 1
|
1月前
|
Java 测试技术 开发者
springboot学习四:Spring Boot profile多环境配置、devtools热部署
这篇文章主要介绍了如何在Spring Boot中进行多环境配置以及如何整合DevTools实现热部署,以提高开发效率。
55 2
|
1月前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
68 1
|
1月前
|
Java API Spring
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中拦截器的入门教程和实战项目场景实现的详细指南。
23 0
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
|
1月前
|
Java API Spring
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中过滤器的基础知识和实战项目应用的教程。
22 0
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现