【Java EE 学习 70 下】【数据采集系统第二天】【Action中User注入】【设计调查页面】【Action中模型赋值问题】【编辑调查】
一、Action中User注入问题
Action中可能会经常用到已经登陆的User对象,如果每次都从Session中拿会显得非常繁琐。可以想一种方法,当Action想要获取User对象的时候直接使用,这种方法还是得需要借助拦截器的力量,直接在登录拦截器中实现即可,但是登陆拦截器怎么知道该Action想要获取User对象呢?这就需要给Action加上一个接口,如果该Action是该接口的实现类,则表示该Action想要获取User对象。接口仿照HttpRequestAware接口的形式,名字为用户关注接口:
package com.kdyzm.struts.action.aware; import com.kdyzm.domain.User;
/**
* 用户关注接口,用于Action获取Session中的User对象
* @author kdyzm
*
*/
public interface UserAware {
public void setUser(User user);
}
这样在登陆拦截器中直接进行判断即可,如果用户已经登陆了,而且请求的Action是UserAware的实现类,那么就通过setUser方法将User注入到Action中。这样登录拦截器中的intercept方法就变成了如下的形式:
public String intercept(ActionInvocation invocation) throws Exception {
System.out.println("被登录拦截器拦截!");
Action action=(Action) invocation.getAction();
if(action instanceof LoginAction ||action instanceof RegisterAction){
System.out.println("即将进行登录或者注册,直接放行!");
return invocation.invoke();
}
HttpServletRequest request=ServletActionContext.getRequest();
HttpSession session=request.getSession();
User user=(User) session.getAttribute("user");
if(user==null){
System.out.println("用户未登录,必须先登录再访问其他资源!即将跳转到登陆界面!");
return "toLoginPage";
}else{
System.out.println("用户已经登陆,登录拦截器已经放行!");
//如果用户名不为空,而且实现了UserAware接口,就需要调用该接口中的相应方法给类中的成员变量赋值
//TODO 给Action中User动态赋值的方法
if(action instanceof UserAware){
((UserAware)action).setUser(user);
}
return invocation.invoke();
}
}
二、设计调查页面,设计调查页面几乎是该项目中最复杂的一个页面了在“我的调查”中的其中一个调查栏目中直接单击“设计调查”超链接,就直接跳转到设计调查页面,当然需要在Action将调查对象(Survey对象)先查询出来。完整的页面设计代码如下这个页面设计起来非常困难,我也是写了好几个小时才完成的,因为需要考虑到很多因素,需要一步一步的进行调试才行。
完整代码是:
<%@ page language="java" pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" href="${pageContext.servletContext.contextPath}/css/header.css" type="text/css">
<style type="text/css">
table{
border: 1px solid black;
border-collapse: collapse;
width: 100%;
}
table tr{
border-collapse: collapse;
}
tr td{
border:1px solid black;
border-collapse: collapse;
}
a{
color: gray;
text-decoration: none;
}
a:HOVER {
color: red;
}
.tdHL{
text-align: left;
border-width: 0px;
}
.tdHR{
text-align: right;
border-width: 0px;
}
.pageTitle{
background-color: #CCF;
}
.questionTitle{
background-color: #CCC;
}
</style>
<title>Insert title here</title>
</head>
<body>
<%@ include file="/header.jsp" %>
<div>
<!-- 先设计一个变量保存住surveyid -->
<s:set value="%{surveyId}" name="id"/>
<table>
<tr>
<td colspan="2">设计调查</td>
</tr>
<tr>
<td class="tdHL">
<!-- 这里使用sturs2标签直接判断图片是否存在! -->
<!-- 在这里加上一个logo标识 -->
<s:if test="isLogoImageExists()">
<img width="40px" alt="这是logo标识" src="<s:url value='%{logoPath}'/>"/>
</s:if>
<s:else>
<!-- 如果图片不存在,则什么都不显示 -->
</s:else>
<s:property value="title"/>
</td>
<td class="tdHR">
<s:a action="SurveyAction_toUploadLogoPage.action" namespace="/">
<s:param name="surveyId" value="%{#id}"></s:param>
增加Logo
</s:a>
<s:a action="SurveyAction_toEditSurveyPage.action" namespace="/">
<s:param name="surveyId" value="%{#id}"></s:param>
编辑调查</s:a>
<s:a action="PageAction_toAddPagePage.action" namespace="/">
<s:param value="%{#id}" name="surveyId"></s:param>
增加页</s:a>
</td>
</tr>
<tr>
<td colspan="2">
<!-- 主干内容开始 -->
<table>
<tr>
<td width="20px"></td>
<td width="*">
<!-- 迭代页面集合 -->
<table>
<s:iterator value="%{pages}" var="page">
<s:set var="pId" value="%{#page.pageId}"></s:set>
<tr>
<td>
<table>
<tr class="pageTitle">
<!-- 页面标题 -->
<td width="40%" class="tdHL">
<s:property value="%{#page.title}"/>
</td>
<td width="60%" class="tdHR">
<s:a action="PageAction_toEditPageTitlePage.action" namespace="/">
<s:param name="pageId" value="%{#pId}"></s:param>
<s:param name="surveyId" value="%{#id}"></s:param>
编辑页面标题</s:a>
<s:a action="PageAction_toSelectTargetPage.action" namespace="/">
<s:param name="pageId" value="%{#pId}"></s:param>
<s:param name="surveyId" value="%{#id}"></s:param>
移动/复制页
</s:a>
<s:a action="QuestionAction_toSelectQuestionTypePage.action" namespace="/">
<s:param name="pageId" value="%{#pId}"></s:param>
<s:param name="surveyId" value="%{#id}"></s:param>
增加问题</s:a>
<s:a action="PageAction_deletePage.action" namespace="/">
<s:param name="pageId" value="%{#pId}"></s:param>
<s:param name="surveyId" value="%{#id}"></s:param>
删除页</s:a>
</td>
</tr>
<tr>
<td colspan="2">
<table>
<tr>
<td width="20px"></td>
<td>
<!-- 迭代问题的集合 -->
<table>
<s:iterator value="%{#page.questions}" var="question">
<s:set var="qid" value="%{#question.questionId}"></s:set>
<!-- 问题题干 -->
<tr class="questionTitle">
<td class="tdHL"><s:property value="%{#question.title}"/></td>
<td class="tdHR">
<s:a action="QuestionAction_editQuestion.action" namespace="/">
<s:param name="pageId" value="%{#pId}"></s:param>
<s:param name="surveyId" value="%{#id}"></s:param>
<s:param name="questionId" value="%{#qid}"></s:param>
编辑问题</s:a>
<s:a action="QuestionAction_deleteQuestion.action" namespace="/">
<s:param name="pageId" value="%{#pId}"></s:param>
<s:param name="surveyId" value="%{#id}"></s:param>
<s:param name="questionId" value="%{#qid}"></s:param>
删除问题</s:a>
</td>
</tr>
<!-- 问题主体,主要涉及的问题就是问题的分类 -->
<tr>
<td colspan="2">
<!-- 定义变量,为当前类型的题型 -->
<s:set value="%{#question.questionType}" var="qt"></s:set>
<!-- 第一种题型:带有单选框或者复选框的
题目标识就是题号小于4,0-3
-->
<s:if test="#qt lt 4">
<s:iterator value="#question.optionTextArr">
<input type='<s:property value="#qt<2?'radio':'checkbox'"/>'>
<s:property/>
<s:if test="#qt==1 || #qt==3">
<br/>
</s:if>
</s:iterator>
<!-- 处理other的情况 -->
<s:if test="#question.other">
<input type='<s:property value="#qt<2?'radio':'checkbox'"/>'>其它
<s:if test="#question.otherType==1">
<input type="text">
</s:if>
<s:elseif test="#question.otherType==2">
<s:select list="#question.otherSelectOptionArr">
</s:select>
</s:elseif>
</s:if>
</s:if>
<!-- 第二种题型,是下拉列表类型的题型 -->
<s:elseif test="#qt==4">
<s:select list="#question.optionTextArr"></s:select>
</s:elseif>
<s:elseif test="#qt==5">
<s:textfield></s:textfield>
</s:elseif>
<!-- 第三种题型,矩阵问题,6,7,8 -->
<s:else>
<table>
<!-- 列头 -->
<tr>
<td></td>
<s:iterator value="#question.matrixColTitleArr">
<td><s:property/></td>
</s:iterator>
</tr>
<!-- 输出N多行 -->
<s:iterator value="#question.matrixRowTitleArr">
<tr>
<td><s:property/></td>
<s:iterator value="#question.matrixColTitleArr">
<td>
<s:if test="#qt==6">
<input type="radio">
</s:if>
<s:elseif test="#qt==7">
<input type="checkbox">
</s:elseif>
<s:elseif test="#qt==8">
<select>
<s:iterator value="#question.matrixSelectOptionArr">
<option>
<s:property/>
</option>
</s:iterator>
</select>
</s:elseif>
</td>
</s:iterator>
</tr>
</s:iterator>
</table>
</s:else>
</td>
</tr>
</s:iterator>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</s:iterator>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</body>
</html>
最核心的代码是对问题种类的判断:
<s:set value="%{#question.questionType}" var="qt"></s:set>
<!-- 第一种题型:带有单选框或者复选框的
题目标识就是题号小于4,0-3
-->
<s:if test="#qt lt 4">
<s:iterator value="#question.optionTextArr">
<input type='<s:property value="#qt<2?'radio':'checkbox'"/>'>
<s:property/>
<s:if test="#qt==1 || #qt==3">
<br/>
</s:if>
</s:iterator>
<!-- 处理other的情况 -->
<s:if test="#question.other">
<input type='<s:property value="#qt<2?'radio':'checkbox'"/>'>其它
<s:if test="#question.otherType==1">
<input type="text">
</s:if>
<s:elseif test="#question.otherType==2">
<s:select list="#question.otherSelectOptionArr">
</s:select>
</s:elseif>
</s:if>
</s:if>
<!-- 第二种题型,是下拉列表类型的题型 -->
<s:elseif test="#qt==4">
<s:select list="#question.optionTextArr"></s:select>
</s:elseif>
<s:elseif test="#qt==5">
<s:textfield></s:textfield>
</s:elseif>
<!-- 第三种题型,矩阵问题,6,7,8 -->
<s:else>
<table>
<!-- 列头 -->
<tr>
<td></td>
<s:iterator value="#question.matrixColTitleArr">
<td><s:property/></td>
</s:iterator>
</tr>
<!-- 输出N多行 -->
<s:iterator value="#question.matrixRowTitleArr">
<tr>
<td><s:property/></td>
<s:iterator value="#question.matrixColTitleArr">
<td>
<s:if test="#qt==6">
<input type="radio">
</s:if>
<s:elseif test="#qt==7">
<input type="checkbox">
</s:elseif>
<s:elseif test="#qt==8">
<select>
<s:iterator value="#question.matrixSelectOptionArr">
<option>
<s:property/>
</option>
</s:iterator>
</select>
</s:elseif>
</td>
</s:iterator>
</tr>
</s:iterator>
</table>
</s:else>
之前就说过,每个问题的位置不能改变,这是因为将会使用该问题的下标得到该问题是什么种类的问题,一种有九种类型的问题,每一种问题都对应一种独一无二的问题类型。
最终设计效果如下图所示:当然如果只是当前阶段的话是没有这种效果的,必须结合之后的添加问题的功能才行。整个页面都是用表格标签进行了嵌套,所以显得比较难看,但是也没有好的方法,如果有时间的话就会对其进行优化。
三、Action中模型赋值问题。(重点)
每一个Action基本上都是BaseAction的子类,继承BaseAction的优点就是不需要每次都实现模型驱动接口并且重写getModel方法了,模型的赋值过程将会在父类中实现,这里当然也会用到泛型。
但是实现了模型驱动接口需要注意一点事项:可能会有数据库中的信息和前端页面显示的信息不一致的情况发生。
首先实现编辑调查功能,小功能非常小,所以略过不提。但是有必要说一下这个过程,因为这是引发该问题的关键
在设计调查页面单击“编辑调查”->请求SurveyAction.toEditSurveyPage()方法,该方法查询数据库,赋值到model->跳转到editSurveyPage.jsp页面回显,这时候诡异的事情就发生了,回显的时候有异常的情况发生。回显的调查标题是“未命名”,但是该调查原来明明有标题是“居民生活水平调查”。为什么会发生这种情况呢?
原因分析:栈顶指针未改变导致的。看一下在编辑调查方法中的代码:
//跳转到编辑调查的页面上去
public String toEditSurveyPage() throws Exception{
this.model=this.surveyService.getModelById(this.model.getSurveyId());
return "toEditSurveyPage";
}
实际上该方法中只有一句代码而已。就是将数据库中查到的对象赋值给model对象。好像这样就将模型给“刷新”了,但是这也只是“好像”而已,实际上并没有刷新model对象。
在栈顶中存放的是旧model的引用地址,说到底model只是一个变量而已,如果改变了model的值,只是将model的指针指向了新的地址,但是栈顶的model对象中的值并没有被改变。只是无法再通过model对象访问到而已。所以如果直接给model对象赋值但是不做其他修改是没有任何意义的。
解决方法:
方法1.再次压栈,示例代码如下:
ActionContext.getContext().getValueStack().push(model);
当然实际上值栈中就会有两份model对象了,这样做的好处就是解决了model赋值的问题,但是也有弊端,每次都需要这么写不嫌麻烦么?而且这么做一点都不“优雅”。
方法2.通过prepare拦截器加上paramsPrepareStack拦截器栈组合完成该项任务。
具体做法是首先将BaseAction实现Prepare接口,然后在Action中重写接口中的方法。伪代码如下:
public void prepareDesignSurvey(){
this.model = xxx ;
}
当然最重要的还是需要改变默认栈为parameterPrepareStack,否则还是没有任何效果。
Action中的目标方法DesignSurvey中直接跳转页面即可,不需要再对model进行修改。
为什么使用这种方法能够解决问题:实际上引发该问题的原因是模型赋值在模型驱动拦截器获取model之后完成的,这样就导致了即使改变model对象也不会改变栈顶指针,如果将两者顺序颠倒一下,即先给模型赋值,然后模型驱动拦截器再取值,这样就没问题了,关键是在合适的位置实现模型赋值,首先确定的是一定是在模型驱动拦截器之前,合适的位置就是prepareInterceptor拦截器,所以实现Prepare接口然后重写方法即可;但是仅仅这么做还是不够的,因为parametersInterceptor在默认拦截器栈中的顺序是在模型驱动拦截器之后,所以在prepareInterceptor拦截器中获取不到Action中的相关参数,一定会引发类似于id can't load的异常 ,解决方法就是使用paramsPreparedStack拦截器栈,该拦截器栈和默认的拦截器栈的唯一区别就是在prepare拦截器之前增加了一个parameter拦截器,正好解决了Action中属性赋值的问题。
下面是两个拦截器栈的定义:
<!-- An example of the paramsPrepareParams trick. This stack is exactly
the same as the defaultStack, except that it includes one extra interceptor
before the prepare interceptor: the params interceptor. This is useful for
when you wish to apply parameters directly to an object that you wish to
load externally (such as a DAO or database or service layer), but can't load
that object until at least the ID parameter has been loaded. By loading the
parameters twice, you can retrieve the object in the prepare() method, allowing
the second params interceptor to apply the values on the object. -->
<interceptor-stack name="paramsPrepareParamsStack">
<interceptor-ref name="exception" />
<interceptor-ref name="alias" />
<interceptor-ref name="i18n" />
<interceptor-ref name="checkbox" />
<interceptor-ref name="multiselect" />
<interceptor-ref name="params">
<param name="excludeParams">dojo\..*,^struts\..*</param>
</interceptor-ref>
<interceptor-ref name="servletConfig" />
<interceptor-ref name="prepare" />
<interceptor-ref name="chain" />
<interceptor-ref name="modelDriven" />
<interceptor-ref name="fileUpload" />
<interceptor-ref name="staticParams" />
<interceptor-ref name="actionMappingParams" />
<interceptor-ref name="params">
<param name="excludeParams">dojo\..*,^struts\..*</param>
</interceptor-ref>
<interceptor-ref name="conversionError" />
<interceptor-ref name="validation">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
<interceptor-ref name="workflow">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
</interceptor-stack>
方法3.修改模型驱动拦截器,设置刷新model标识为true
模型驱动拦截器代码很短,看看它到底干了什么事:
public class ModelDrivenInterceptor extends AbstractInterceptor { protected boolean refreshModelBeforeResult = false;
public void setRefreshModelBeforeResult(boolean val) {
this.refreshModelBeforeResult = val;
} @Override
public String intercept(ActionInvocation invocation) throws Exception {
Object action = invocation.getAction(); if (action instanceof ModelDriven) {
ModelDriven modelDriven = (ModelDriven) action;
ValueStack stack = invocation.getStack();
Object model = modelDriven.getModel();
if (model != null) {
stack.push(model);
}
if (refreshModelBeforeResult) {
invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model));
}
}
return invocation.invoke();
} /**
* Refreshes the model instance on the value stack, if it has changed
*/
protected static class RefreshModelBeforeResult implements PreResultListener {
private Object originalModel = null;
protected ModelDriven action; public RefreshModelBeforeResult(ModelDriven action, Object model) {
this.originalModel = model;
this.action = action;
} public void beforeResult(ActionInvocation invocation, String resultCode) {
ValueStack stack = invocation.getStack();
CompoundRoot root = stack.getRoot(); boolean needsRefresh = true;
Object newModel = action.getModel(); // Check to see if the new model instance is already on the stack
for (Object item : root) {
if (item.equals(newModel)) {
needsRefresh = false;
break;
}
} // Add the new model on the stack
if (needsRefresh) { // Clear off the old model instance
if (originalModel != null) {
root.remove(originalModel);
}
if (newModel != null) {
stack.push(newModel);
}
}
}
}
}
在模型驱动拦截器中有个非常重要的属性:refreshModelBeforeResult,该属性是一个标识字段的属性,用于标识是否需要刷新model,什么是刷新model,就是重新获取model的值并压栈,这样最终就能够实现栈顶中的model对象是最新的model对象。
实现原理:执行模型驱动拦截器的时候,会判断refreshModelBeforeResult的值是否为true,如果为true,则给invocation对象添加一个PreResultListener监听器,模型驱动拦截器中有一个静态类RefreshModelBeforeResult实现了该监听器的接口,观察该实现类的类名,翻译成中文就是“在执行Result之前刷新Model对象”,真是一个直白的方法名,我们知道执行结果集是在最后完成的,那么在执行结果集之前刷新Model对象的话就不会出现上述问题了。它实现刷新的原理十分简单,就是先让老的Model对象弹栈,再获取新的model对象并将新的model对象压栈,OK,前端页面获取的一定就是最新的Model对象了。
方法4:使用BeanUtils中的copy属性的方法直接给model对象中的所有属性重新赋值,使用这种方式的好处就是不需要考虑model对象是新的对象还是老的对象,但是有一点不好之处就是该方法底层使用反射技术,效率比较低,而且每次都要写该方法实际上和方法一没有什么差别了,都比较麻烦,而且显得十分的“不优雅”。
四、编辑调查,略。
【Java EE 学习 70 下】【数据采集系统第二天】【Action中User注入】【设计调查页面】【Action中模型赋值问题】【编辑调查】的更多相关文章
- 【Java EE 学习 80 下】【调用WebService服务的四种方式】【WebService中的注解】
不考虑第三方框架,如果只使用JDK提供的API,那么可以使用三种方式调用WebService服务:另外还可以使用Ajax调用WebService服务. 预备工作:开启WebService服务,使用jd ...
- 【Java EE 学习 70 上】【数据采集系统第二天】【数据加密处理】【登陆验证】【登陆拦截器】【新建调查】【查询调查】
一.数据加密处理 这里使用MD5加密处理,使用java中自带加密工具类MessageDigest. 该类有一个方法digest,该方法输入参数是一个字符串返回值是一个长度为16的字节数组.最关键的是需 ...
- 【Java EE 学习 74 下】【数据采集系统第六天】【使用Jfreechart的统计图实现】【将JFreechart整合到项目中】
之前说了JFreechart的基本使用方法,包括生成饼图.柱状统计图和折线统计图的方法.现在需要将其整合到数据采集系统中根据调查结果生成三种不同的统计图. 一.统计模型的分析和设计 实现统计图显示的流 ...
- 【Java EE 学习 69 下】【数据采集系统第一天】【实体类分析和Base类书写】
之前SSH框架已经搭建完毕,现在进行实体类的分析和Base类的书写.Base类是抽象类,专门用于继承. 一.实体类关系分析 既然是数据采集系统,首先调查实体(Survey)是一定要有的,一个调查有多个 ...
- 【Java EE 学习 77 下】【数据采集系统第九天】【使用spring实现答案水平分库】【未解决问题:分库查询问题】
之前说过,如果一个数据库中要存储的数据量整体比较小,但是其中一个表存储的数据比较多,比如日志表,这时候就要考虑分表存储了:但是如果一个数据库整体存储的容量就比较大,该怎么办呢?这时候就需要考虑分库了, ...
- 【Java EE 学习 72 下】【数据采集系统第四天】【移动/复制页分析】【使用串行化技术实现深度复制】
一.移动.复制页的逻辑实现 移动.复制页的功能是在设计调查页面的时候需要实现的功能.规则是如果在同一个调查中的话就是移动,如果是在不同调查中的就是复制. 无论是移动还是复制,都需要注意一个问题,那就是 ...
- 【Java EE 学习 71 下】【数据采集系统第三天】【分析答案实体】【删除问题】【删除页面】【删除调查】【清除调查】【打开/关闭调查】
一.分析答案实体 分析答案实体主要涉及到的还是设计上的问题,技术点几乎是没有的.首先需要确定一下答案的格式才能最终确定答案实体中需要有哪些属性. 答案格式的设计是十分重要的,现设计格式如下: 在表单中 ...
- 【Java EE 学习 67 下】【OA项目练习】【SSH整合JBPM工作流】【JBPM项目实战】
一.SSH整合JBPM JBPM基础见http://www.cnblogs.com/kuangdaoyizhimei/p/4981551.html 现在将要实现SSH和JBPM的整合. 1.添加jar ...
- 【Java EE 学习 69 上】【struts2】【paramsPrepareParamsStack拦截器栈解决model对象和属性赋值冲突问题】
昨天有同学问我问题,他告诉我他的Action中的一个属性明明提供了get/set方法,但是在方法中却获取不到表单中传递过来的值.代码如下(简化后的代码) public class UserAction ...
随机推荐
- 编译安装mmseg提示cannot find input file: src/Makefile.in错误
今天安装中文词检索功能模块 coreseek,其中一个分词模块 mmseg ,编译安装到最后,出现annot find input file: src/Makefile.in aclocal // ...
- eclipse 快捷键大全(转载)
Ctrl+1 快速修复(最经典的快捷键,就不用多说了)Ctrl+D: 删除当前行 Ctrl+Alt+↓ 复制当前行到下一行(复制增加)Ctrl+Alt+↑ 复制当前行到上一行(复制增加)Alt+↓ 当 ...
- 12月15日smarty模板基本语法
smarty基本语法: 1.注释:<{* this is a comment *}>,注意左右分隔符的写法,要和自己定义的一致. <{* I am a Smarty comment, ...
- 批量解密SQLSERVER数据库中的各种对象的工具dbForge SQL Decryptor
批量解密SQLSERVER数据库中的各种对象的工具dbForge SQL Decryptor2.1.11 之前写过一篇文章,使用redgate公司的SQL PROMPT工具,但是不太方便 SQLPRO ...
- [从产品角度学EXCEL 00]-为什么要关注EXCEL的本质
前言 Hello 大家好,我是尾巴,从今天开始,在这里连载<从产品角度学EXCEL>的系列文章.本文不接受无授权转载,如需转载,请先联系我,非常感谢. 与世面上的大部分EXCEL教程不同的 ...
- 我们为什么要看《超实用的Node.JS代码段》
不知道自己Node.JS水平如何?看这张图 如果一半以上的你都不会,必须看这本书,一线工程师用代码和功能页面来告诉你每一个技巧点. 都会一点,但不知道如何检验自己,看看本书提供的面试题: 1. ...
- Facebook的Web开发三板斧:React.js、Relay和GraphQL
2015-02-26 孙镜涛 InfoQ Eric Florenzano最近在自己的博客上发表了一篇题为<Facebook教我们如何构建网站>的文章,他认为软件开发有些时候需要比较大的跨 ...
- springmvc拦截器验证登录时间
在前一篇[Filter实现用户名验证]的随笔里,记录了如何使用filter 这次增加了拦截器实现 ①filter实现用户登陆时验证用户名是否为null ②interceptor实现用户登陆时时间判断, ...
- 元素堆叠问题、z-index、position
多次在项目中遇到html页面元素的非期待重叠错误,多数还是position定位情况下z-index的问题.其实每次解决类似问题思路大致都是一样的,说到底还是对z-index的理解比较模糊,可以解决问题 ...
- Distinct Subsequences
https://leetcode.com/problems/distinct-subsequences/ Given a string S and a string T, count the numb ...