最近遇到一个很磨人的需求,需要在jsp里面中动态调用后台函数,动态赋值;也可以通过自定义函数库和标签库,这里介绍另外一种方法,使用spel
Spring3中引入了Spring表达式语言SpringEL,SpEL是一种强大,简洁的装配Bean的方式,他可以通过运行期间执行的表达式将值装配到我们的属性或构造函数当中,更可以调用JDK中提供的静态常量,获取外部Properties文件中的的配置,可以支持页面、json文件、Properties文件等中使用el表达式的方式动态赋值,非常方便
以下为主体类,需要在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<String, Object> variables) {Map configData = new HashMap(config);StandardEvaluationContext context = new StandardEvaluationContext(this);Map<String, Method> 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<String> springEls = RegexUtil.searchListBetween(value, "\\$\\{([^\\}]+)\\}");if (!springEls.isEmpty()) {Map<String, Object> 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<String, Method> getFunctions() {Map<String, Method> 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 {@Autowiredprivate 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 = "<span class='box-right'>总计:<span>${getVal2('aaa',1,'bbb','ccc')}</span>人</span>";Map htmlMap = spelService.replaceSpringEl(ConstructUtils.map("html", htmlEl), null);return htmlMap.get("html").toString();}return null;}
}