【小家Spring】Spring中@Value注解有多强大?从原理层面去剖析为何它有如此大的“能耐“(中)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 【小家Spring】Spring中@Value注解有多强大?从原理层面去剖析为何它有如此大的“能耐“(中)

BeanExpressionResolver

策略接口,用于通过将值作为表达式进行评估来解析值(如果适用)。它持有Bean工厂~


// @since 3.0
public interface BeanExpressionResolver {
  // value此时还是复杂类型,比如本例的#{person.name}
  // BeanExpressionContext:持有beanFactory和scope的引用而已~
  @Nullable
  Object evaluate(@Nullable String value, BeanExpressionContext evalContext) throws BeansException;
}


它的唯一实现类StandardBeanExpressionResolver(当然我们是可以自己实现的,比如后面我们自定义@Value功能,通过继承StandardBeanExpressionResolver来扩展实现~毕竟Spring很暖心的给我们开了口)


StandardBeanExpressionResolver


语言解析器的标准实现,支持解析SpEL语言。

public class StandardBeanExpressionResolver implements BeanExpressionResolver {
  // 因为SpEL是支持自定义前缀、后缀的   此处保持了和SpEL默认值的统一
  // 它的属性值事public的   so你可以自定义~
  /** Default expression prefix: "#{". */
  public static final String DEFAULT_EXPRESSION_PREFIX = "#{";
  /** Default expression suffix: "}". */
  public static final String DEFAULT_EXPRESSION_SUFFIX = "}";
  private String expressionPrefix = DEFAULT_EXPRESSION_PREFIX;
  private String expressionSuffix = DEFAULT_EXPRESSION_SUFFIX;
  private ExpressionParser expressionParser; // 它的最终值是SpelExpressionParser
  // 每个表达式都对应一个Expression,这样可以不用重复解析了~~~
  private final Map<String, Expression> expressionCache = new ConcurrentHashMap<>(256);
  // 每个BeanExpressionContex都对应着一个取值上下文~~~
  private final Map<BeanExpressionContext, StandardEvaluationContext> evaluationCache = new ConcurrentHashMap<>(8);
  // 匿名内部类   解析上下文。  和TemplateParserContext的实现一样。个人觉得直接使用它更优雅
  // 和ParserContext.TEMPLATE_EXPRESSION 这个常量也一毛一样
  private final ParserContext beanExpressionParserContext = new ParserContext() {
    @Override
    public boolean isTemplate() {
      return true;
    }
    @Override
    public String getExpressionPrefix() {
      return expressionPrefix;
    }
    @Override
    public String getExpressionSuffix() {
      return expressionSuffix;
    }
  };
  // 空构造函数:默认就是使用的SpelExpressionParser  下面你也可以自己set你自己的实现~
  public StandardBeanExpressionResolver() {
    this.expressionParser = new SpelExpressionParser();
  }
  public void setExpressionParser(ExpressionParser expressionParser) {
    Assert.notNull(expressionParser, "ExpressionParser must not be null");
    this.expressionParser = expressionParser;
  }
  // 解析代码相对来说还是比较简单的,毕竟复杂的解析逻辑都是SpEL里边~  这里只是使用一下而已~
  @Override
  @Nullable
  public Object evaluate(@Nullable String value, BeanExpressionContext evalContext) throws BeansException {
    if (!StringUtils.hasLength(value)) {
      return value;
    }
    try {
      Expression expr = this.expressionCache.get(value);
      if (expr == null) {
        // 注意:此处isTemplte=true
        expr = this.expressionParser.parseExpression(value, this.beanExpressionParserContext);
        this.expressionCache.put(value, expr);
      }
      // 构建getValue计算时的执行上下文~~~
      // 做种解析BeanName的ast为;org.springframework.expression.spel.ast.PropertyOrFieldReference
      StandardEvaluationContext sec = this.evaluationCache.get(evalContext);
      if (sec == null) {
        // 此处指定的rootObject为:evalContext   --> BeanExpressionContext 
        sec = new StandardEvaluationContext(evalContext);
        // 此处新增了4个,加上一个默认的   所以一共就有5个属性访问器了
        // 这样我们的SpEL就能访问BeanFactory、Map、Enviroment等组件了~
        // BeanExpressionContextAccessor表示调用bean的方法~~~~(比如我们此处就是使用的它)  最终执行者为;BeanExpressionContext   它持有BeanFactory的引用嘛~
        // 如果是单村的Bean注入,最终使用的也是BeanExpressionContextAccessor 目前没有找到BeanFactoryAccessor的用于之地~~~
        // addPropertyAccessor只是:addBeforeDefault 所以只是把default的放在了最后,我们手动add的还是保持着顺序的~
        // 注意:这些属性访问器是有先后顺序的,具体看下面~~~
        sec.addPropertyAccessor(new BeanExpressionContextAccessor());
        sec.addPropertyAccessor(new BeanFactoryAccessor());
        sec.addPropertyAccessor(new MapAccessor());
        sec.addPropertyAccessor(new EnvironmentAccessor());
        // setBeanResolver不是接口方法,仅仅辅助StandardEvaluationContext 去获取Bean
        sec.setBeanResolver(new BeanFactoryResolver(evalContext.getBeanFactory()));
        sec.setTypeLocator(new StandardTypeLocator(evalContext.getBeanFactory().getBeanClassLoader()));
        // 若conversionService不为null,就使用工厂的。否则就使用SpEL里默认的DefaultConverterService那个  
        // 最后包装成TypeConverter给set进去~~~
        ConversionService conversionService = evalContext.getBeanFactory().getConversionService();
        if (conversionService != null) {
          sec.setTypeConverter(new StandardTypeConverter(conversionService));
        }
        // 这个很有意思,是一个protected的空方法,因此我们发现若我们自己要自定义BeanExpressionResolver,完全可以继承自StandardBeanExpressionResolver
        // 因为我们绝大多数情况下,只需要提供更多的计算环境即可~~~~~
        customizeEvaluationContext(sec);
        this.evaluationCache.put(evalContext, sec);
      }
      return expr.getValue(sec);
    } catch (Throwable ex) {
      throw new BeanExpressionException("Expression parsing failed", ex);
    }
  }
  //Spring留给我们扩展的SPI  
  protected void customizeEvaluationContext(StandardEvaluationContext evalContext) {
  }
}


