Java解析JSP表达式

最近系统中希望可以动态解析用户传入的变量,在调研了一下决定JSP表达式是一个非常合适的选择,并且支持嵌套。所以对如何在JAVA中提供并解析标准的JSP表达式做一下总结。

在解析时使用了以下两个包:

       <dependency>
            <groupId>commons-el</groupId>
            <artifactId>commons-el</artifactId>
            <version>1.0</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.2.1</version>
        </dependency>

在解析时主要包含三个步骤:

  1. 使用以上两个包创建解析工具
  2. 自定义jsp表达式,并开发相应的解析方法
  3. 加载解析函数,并利用工具解析
下面是第一步,这里直接上代码:
package com.e;

import org.apache.commons.el.ExpressionEvaluatorImpl;

import javax.servlet.jsp.el.ELException;
import javax.servlet.jsp.el.FunctionMapper;
import javax.servlet.jsp.el.VariableResolver;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by ji on 16-10-10.
 * <p>
 * JSP Expression Language Evaluator.
 */
public class ELEvaluator {

    /**
     * 为ELEvaluator提供解析函数和相关参数。
     * <p/>
     * EL表达式解析的时候可以从context中获取解析的方法和方法的参数
     */
    public static class Context implements VariableResolver, FunctionMapper {

        //存储所需参数
        private Map<String, Object> vars;

        //存储解析的方法
        private Map<String, Method> functions;

        public Context() {
            vars = new HashMap<String, Object>();
            functions = new HashMap<String, Method>();
        }

        /**
         * 添加多个参数. <p/>
         *
         * @param vars 所需要的参数对<参数名,参数值>.
         */
        public void setVariables(Map<String, Object> vars) {
            this.vars.putAll(vars);
        }

        /**
         * 添加一个参数. <p/>
         *
         * @param name  参数名.
         * @param value 参数值.
         */
        public void setVariable(String name, Object value) {
            vars.put(name, value);
        }

        /**
         * 获取参数. <p/>
         *
         * @param name variable 参数名.
         * @return 参数值.
         */
        public Object getVariable(String name) {
            return vars.get(name);
        }

        /**
         * 添加解析函数到context中. <p/>
         *
         * @param prefix       前缀,例如${t:f()},t就是前缀.
         * @param functionName 方法名.
         * @param method       将要调用的方法,方法必须是 public static 的 .
         */
        public void addFunction(String prefix, String functionName, Method method) {
            if ((method.getModifiers() & (Modifier.PUBLIC | Modifier.STATIC))
                    != (Modifier.PUBLIC | Modifier.STATIC)) {
                throw new IllegalArgumentException(
                        "Method[" + method.getName() + "] must be public and static");
            }
            prefix = (prefix != null && prefix.length() > 0) ? prefix + ":" : "";
            functions.put(prefix + functionName, method);
        }

        /**
         * 添加多个解析函数. <p/>
         *
         * @param functions 多个 public static 的方法.
         */
        public void setFunctions(Map<String, Method> functions) {
            this.functions.putAll(functions);
        }

        /**
         * 解析遍历. <p/>
         *
         * @param name 变量名.
         * @return the 变量值.
         * @throws ELException 如果没有定义,那么会抛出异常.
         */
        public Object resolveVariable(String name) throws ELException {
            String tmpName = name.toLowerCase();
            if (!vars.containsKey(tmpName)) {
                throw new ELException("variable [{" + name + "}] cannot be resolved");
            }
            return vars.get(tmpName);
        }

        /**
         * 解析函数变量 ${prefix:name()}.
         * <p/>
         *
         * @param prefix 变量前缀.
         * @param name   方法名.
         * @return
         */
        public Method resolveFunction(String prefix, String name) {
            if (prefix.length() > 0) {
                name = prefix + ":" + name.toLowerCase();
            } else {
                name = name.toLowerCase();
            }
            return functions.get(name);
        }
    }

    /**
     * 每次调用解析的时候,会将包含相应参数的ELEvaluator存在ThreadLocal中
     * </p>
     * 这样就可以做到多个线程调用时可以访问自己的变量
     */
    private static ThreadLocal<ELEvaluator> current = new ThreadLocal<ELEvaluator>();

