不管是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"/>