最近遇到一个很磨人的需求,需要在jsp里面中动态调用后台函数,动态赋值;也可以通过自定义函数库和标签库达到目的,这里介绍另外一种方法,使用spel
Spring3中引入了Spring表达式语言SpringEL,SpEL是一种强大,简洁的装配Bean的方式,他可以通过运行期间执行的表达式将值装配到我们的属性或构造函数当中,更可以调用JDK中提供的静态常量,获取外部Properties文件中的的配置,可以支持页面、json文件、Properties文件等中使用el表达式的方式动态赋值,非常方便
我在网上也搜了一些相关的文章,几乎没有看到讲实际用法的,都是一些千篇一律的小demo,特此记录一下,希望能帮助大家更好的理解SpringEl的用法
以下为主体类,需要在spel自己的上下文StandardEvaluationContext 中定义我们需求在页面调用的函数,并把它注册到spel中,如这里注册了2个函数getVal1和getVal2
@Service
public class SpelService {
private static final Log log = LogFactory.getLog(SpelService.class);
/**
* 替换el表达式
*
* @param config
* @param variables
*/
public Map replaceSpringEl(Map config, Map variables) {
Map configData = new HashMap(config);
StandardEvaluationContext context = new StandardEvaluationContext(this);
Map functions = getFunctions();
if (null != functions) {
functions.keySet().stream().forEach(key -> context.setVariable(key, functions.get(key)));
}
//设置变量值
if (null != variables) {
variables.keySet().stream().forEach(key -> context.setVariable(key, variables.get(key)));
}
if (null != configData) {
configData.keySet().stream().forEach(key -> configData.put(key, parseDataCascade(configData.get(key), context)));
}
return configData;
}
/**
* 递归执行springel表达式替换
* @param data
* @param context
* @return
*/
private Object parseDataCascade(Object data, StandardEvaluationContext context){
if(data instanceof List){
List listData = (List) data;
List dataList = new ArrayList<>();
for(Object o : listData){
dataList.add(parseDataCascade(o, context));
}
return dataList;
}else if(data instanceof Map){
Map mapData = (Map) data;
Map dataMap = new HashMap<>();
for(Object key : mapData.keySet()){
dataMap.put(key, parseDataCascade(mapData.get(key), context));
}
return dataMap;
}
try {
return execSpringEl(data.toString(), context);
}catch (Exception e){
log.error("执行EL表达式出错! el="+data.toString(), e);
}
return null;
}
/**
* 执行springel表达式
* @param value
* @param context
* @return
*/
public Object execSpringEl(String value, StandardEvaluationContext context){
ExpressionParser parser = new SpelExpressionParser();
//提取表达式
List springEls = RegexUtil.searchListBetween(value, "\\$\\{([^\\}]+)\\}");
if (!springEls.isEmpty()) {
Map elValMap = new HashMap<>();
springEls.stream().forEach(el -> {
try {
Object elValue = parser.parseExpression(el).getValue(context, Object.class);
elValMap.put(el, elValue);
} catch (Exception e) {
elValMap.put(el, e.getMessage());
log.error(e, e);
}
});
//替换表达式
for (String elKey : elValMap.keySet()) {
Object elVal = elValMap.get(elKey);
//如果只有一个表达式,可以直接返回对应的类型
if(elValMap.size() == 1 && value.startsWith("${") && value.endsWith("}")){
return elVal;
}
//多个表达式,取每个表达式的返回值,然后替换
value = value.replace(String.format("${%s}", elKey), null!=elVal?elVal.toString():"");
}
}
return value;
}
/**
* 设置函数
*
* @return
*/
public Map getFunctions() {
Map funMap = new HashMap<>();
try {
Method method = getClass().getDeclaredMethod("getVal1", String.class, Integer.class, String.class, String.class);
if (null != method) {
funMap.put(method.getName(), method);
}
method = getClass().getDeclaredMethod("getVal2", String.class, Integer.class, String.class, String.class, Boolean.class, Integer.class);
if (null != method) {
funMap.put(method.getName(), method);
}
} catch (NoSuchMethodException e) {
log.error(e, e);
}
return funMap;
}
/**
* 注册函数的具体实现
*/
public Integer getVal1(String param1, Integer param2, String param3, String param4){
...
}
public String getVal2(String param1, Integer param2, String param3, String param4, Boolean param5, Integer param6){
...
}
}
下面我们来测试一下,假设flagEl 和htmlEl都是从前台页面传回后台的参数,通过如下调用,可以实现将字符串中的el表达式
${getVal1()}和${getVal2()}传入指定参数执行,并将执行结果替换到el表达式对应的位置返回到前台
@Controller
public class VisitDataIncludeController extends GeiBaseController {
@Autowired
private SpelService spelService;
@RequestMapping("spelTest")
public String spelTest(){
String flagEl = "${getVal1('aaa',1,'bbb','ccc')}";
Map showMap = spelService.replaceSpringEl(ConstructUtils.map("showFlag", flagEl), null);
int showFlag = Integer.parseInt(showMap.get("showFlag").toString());
if(showFlag > 0){
String htmlEl = "总计:${getVal2('aaa',1,'bbb','ccc')}人";
Map htmlMap = spelService.replaceSpringEl(ConstructUtils.map("html", htmlEl), null);
return htmlMap.get("html").toString();
}
return null;
}
}