    public static ELEvaluator getCurrent() {
        return current.get();
    }

    private Context context;

    private ExpressionEvaluatorImpl evaluator = new ExpressionEvaluatorImpl();

    /**
     * 创建一个ElEvaluator
     */
    public ELEvaluator() {
        this(new Context());
    }

    /**
     * @param context
     */
    public ELEvaluator(Context context) {
        this.context = context;
    }

    /**
     * @return .
     */
    public Context getContext() {
        return context;
    }

    /**
     * 可以直接从ELEvaluator设置参数变量 <p/>
     *
     * @param name  变量名.
     * @param value 变量值.
     */
    public void setVariable(String name, Object value) {
        context.setVariable(name, value);
    }

    /**
     * 获取变量值 <p/>
     *
     * @param name 变量名.
     * @return 变量值.
     */
    public Object getVariable(String name) {
        return context.getVariable(name);
    }

    /**
     * 解析方法. <p/>
     *
     * @param expr  要解析的表达式.
     * @param clazz 返回的类型.
     * @return
     * @throws
     */
    public <T> T evaluate(String expr, Class<T> clazz) throws Exception {
        ELEvaluator existing = current.get();
        try {
            current.set(this);
            return (T) evaluator.evaluate(expr, clazz, context, context);
        } catch (ELException ex) {
            if (ex.getRootCause() instanceof Exception) {
                throw (Exception) ex.getRootCause();
            } else {
                throw ex;
            }
        } finally {
            current.set(existing);
        }
    }

}

第二步,自定义表达式,并创建相应的解析方法,这里以三个自定义变量为例,分别是
  • ${add(时间字符串,时间偏移量,偏移单位)}:可以实现时间的加减,传入基准时间字符串(yyyy-MM-dd HH:mm:ss),时间偏移(数值类型:1,2,-1等)和偏移单位(比如:年,月日)
  • ${format(时间字符串,格式化)}:可以实现时间format,传入时间字符串(yyyy-MM-dd HH:mm:ss),格式化(例如:yyyy-MM-dd等)
  • ${year},${day}:可以根据传入的时间来替换字符串中的变量,例如: /tmp/data/${year}/${day}将会输出/tmp/data/2016/10
下面是解析方法代码:
package com.e;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

/**
 * Created by ji on 16-10-12.
 * EL 表达式解析方法
 */
public class JspelFunctions {

    /**
     * 格式化日期
     *
     * @param dateStr
     * @param fmtStr
     * @return
     * @throws ParseException
     */
    public static String format(String dateStr, String fmtStr) throws ParseException {

        SimpleDateFormat parse = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        Date sourceDate = parse.parse(dateStr);

        SimpleDateFormat format = new SimpleDateFormat(fmtStr);

        return format.format(sourceDate);

    }


    /**
     * 支持偏移量
     *
     * @param dateStr 基准时间,格式yyyy-MM-dd HH:mm:ss
     * @param v       偏移量
     * @param cycle   偏移单位:YEAR,MONTH,DAY,HOUR,MINUTE,SECOND
     * @return
     * @throws Exception
     */
    public static String add(String dateStr, int v, String cycle) throws Exception {
        if (cycle == null || cycle.equals("")) {
            throw new RuntimeException("offset方法周期不可为空");
        }

        String result;
        int dt = 0;
        if (cycle.equalsIgnoreCase("YEAR")) {
            dt = Calendar.YEAR;
        } else if (cycle.equalsIgnoreCase("MONTH")) {
            dt = Calendar.MONTH;
        } else if (cycle.equalsIgnoreCase("DAY")) {
            dt = Calendar.DAY_OF_MONTH;
        } else if (cycle.equalsIgnoreCase("HOUR")) {
            dt = Calendar.HOUR_OF_DAY;
        } else if (cycle.equalsIgnoreCase("MINUTE")) {
            dt = Calendar.MINUTE;
        } else if (cycle.equalsIgnoreCase("SECOND")) {
            dt = Calendar.SECOND;
        } else {
            throw new RuntimeException("offset未知的周期:" + cycle);
        }

        String fmtStr = "yyyy-MM-dd HH:mm:ss";
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(fmtStr);
        Date time = simpleDateFormat.parse(dateStr);

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(time);
        calendar.add(dt, v);
        SimpleDateFormat format = new SimpleDateFormat(fmtStr);
        result = format.format(calendar.getTime());
        return result;
    }

}