如上,整个@Value的解析过程至此就全部完成了。可能有小伙伴会问:怎么不见Resource这种注入呢?其实,从上面不难看出,这个是ConversionService去做的事,它能够把一个字符串转换成Resource对象,仅此而已


总得来说@Value它自己做的事本身还是非常单一的:依赖注入,只是它把众多功能都很好的像插件一样插拔进来了,从而对用户很友好的显示了显它的神通广大~


需要注意的是,在整个依赖的解析过程中,有两个非常重要的接口:BeanExpressionResolver和AutowireCandidateResolver都扮演着重要角色,有兴趣的可以深入继续了解~


SpEL中PropertyAccessor的匹配规则和执行顺序


本来这个内容应该放在SpEL章节更为合适,但是就着这个Spring的环境,就放在此处了。

如上StandardEvaluationContext中默认就会给放在上4+1个PropertyAccessor,他们的匹配规则和执行顺序势必会影响到最终的取值,因此这部分就看看他们的决策原理。


备注:这部分的理解对平时的使用几乎没有关系,但对你自定义扩展功能有较大的影响~


默认注册后,他们的顺序如下:

image.png



它选择的代码在此处(SpEL章节说到了,目前只有PropertyOrFieldReference这个AST才会涉及到PropertyAccessor的决策):

// 它是一个AST~
public class PropertyOrFieldReference extends SpelNodeImpl {
  ...
  // 按照本例而言,contextObject就为BeanExpressionContext
  private List<PropertyAccessor> getPropertyAccessorsToTry(@Nullable Object contextObject, List<PropertyAccessor> propertyAccessors) {
    Class<?> targetType = (contextObject != null ? contextObject.getClass() : null);
    List<PropertyAccessor> specificAccessors = new ArrayList<>();
    List<PropertyAccessor> generalAccessors = new ArrayList<>();
    // 此处propertyAccessors默认会有5个值,如上截图
    for (PropertyAccessor resolver : propertyAccessors) {
      Class<?>[] targets = resolver.getSpecificTargetClasses();
      if (targets == null) {
        // generic resolver that says it can be used for any type
        // 如果为null没指定类型,那就相当于这个处理器可议处理任意type
        generalAccessors.add(resolver);
      }
      else if (targetType != null) {
        for (Class<?> clazz : targets) {
          //你的处理器指定的targetType和我的完全一样,我才选中这个处理器,加入到specificAccessors
          // 找到了就立马break~~~ 所以我说  他们顺序是很重要的
          // specificAccessors里的值要么为[]  要么只有一个
          if (clazz == targetType) {
            specificAccessors.add(resolver);
            break;
          } 
          // targetType若是clazz的子类,加入到generalAccessors 作为通用的处理器
          else if (clazz.isAssignableFrom(targetType)) {
            generalAccessors.add(resolver);
          }
        }
      }
    }
    // 这里有些神操作~~~resolvers 作为最终返回  
    // 1、 先从generalAccessors里面移除掉了所有已匹配上的~~~比如此处generalAccessors的size为0~
    // 2、移除好后把它在添加进resolvers里面去~~~~
    List<PropertyAccessor> resolvers = new ArrayList<>(specificAccessors);
    generalAccessors.removeAll(specificAccessors);
    resolvers.addAll(generalAccessors);
    return resolvers;
  }
  ...
}


如此,我们每一种待处理的类型,一般最终得到的都是唯一一个resolver,从而完成对该引用的取值。


相关文章
|
24天前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
165 73
|
19天前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
49 21
|
24天前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
24天前
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
1月前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
100 14
|
2月前
|
前端开发 Java Spring
Spring MVC核心:深入理解@RequestMapping注解
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的核心,它将HTTP请求映射到控制器的处理方法上。本文将深入探讨`@RequestMapping`注解的各个方面,包括其注解的使用方法、如何与Spring MVC的其他组件协同工作,以及在实际开发中的应用案例。
54 4
|
2月前
|
Java 开发者 Spring
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。
|
2月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
175 2
|
2月前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
69 2
|
2月前
|
消息中间件 Java 数据库
解密Spring Boot:深入理解条件装配与条件注解
Spring Boot中的条件装配与条件注解提供了强大的工具,使得应用程序可以根据不同的条件动态装配Bean,从而实现灵活的配置和管理。通过合理使用这些条件注解,开发者可以根据实际需求动态调整应用的行为,提升代码的可维护性和可扩展性。希望本文能够帮助你深入理解Spring Boot中的条件装配与条件注解,在实际开发中更好地应用这些功能。
51 2