Skip to content

Spring Expression Language (SpEL) - 构造器调用详解 🏗️

概述

Spring Expression Language (SpEL) 是 Spring 框架提供的强大表达式语言,它允许我们在运行时查询和操作对象图。其中,构造器调用是 SpEL 的一个重要特性,让我们能够在表达式中动态创建对象实例。

NOTE

SpEL 的构造器调用功能让我们可以在配置文件、注解或代码中动态创建对象,而不需要硬编码具体的实例化逻辑。

为什么需要 SpEL 构造器调用? 🤔

在传统的 Java 开发中,我们通常需要在编译时确定要创建的对象类型。但在某些场景下,我们希望能够:

  • 动态配置:根据配置文件或运行时条件创建不同的对象
  • 表达式驱动:在 Spring 配置中使用表达式来创建复杂对象
  • 灵活性增强:避免硬编码,提高代码的可配置性
kotlin
// 硬编码创建对象
val inventor = Inventor("Albert Einstein", "German")

// 需要根据条件创建不同对象时,代码会变得复杂
val inventor = when (config.type) {
    "scientist" -> Inventor("Albert Einstein", "German")
    "artist" -> Artist("Leonardo da Vinci", "Italian")
    else -> Person("Unknown", "Unknown")
}
kotlin
// 使用 SpEL 动态创建对象
val expression = "new org.example.Inventor('Albert Einstein', 'German')"
val inventor = parser.parseExpression(expression)
    .getValue(Inventor::class.java) 

// 可以从配置文件读取表达式,实现完全的动态化
val configExpression = config.getProperty("inventor.expression")
val dynamicInventor = parser.parseExpression(configExpression)
    .getValue(Inventor::class.java)

核心语法与使用方式 📝

基本语法结构

SpEL 构造器调用使用 new 操作符,语法格式如下:

new 完全限定类名(构造参数1, 构造参数2, ...)

IMPORTANT

除了 java.lang 包中的类型(如 IntegerStringFloat 等),其他所有类型都必须使用完全限定类名。

实际应用示例

让我们通过一个完整的 Kotlin + Spring Boot 示例来理解 SpEL 构造器调用:

kotlin
import org.springframework.expression.ExpressionParser
import org.springframework.expression.spel.standard.SpelExpressionParser
import org.springframework.expression.spel.support.StandardEvaluationContext
import org.springframework.stereotype.Service

// 示例数据类
data class Inventor(
    val name: String,
    val nationality: String,
    val inventions: MutableList<String> = mutableListOf()
)

data class Society(
    val name: String,
    val members: MutableList<Inventor> = mutableListOf()
)

@Service
class SpelConstructorService {
    
    private val parser: ExpressionParser = SpelExpressionParser()
    
    /**
     * 基本构造器调用示例
     */
    fun createInventorWithSpel(): Inventor {
        // 使用 SpEL 创建 Inventor 对象
        val expression = "new com.example.Inventor('Albert Einstein', 'German')"
        
        return parser.parseExpression(expression)
            .getValue(Inventor::class.java) 
    }
    
    /**
     * 在集合操作中使用构造器调用
     */
    fun addInventorToSociety(): Society {
        val society = Society("Royal Society")
        
        // 创建评估上下文,将 society 对象设置为根对象
        val context = StandardEvaluationContext(society)
        
        // 在 SpEL 表达式中调用构造器并添加到集合
        val expression = "members.add(new com.example.Inventor('Isaac Newton', 'English'))"
        
        parser.parseExpression(expression)
            .getValue(context) 
            
        return society
    }
    
    /**
     * 支持可变参数的构造器调用
     */
    fun createInventorWithVarargs(): Inventor {
        // 假设 Inventor 有一个接受可变参数的构造器
        val expression = "new com.example.Inventor('Thomas Edison', 'American', 'Light Bulb', 'Phonograph', 'Motion Picture Camera')"
        
        return parser.parseExpression(expression)
            .getValue(Inventor::class.java)
    }
}

Java.lang 包类型的特殊处理

对于 java.lang 包中的类型,我们可以直接使用简单类名:

kotlin
@Service
class BasicTypeConstructorService {
    
    private val parser: ExpressionParser = SpelExpressionParser()
    
    fun createBasicTypes() {
        // 创建 String 对象(java.lang 包,可以使用简单类名)
        val stringValue = parser.parseExpression("new String('Hello SpEL')")
            .getValue(String::class.java) 
        
        // 创建 Integer 对象
        val integerValue = parser.parseExpression("new Integer(42)")
            .getValue(Integer::class.java)
        
        // 创建 Float 对象
        val floatValue = parser.parseExpression("new Float(3.14)")
            .getValue(Float::class.java)
        
        println("String: $stringValue")
        println("Integer: $integerValue") 
        println("Float: $floatValue")
    }
}

实际业务场景应用 🚀

场景一:动态配置对象创建