第三步,加载解析函数
package com.e;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by ji on 16-10-12.
 * 系统启动时将JSP Expression表达式的解析方法加载到内存
 */
public class FunctionLoader {

    public static final Map<String, Method> functions = new HashMap<String, Method>();
    public static final List<String> FUNCTIONS_TO_LOAD = Arrays.asList("add", "format");

    private FunctionLoader() {
    }

    public static void loadElFunction() throws Exception {
        Class clazz = Thread.currentThread().getContextClassLoader()
                .loadClass(JspelFunctions.class.getName());
        for (Method method : clazz.getMethods()) {
            if (method != null) {
                //这里没有设置前缀
                addFunction(null, method.getName().toLowerCase(), method);
            }
        }
    }

    private static void addFunction(String prefix, String functionName, Method method) {
        if (!FUNCTIONS_TO_LOAD.contains(functionName)) {
            return;
        }
        if ((method.getModifiers() & (Modifier.PUBLIC | Modifier.STATIC))
                != (Modifier.PUBLIC | Modifier.STATIC)) {
            throw new IllegalArgumentException(
                    "Method[" + functionName + "] must be public and static");
        }
        prefix = (prefix != null && prefix.length() > 0) ? prefix + ":" : "";
        functions.put(prefix + functionName, method);
    }

}

下面利用上面说的三个例子来看如何使用我们定义的工具和解析方法来解析:
package com.e;

import java.util.Calendar;

/**
 * Created by ji on 16-10-12.
 */
public class Test {

    public static void main(String[] args) throws Exception {
        /** 第一步:需要加载解析的方法,并缓存,以便后面使用 */
        FunctionLoader.loadElFunction();

        /** 第二步:可以开始解析相关工作 */

        //解析${prefix:function()},我在这里在加载解析方法的时候没有添加prefix
        ELEvaluator.Context context = new ELEvaluator.Context();
        ELEvaluator evaluator = new ELEvaluator(context);
        context.setFunctions(FunctionLoader.functions);//设置解析方法

        //1.这里使用一下日期偏移功能,时间加1小时
        String exp = "${add('2016-10-01 12:00:00',1,'HOUR')}";
        String result = evaluator.evaluate(exp, String.class);
        System.out.println(result);//这里将输出 2016-10-01 13:00:00

        //2.在使用一下format日期格式化功能
        String exp1 = "${format('2016-10-01 13:00:00','yyyy-MM-dd')}";
        String fmt1 = evaluator.evaluate(exp1, String.class);
        System.out.println(fmt1); //这里将输出 2016-10-01

        //3.我们再使用一下更复杂的嵌套,是前两个步骤的一个综合
        String exp2 = "${format(add('2016-10-01 12:00:00',1,'HOUR'),'yyyy-MM-dd')}";
        String fmt2 = evaluator.evaluate(exp2, String.class);
        System.out.println(fmt2); //这里将输出 2016-10-01


        /**********************************************************/
        //我们再使用一下非function类型的解析,也就是${yesterday}
        Calendar calendar = Calendar.getInstance();
        evaluator.setVariable("year", (calendar.get(Calendar.MONTH) + 1) < 10 ?
                "0" + (calendar.get(Calendar.MONTH) + 1) : (calendar
                .get(Calendar.MONTH) + 1));
        evaluator.setVariable("day", calendar.get(Calendar.DAY_OF_MONTH) < 10 ?
                "0" + calendar.get(Calendar.DATE) : calendar
                .get(Calendar.DATE));
        String exp3 = "/tmp/data/${year}/${day}";
        System.out.println(evaluator.evaluate(exp3, String.class));

    }

}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值