## TypeLocator
- [TypeLocator](#TypeLocator)
- [一、基本信息](#一基本信息)
- [二、知识储备](#二知识储备)
- [三、基本描述](#三基本描述)
- [四、主要功能](#四主要功能)
- [五、接口源码](#五接口源码)
- [六、主要实现](#六主要实现)
- [七、最佳实践](#七最佳实践)
- [八、与其他组件的关系](#八与其他组件的关系)
- [九、常见问题](#九常见问题)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading)
### 二、知识储备
1. **Spring 表达式语言(SpEL)**
+ 了解 SpEL 的基础语法和用法是必要的,因为 `TypeLocator` 接口通常用于 SpEL 中,用于动态获取类型信息。
2. **反射(Reflection)**
+ 了解 Java 中的反射机制,包括 `Class` 类、`Method` 类、`Field` 类等,因为 `TypeLocator` 接口通常需要使用反射来查找和操作类型信息。
3. **设计模式**
+ 熟悉常见的设计模式,如工厂模式、策略模式等,这些设计模式在实现 `TypeLocator` 接口时可能会有所应用。
### 三、基本描述
`TypeLocator` 接口是 Spring Framework 中的关键接口之一,用于动态定位类型信息,在 Spring 表达式语言(SpEL)等场景中扮演重要角色,通过提供方法如`findType(String typeName)`和`hasType(String typeName)`,允许 SpEL 在运行时动态获取和检查类型信息,增强了 Spring 应用程序的灵活性和功能性。
### 四、主要功能
1. **查找类型信息**
+ 通过 `findType(String typeName)` 方法,根据给定的类型名称查找对应的类型信息,使得 SpEL 在运行时能够动态获取所需类型的信息。
2. **检查类型是否存在**
+ 通过 `hasType(String typeName)` 方法,可以检查是否存在给定名称的类型。这对于确定能否解析给定的类型很有用。
### 五、接口源码
`TypeLocator` 接口定义了一种用于定位类型信息的机制,其中包含一个抽象方法 `findType(String typeName)`,用于根据给定的类型名称查找对应的类型,并返回表示该类型的 `Class` 对象。
```java
/**
* 实现此接口的类应能够定位类型。它们可以使用自定义的 {@link ClassLoader},
* 和/或以任何方式处理常见的包前缀(例如 {@code java.lang})。
*
*
参见 {@link org.springframework.expression.spel.support.StandardTypeLocator}
* 以获取示例实现。
*
* @author Andy Clement
* @since 3.0
*/
@FunctionalInterface
public interface TypeLocator {
/**
* 根据名称查找类型。名称可以是完全限定的,也可以不是(例如 {@code String} 或 {@code java.lang.String})。
* @param typeName 要定位的类型
* @return 表示该类型的 {@code Class} 对象
* @throws EvaluationException 如果查找类型时出现问题
*/
Class> findType(String typeName) throws EvaluationException;
}
```
`StandardTypeLocator` 是一个简单的实现类,实现了 `TypeLocator` 接口,用于根据给定的类型名称查找对应的类型信息。它支持使用上下文 ClassLoader 和注册的导入前缀来定位类型,当找不到类型时会尝试使用注册的导入前缀来定位。
```java
/**
* 一个简单的 {@link TypeLocator} 实现,它使用上下文 ClassLoader(或设置在其上的任何 ClassLoader)。
* 它支持'well-known'包:如果找不到类型,则会尝试注册的导入来定位它。
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public class StandardTypeLocator implements TypeLocator {
@Nullable
private final ClassLoader classLoader;
private final List knownPackagePrefixes = new ArrayList<>(1);
/**
* 为默认的 ClassLoader(通常是线程上下文 ClassLoader)创建一个 StandardTypeLocator。
*/
public StandardTypeLocator() {
this(ClassUtils.getDefaultClassLoader());
}
/**
* 为给定的 ClassLoader 创建一个 StandardTypeLocator。
* @param classLoader 要委托的 ClassLoader
*/
public StandardTypeLocator(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
// 类似于编写常规的 Java 代码,默认只知道 java.lang
registerImport("java.lang");
}
/**
* 注册一个新的导入前缀,用于搜索未限定类型时使用。
* 期望的格式类似于 "java.lang"。
* @param prefix 要注册的前缀
*/
public void registerImport(String prefix) {
this.knownPackagePrefixes.add(prefix);
}
/**
* 从此定位器的导入列表中删除指定的前缀。
* @param prefix 要移除的前缀
*/
public void removeImport(String prefix) {
this.knownPackagePrefixes.remove(prefix);
}
/**
* 返回此 StandardTypeLocator 注册的所有导入前缀的列表。
* @return 注册的导入前缀列表
*/
public List getImportPrefixes() {
return Collections.unmodifiableList(this.knownPackagePrefixes);
}
/**
* 查找(可能是未限定的)类型引用 - 首先使用原始类型名称,然后如果找不到类型名称,则尝试任何注册的前缀。
* @param typeName 要定位的类型
* @return 类型的 Class 对象
* @throws EvaluationException 如果找不到类型
*/
@Override
public Class> findType(String typeName) throws EvaluationException {
String nameToLookup = typeName;
try {
return ClassUtils.forName(nameToLookup, this.classLoader);
}
catch (ClassNotFoundException ey) {
// 在放弃之前尝试任何已注册的前缀
}
for (String prefix : this.knownPackagePrefixes) {
try {
nameToLookup = prefix + '.' + typeName;
return ClassUtils.forName(nameToLookup, this.classLoader);
}
catch (ClassNotFoundException ex) {
// 可能是另一个前缀
}
}
throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName);
}
}
```
### 六、主要实现
1. **StandardTypeLocator**
+ `StandardTypeLocator` 类是实现了 `TypeLocator` 接口的简单实现,用于在给定类型名称时定位类型信息。
### 七、最佳实践
使用 Spring 表达式语言(SpEL)来获取类型信息。通过解析不同的表达式,包括获取特定类型的 Class 对象和比较不同类型的枚举值,展示了 SpEL 在类型定位和类型比较方面的功能。
```java
public class TypeLocatorDemo {
public static void main(String[] args) {
// 创建一个SpEL表达式解析器
ExpressionParser parser = new SpelExpressionParser();
// 解析表达式获取 Date 类的 Class 对象
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
System.out.println("dateClass = " + dateClass);
// 解析表达式获取 String 类的 Class 对象
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
System.out.println("stringClass = " + stringClass);
// 解析表达式比较两个 RoundingMode 枚举值的大小
boolean trueValue = parser.parseExpression("T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR").getValue(Boolean.class);
System.out.println("trueValue = " + trueValue);
}
}
```
运行结果,成功获取了 `java.util.Date` 和 `java.lang.String` 的 Class 对象,并且对 `java.math.RoundingMode` 枚举类型进行了比较,结果为真。
```properties
dateClass = class java.util.Date
stringClass = class java.lang.String
trueValue = true
```
### 八、与其他组件的关系
1. **Class**
+ `Class` 类是 Java 反射中的重要类,用于表示类的运行时信息。它提供了获取类的名称、方法、字段等信息的方法。在 `TypeLocator` 接口的实现中,可能会使用 `Class` 类来表示获取到的类信息。
2. **ClassLoader**
+ `ClassLoader` 类是 Java 中的一个关键类,用于动态加载 Java 类文件到 Java 虚拟机中。它负责加载类文件并生成对应的 `Class` 对象。在与 `TypeLocator` 接口相关的实现中,可能会使用 `ClassLoader` 来加载和获取类信息。
3. **StandardTypeLocator**
+ `TypeLocator`接口的实现类,是一个简单的类型定位器,通常用于在 SpEL 中查找类型信息。
### 九、常见问题
1. **如何实现自定义的 TypeLocator?**
- 我们可能会想要根据特定需求实现自定义的 `TypeLocator` 接口,以满足特定的类型定位需求。在这种情况下,需要实现 `findType(String typeName)` 方法,根据给定的类型名称查找对应的类型信息,并根据需求处理类型的查找逻辑。
2. **如何处理不同的类型查找策略?**
- 在某些情况下,可能需要根据不同的情况使用不同的类型查找策略。例如,可能需要根据不同的包前缀使用不同的类型查找逻辑,或者需要根据不同的条件动态切换类型查找策略。在这种情况下,我们需要考虑如何设计和实现灵活的类型查找策略。
3. **如何处理类型查找失败的情况?**
- 当无法找到指定类型时,需要考虑如何处理类型查找失败的情况。可能的处理方式包括抛出异常、返回默认值或者尝试其他类型查找策略。我们需要根据具体情况选择合适的处理方式,并确保用户能够得到明确的反馈。