在实际项目中,我们经常需要根据配置文件动态创建不同类型的对象:

kotlin
@Configuration
@ConfigurationProperties(prefix = "app.objects")
data class ObjectConfig(
    var expressions: Map<String, String> = mutableMapOf()
)

@Service
class DynamicObjectFactory(
    private val objectConfig: ObjectConfig
) {
    
    private val parser: ExpressionParser = SpelExpressionParser()
    
    /**
     * 根据配置动态创建对象
     */
    fun <T> createObject(key: String, targetClass: Class<T>): T? {
        val expression = objectConfig.expressions[key] ?: return null
        
        return try {
            parser.parseExpression(expression)
                .getValue(targetClass) 
        } catch (e: Exception) {
            println("Failed to create object for key: $key, error: ${e.message}") 
            null
        }
    }
}

对应的配置文件 application.yml

yaml
app:
  objects:
    expressions:
      default-inventor: "new com.example.Inventor('Default Inventor', 'Unknown')"
      einstein: "new com.example.Inventor('Albert Einstein', 'German')"
      newton: "new com.example.Inventor('Isaac Newton', 'English')"

场景二:条件化对象创建

kotlin
@Service
class ConditionalObjectCreator {
    
    private val parser: ExpressionParser = SpelExpressionParser()
    
    /**
     * 根据条件创建不同的对象
     */
    fun createInventorByType(type: String, name: String, nationality: String): Any {
        val context = StandardEvaluationContext().apply {
            setVariable("type", type)
            setVariable("name", name)
            setVariable("nationality", nationality)
        }
        
        // 使用三元操作符进行条件判断
        val expression = """
            #type == 'scientist' ? 
                new com.example.Scientist(#name, #nationality) : 
                new com.example.Inventor(#name, #nationality)
        """.trimIndent()
        
        return parser.parseExpression(expression)
            .getValue(context) ?: throw IllegalArgumentException("Failed to create object") 
    }
}

时序图:SpEL 构造器调用流程 📊

最佳实践与注意事项 ⚠️

1. 异常处理

kotlin
@Service
class SafeSpelConstructorService {
    
    private val parser: ExpressionParser = SpelExpressionParser()
    
    fun safeCreateObject(expression: String): Inventor? {
        return try {
            parser.parseExpression(expression)
                .getValue(Inventor::class.java)
        } catch (e: Exception) {
            when (e) {
                is org.springframework.expression.ParseException -> {
                    println("SpEL 解析错误: ${e.message}") 
                }
                is org.springframework.expression.EvaluationException -> {
                    println("SpEL 执行错误: ${e.message}") 
                }
                else -> {
                    println("未知错误: ${e.message}") 
                }
            }
            null
        }
    }
}

2. 性能优化

TIP

对于频繁使用的表达式,建议缓存解析后的 Expression 对象,避免重复解析。

kotlin
@Service
class CachedSpelService {
    
    private val parser: ExpressionParser = SpelExpressionParser()
    private val expressionCache = ConcurrentHashMap<String, Expression>()
    
    fun getCachedExpression(expressionString: String): Expression {
        return expressionCache.computeIfAbsent(expressionString) { 
            parser.parseExpression(it) 
        }
    }
    
    fun createInventorCached(name: String, nationality: String): Inventor {
        val expression = getCachedExpression(
            "new com.example.Inventor('$name', '$nationality')"
        )
        return expression.getValue(Inventor::class.java)
    }
}

3. 安全考虑

WARNING

在处理用户输入的 SpEL 表达式时,需要特别注意安全性,避免恶意代码注入。

kotlin
@Service
class SecureSpelService {
    
    private val parser: ExpressionParser = SpelExpressionParser()
    
    // 白名单允许的类
    private val allowedClasses = setOf(
        "com.example.Inventor",
        "com.example.Society",
        "java.lang.String",
        "java.lang.Integer"
    )
    
    fun validateAndCreateObject(expression: String): Any? {
        // 简单的安全检查
        if (!isExpressionSafe(expression)) {
            throw SecurityException("不安全的表达式: $expression") 
        }
        
        return parser.parseExpression(expression).getValue()
    }
    
    private fun isExpressionSafe(expression: String): Boolean {
        // 检查是否只包含允许的类名
        return allowedClasses.any { className ->
            expression.contains("new $className(")
        }
    }
}

总结 📚

SpEL 的构造器调用功能为我们提供了强大的动态对象创建能力:

灵活性:可以在运行时动态决定创建什么对象
配置化:支持通过配置文件控制对象创建逻辑
表达式驱动:与其他 SpEL 功能无缝集成
类型安全:支持强类型返回值

IMPORTANT

记住使用完全限定类名(除了 java.lang 包的类),合理处理异常,并在处理用户输入时注意安全性。

通过掌握 SpEL 构造器调用,你可以构建更加灵活和可配置的 Spring 应用程序! 🎉