不管是json仍是ftl仍是jsp的字符串替换咱们都必须在没有真正返回给浏览器以前来作字符串替换的工做。
大体思路:
发送一个http请求通过解析域名找到对应的tomcat服务,由tomcat决定调用哪个应用程序响应,
而后应用程序找到对应的jsp或者html,咱们经过某种方式获取到jsp或者html的内容经过规则匹配
就能够进行字符串替换。
其实,最简单的方式咱们能够建立一个filter,在url请求过来的时候在doFilter()方法中根据某种url规则(好比以.jsp结尾的请求)
获取请求的文件(jsp,ftl,html), 而后读取文件内容进行匹配,最终能够完成替换。html
定义一个规则凡是符合规则的就进行字符串替换工做,
能够定义一个特殊的标签:好比:
那咱们就规定在jsp或者ftl中以<vt"开头的标签都进行字符串替换。web
其中
咱们能够经过为每个处理的类添加一个标识,经过正则匹配找到这个vt处理类
而后调用这个处理器的处理方法完成字符串替换。ajax
咱们能够抽取一个vt处理器接口,提供一个针对这个接口的抽象实现类,
让其处理相同逻辑的实现并约定好必须要实现的方法,
使用者能够经过实现这个抽象类进行字符串替换逻辑编写。redis
这个由使用者去作,去抽象。spring
在框架上使用这个的时候需找两个点:
1.框架在视图层有没有提供可让咱们对url访问的资源(文件)进行处理的入口?
2.框架有没有可让咱们的vt相关处理类放到ioc容器管理的方法??数据库
IViewTag:实现这个接口,便可以根据标签替换成对应的视图显示内容
AbstractCacheViewTag:抽象的缓存视图标签
DateViewTag: vt引擎的日期处理类 处理 <vt:date uid="2013-12-13 12:31:54"/> 转化为 xx秒前,xx分钟前,xx天前
ViewTag: vt核心引擎,负责对外提供服务json
/** * 视图标签 * <p> * 实现这个接口,便可以根据标签替换成对应的视图显示内容 * * @Date 2016年11月30日 */ public interface IViewTag { /** * 转换 * <p> * 全部须要转换的字符串进行过滤以后,将能进行转换的进行转换放入map * * @param cmsTags 全部的cms标签 * @param needChange 须要转换的map */ void change(final HttpServletRequest req, final Set<String> cmsTags, final Map<String, String> needChange); /** * 清除缓存 * * @param type 数据类别 * @param id 惟一标示 */ void rmCache(final String type, final String id); }
/** * 抽象的缓存视图标签 * <p> * 增长了自动缓存的功能 * * @author ZhuanJunxiang * @Date 2016年11月25日 */ public abstract class AbstractCacheViewTag extends AbstractViewTag { /** * Logger for this class */ private static final Logger logger = LoggerFactory.getLogger(AbstractCacheViewTag.class); /** * 原始的字符串 */ private static final String ORIGIN = "origin"; /** * 原始的字符串,() */ public static final String AFTER_CMS = "afterCms"; /** * 是否开启 */ @Value("#{configProperties['vtCache']}") private boolean vtCache; @Autowired protected IRedisDao redisDao; @Override protected void changeMatch(final HttpServletRequest req, final Set<String> needDeal, final Map<String, String> result) { //获取每一个cms参数的列表 List<Map<String, String>> props = getPropMap(needDeal); //先从redis中获取 if (vtCache) { loadFromRedis(props, result); } //剩余的从db中获取后,进行构造,并塞入缓存 loadFormDb(props, result); //若是还有未处理的暂时不处理 } protected List<Map<String, String>> getPropMap(final Set<String> needDeal) { @SuppressWarnings("unchecked") List<Map<String, String>> result = CollectionUtil.list(); for (String one : needDeal) { Map<String, String> props = ParseUtil.getProps(one); props.put(ORIGIN, one); result.add(props); } return result; } public void loadFromRedis(final List<Map<String, String>> tagProps, final Map<String, String> result) { List<String> keys = CollectionUtil.list(); for (Map<String, String> one : tagProps) { keys.add(getRedisKey(one)); } String[] collection2array = CollectionUtil.collection2array(keys); List<String> re = redisDao.mget(collection2array); if (Util.isEmpty(re)) { return; } for (int i = re.size() - 1; i >= 0; i--) { String string = re.get(i); if (string == null) { continue; } Map<String, String> remove = tagProps.remove(i); result.put(remove.get(ORIGIN), string); } } protected String getRedisKey(final Map<String, String> map) { List<String> keys = CollectionUtil.list("vt", getType()); if (map.containsKey(UID)) { keys.add(map.get(UID)); } if (map.containsKey(SUB_TYPE)) { keys.add(map.get(SUB_TYPE)); } return StringUtil.join(":", keys); } protected void loadFormDb(final List<Map<String, String>> tagProps, final Map<String, String> result) { if (Util.isEmpty(tagProps)) { return; } Map<String, String> needCache = MapUtil.map(); for (Map<String, String> one : tagProps) { String content; try { content = loadFormDb(one); } catch (Exception e) { logger.error(e.getMessage()); content = ""; } result.put(one.get(ORIGIN), content); needCache.put(getRedisKey(one), content); } // redis.set(needCache); //设置过时时间 for (Entry<String, String> entry : needCache.entrySet()) { redisDao.set(entry.getKey(), entry.getValue()); redisDao.expire(entry.getKey(), getExpireTime()); } } /** * 过时时间 * <p> * 默认的过时时间 * * @return 过时时间 */ protected int getExpireTime() { return 1000; } /** * 从数据库里面加载数据 * 处理完毕以后,直接放到tagProps的每一个map的AFTER_CMS属性里面 * * @param tagProp 待处理的数据 */ protected abstract String loadFormDb(final Map<String, String> tagProp); /** * 若是 subType为空,则更新某个对象的全部类型的缓存 * <p> * 若是id为空,则更新subType类型的全部对象的缓存 * <p> * 若是,两个都为空,则表示更新该类getType()的全部缓存 */ @Override public void rmCache(final String subType, final String id) { List<String> keys = CollectionUtil.list("vt", getType()); if (!Util.isEmpty(id)) { keys.add(id); } if (!Util.isEmpty(subType)) { keys.add(subType); } String key = StringUtil.join(":", keys) + "*"; redisDao.del(key); } /** * 替换 * <p> * 把标签替换成根据业务处理后的字符串 * * @param tag 标签对象 * @param result 须要替换的字符串 */ protected void replace(Map<String, String> tag, String result) { tag.put(AFTER_CMS, result); } protected String getFtlPath(String subType) { return getType() + "/" + subType + ".ftl"; } }
/** * vt核心引擎,负责对外提供服务 * * @author ZhuangJunxiang * @Date 2013-12-20 */ @Data public class ViewTag { /** * Logger for this class */ private static final Logger logger = LoggerFactory.getLogger(ViewTag.class); private static int MAX_PARSE_NUM = 3; private static String PATTERNSTR = "<vt:.*?/>"; private static Pattern PATTERN = Pattern.compile(PATTERNSTR); private List<IViewTag> changes; /** * 使用模板解析并替换内容 * * @param str 原字符串 * @param req http请求对象 * @param context ServletContext对象 * @return 待替换的字符传 */ public String parse(final HttpServletRequest req, final String str) { return parseTime(req, str, 0); } /** * 防止存在嵌套标签,进行屡次处理 * 在不超过最大次数的状况下进行屡次处理 * @param req 请求对象 * @param str 待处理的字符串 * @param num 处理次数 * @return 处理后结果 */ private String parseTime(final HttpServletRequest req, final String str, final int num) { if (Util.isEmpty(str)) { return ""; } if (Util.isEmpty(changes)) { logger.error("not set cms changes yet!"); //$NON-NLS-1$ return str; } Set<String> cmsTags = matchCms(str); if (Util.isEmpty(cmsTags)) { return str; } Map<String, String> needChange = MapUtil.map(); for (IViewTag cms : changes) { cms.change(req, cmsTags, needChange); } String result = str; for (Entry<String, String> en : needChange.entrySet()) { String value = filterAjaxValue(req, en.getValue()); String key = filterAjaxKey(req, en.getKey()); result = StringUtil.replaceAll(result, key, value); } if (!Util.isEmpty(cmsTags)) { logger.error("not changed cms: {0}", Json.toJson(cmsTags)); //$NON-NLS-1$ for (String one : cmsTags) { result = StringUtil.replaceAll(result, one, ""); } } if (num < MAX_PARSE_NUM) { return parseTime(req, result, num + 1); } return result; } private String filterAjaxKey(HttpServletRequest req, String inputKey) { // if (!RequestUtil.isAjax(req)) { // return inputKey; // // } String key = inputKey; if (key != null) { key = key.replaceAll("\\\\", "\\\\\\\\"); } return key; } /** * 过滤ajax请求 * 若是是ajax,则须要把html代码转成json * 若是不是,则直接返回便可。 * * @param req * @param value * @return TODO(这里描述每一个参数,若是有返回值描述返回值,若是有异常描述异常) */ private String filterAjaxValue(final HttpServletRequest req, String value) { // if (!RequestUtil.isAjax(req)) { // return value; // // } String json = JsonUtil.toJson(JsonUtil.toJson(value)); return json.substring(3, json.length() - 3); } /** * 匹配cms标签 * * @param str 待匹配的字符串 * @return 匹配到的字符串 */ private Set<String> matchCms(final String str) { Matcher matcher = PATTERN.matcher(str); Set<String> cmsTags = Lang.set(); while (matcher.find()) { cmsTags.add(matcher.group(0)); } return cmsTags; } /** *根据类型清理缓存数据 * * @param type 类型 * @param id 惟一标示 */ public void rmCache(final String type, final String id) { if (Util.isEmpty(changes)) { return; } for (IViewTag cms : changes) { cms.rmCache(type, id); } } public ViewTag() { } }
/** * vt引擎的日期处理类 * <p> * 处理 <vt:date uid="2013-12-13 12:31:54"/> * 转化为 xx秒前,xx分钟前,xx天前 * * @author ZhuangJunxiang * @Date 2013-12-20 */ @Component public class DateViewTag extends AbstractViewTag { /** * Logger for this class */ private static final Logger logger = LoggerFactory.getLogger(DateViewTag.class); @Override protected String getType() { return "date"; } @Override protected void changeMatch(final HttpServletRequest req, final Set<String> needDeal, final Map<String, String> result) { long now = DateTimeUtil.millis(); for (String s : needDeal) { Map<String, String> props = ParseUtil.getProps(s); String uid = props.get("uid"); result.put(s, showDate(uid, now)); } } /** * 显示时间 * * @param date 要处理的时间 * @param now 如今的时间戳 * @return 显示结果 */ private String showDate(final String date, final long now) { try { DateTime dt = DateTimeUtil.string2DateTime(date, null); long time = dt.getMillis(); long diff = now - time; if (diff < 60 * 1000) { return "刚刚"; } if (diff < 60 * 60 * 1000) { return new StringBuilder(diff / (60 * 1000) + "").append("分钟前").toString(); } if (diff < 24 * 60 * 60 * 1000) { return new StringBuilder(diff / (60 * 1000 * 60) + "").append("小时前").toString(); } if (diff < (30L * 24L * 60L * 60L * 1000L)) { return new StringBuilder(diff / (24L * 60L * 60L * 1000L) + "").append("天前").toString(); } if (diff < (12L * 31L * 24L * 60L * 60L * 1000L)) { return new StringBuilder(diff / (30L * 24L * 60L * 60L * 1000L) + "").append("个月前").toString(); } return DateTimeUtil.format(DateTimeUtil.toDate(dt), "yyyy-MM-dd HH:mm:ss"); } catch (Exception e) { logger.error("日期格式异常", e); //$NON-NLS-1$ return ""; } } }
继承InternalResourceView重写renderMergedOutputModel方法
添加对ViewTag类的调用,添加vt处理逻辑。浏览器
/** * jsp视图 * * @author ZhuangJunxiang(529272571@qq.com) * @Date 2017年4月10日 */ public class JspView extends InternalResourceView { /** * (non-Javadoc) * @see */ @Override protected void renderMergedOutputModel(final Map<String, Object> model, final HttpServletRequest request, final HttpServletResponse response) throws Exception { // Expose the model object as request attributes. exposeModelAsRequestAttributes(model, request); // Expose helpers as request attributes, if any. exposeHelpers(request); // Determine the path for the request dispatcher. String dispatcherPath = prepareForRendering(request, response); // Obtain a RequestDispatcher for the target resource (typically a JSP). RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); if (rd == null) { throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + "]: Check that the corresponding file exists within your web application archive!"); } if (logger.isDebugEnabled()) { logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); } ResponseWrapper rw = new ResponseWrapper(response); rd.include(request, rw); String result = ""; try { result = rw.getContent(); } catch (Throwable e) { logger.error(e.getMessage()); } //result = getTransfer().transfer(request, response, result); result = getViewTag().parse(request, result); PrintWriter writer = response.getWriter(); writer.write(HtmlCompressorUtil.compress(result)); response.flushBuffer(); } public ViewTag getViewTag() { ViewTag vt = SpringContextUtil.getBean("viewTag"); return vt; } }
<bean id="viewTag" class="com.we.core.vt.ViewTag"> <property name="changes"> <array> <ref bean="dateViewTag"/> </array> </property> </bean> <!-- 配置视图解析器 如何把handler 方法返回值解析为实际的物理视图 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 添加自定义jsp视图 --> <property name="viewClass" value="com.we.core.web.view.JspView" /> <property name="prefix" value="/WEB-INF/"></property> <property name="suffix" value=".jsp"></property> </bean>
<vt:date uid="2016-12-13 12:31:54"/>