9个交半价的人今天已经有一个人率先离开了,剩下的人也都要走么?暂时无从得知,年前必须交至少1900,才能继续留在这里,否则没有商量,大概商家都是这样吧……不过空位子很快就被其他在后面的人给占据了,毕竟走的这小子占据的位置太好,和我一样,第一排,靠着过道,嗯……确实是最好的位置:)
今天对面的机房被盗了,好多主机箱的cpu和内存条都被偷走了,大概损失4、5万吧,还来了好多条子拿来好多办案的箱子,里面这仪器那设备的,很多人一通的这儿找指纹那儿找指纹,折腾的真是沸沸扬扬啊……呵呵,搞不好是内贼干的呢,要不怎么不偷我们这间屋子呢,我们这里机器破嘛!
上午课程开始:
struts-config中还有一个data-source……这个数据源和服务器没有关系,是struts自己提供的
例如:<data-sources> <data-source> <set-property property="driverClass" value="sun.jdbc.odbc.JdbcOdbcDriver" /> <set-property property="url" value="jdbc:odbc:sqlserver" /> <set-property property="maxCount" value="10" /> <set-property property="minCount" value="4" /> <set-property property="user" value="sa" /> <set-property property="password" value="sa" /> </data-source> </data-sources>除了这些还有一个key属性,可以用来标识数据库连接池在没有设置key属性时,则以Globals.DATA_SOURCE_KEY为默认的标识
在数据库的struts库里面建立users表:id、username、password、age等字段//现在别忘了把mysql的jar包给弄进去<struts-config> <data-sources type="org.apache.commons.dbcp.BasicDataSource"> //这个type里面必须填写实现了DataSource的实现类, //要用到commons-dbcp commons-pool commons-collections三个包 <data-source>//这里可以指明key,也就是应用程序作用域的属性名。 <set-property property="driverClassName" value="com.mysql.jdbc.Driver"/> //注意这里必须要保证property要完全对应BasicDataSource的setter方法,action里面也有对应的set-property,其实就是根据同名 //的setter方法设置javaBean属性。 <set-property property="url" value="jdbc:mysql:///struts" /> <set-property property="username" value="root" /> <set-property property="password" value="root" /> </data-source> </data-sources></struts-config>
---------------------------------------------
index.jsp:<a href="register.jsp">register?</a>
register.jsp:
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<body> <h3><font color="red"><html:errors property="insertFail" /></font></h3> <html:form action="/register"> user name:<html:text property="username" /><html:errors property="username" /><br> password:<html:password property="password" /><br> age:<html:text property="age" /><br> <html:submit></html:submit> </html:form></body>
使用MyEclipse中的Web-Struts中的Struts1.2 Form来使用可视化功能创建FormBean:可以在底下添加属性
在这个formbean中的validate方法里面:
public ActionErrors validate(ActionMapping mapping,HttpServletRequest request){ //通过request到session,然后通过session得到应用程序作用域 ActionErrors errors = new ActionErrors() ; DataSource ds = (DataSource)request.getSession(). getServletContext(). getAttribute(Globals.DATA_SOURCE_KEY); //也可以利用DataSource ds = new Action().getDataSource(request)!! Connection conn = null ; PreparedStatement pstmt = null ; ResultSet rs = null ; rs = pstmt.executeQuery() ; if(rs.next()) { errors.add("username",new ActionMessage("用户名已经存在了!",false));//这就是不去查资源包了! } try { conn = ds.getConnection() ; pstmt = conn.prepareStatement("select * from users where username=?"); pstmt.setString(1,username); } catch(Exception ex) { ex.printStackTrace() ; } finally { if(conn!=null&&!conn.isClosed()) { conn.close() ; conn = null ; } }
return errors ;}
-----------------------------使用myEclipse新建一个Action
在Action里面指明与其相关联的formbeanName里面填写配置文件里面的formbean的name属性,底下的input Source是发生错误之后转回到哪个页面去Forwards里面就是什么情况下跳到哪个页面,其实就是在action里面配置forward元素
public ActionForward execute(ActionMapping mapping,ActionForm form,………………………………){ RegisterForm registerForm = (RegisterForm)form ; String target = "failure" ; Connection conn = null ; PreparedStatement pstmt = null ; try { DataSource ds = getDataSource(request); conn = ds.getConnection() ; pstmt= conn.prepareStatement("insert into users(username,password,age) values(?,?,?)") ; pstmt.setString(1,registerForm.getUsername()); pstmt.setString(2,registerForm.getPassword()); pstmt.setInt(3,registerForm.getAge());
if(pstmt.executeUpdate()!=1) { ActionErrors errors = new ActionErrors() ; errors.add("insertFail",new ActionMessage("插入异常!",false)) ; addErrors(request,errors) ; throw new SQLException() ; } else { target = "success" ; } } catch(Exception ex) { ex.printStackTrace() ; } finally { if(conn!=null&&!conn.isClosed()) { conn.close() ; conn = null ; } } return mapping.findAttribute(target);
}-----------------------------------------------上面的过程就是注册中的唯一性验证以及插入数据库的过程。
主要掌握Struts中数据源的配置(struts配置文件中配置,新版本中要指明type,type必须是实现了DataSource接口的实现类!)和加载的过程(Struts加载数据源的时候,会把它实例化出来后存到应用程序作用域中的Globals.DATASOURCE_KEY中)唯一性校验是放到formbean的validate方法里面去做的,也就是表单提交上去还没有给具体的Action的时候就去判断到底是不是在数据库中唯一!
注意每一个发生异常的地方,也就是被catch捕获的地方,都要在errors中添加一个错误!比如:catch(Exception ex){ errors.add("insertFail",new ActionMessage("发生了数据库异常!",false))}
=====================================================================================下午课程开始:Action中的execute方法抛出的异常可以是Exception
捕获的任务是ActionServlet负责的,它有一整套的异常处理机制。在每一个action里面都有个exception元素exception元素要放在forward元素的前面<exception type="java.sql.SQLException" path="/register.jsp" key="error.sql.exception" />//上面的key是资源包的键值,应该加到资源包里面。//path里面是发生异常的时候转到相应的页面,path并不是必须的,如果不指定的话会转到上级的Action里面所对应的//input属性里面。
首先看捕获道的异常是否和type一致,如果一致的话,就到资源包里面去找key,找到后存到errors里面,注意存的时候property就是那个key值!!也就是errors.add("error.sql.exception",new ActionMessage("……………………"))捕获异常错误的时候可以:<html:errors property="error.sql.exception">可以定位显示出异常的信息。再把errors存储到请求作用域里面去。
struts-config元素中还提供了global-exceptions和global-forwards元素,查找的时候如果在自己的action里面没有找到对应类型的异常的话就会去global-exceptions里面去找。
ActionErrors是用来处理逻辑上的错误的,而Struts中的Exception异常处理机制是专门对应程序发生的异常的。
==========================================================================================
如果没有唯一性校验的话,比方说发表文章,不可能对文章做唯一性校验的……但是可能由于用户的刷新问题导致多发了好几个回复,服务器端其实已经提交了,但是由于客户端可能没有看到响应所以又刷新或是重新又提交了一次,这就是著名的重复提交问题。
回避的方法是动态生成一个验证码图片,也就是做一个标识,用户第一次提交的时候服务器端存储好这个标识,用户提交的时候和服务器端的标识一样,既然比较后一样,那么服务器就立刻更换一个标识,这样就可以防止这种情况的发生。其实和验证码的逻辑是一样的。验证码之所以要用图片显示是因为软件可以从页面上读出数字,但是很难分析出图片里面的具体数字(尤其是图片做的很花哨的时候),也就是每一次显示提交的时候有且只有一个验证码图片或是一个标识。
动态验证码是为了防止黑客软件恶意注册。
抛开Struts,如果自己用方法来实现的话:除了提交表单信息还要提交标识符,进入这个表单之前必然有个隐藏表单域来存储这个事先生成的标识,这样点击提交就把标识提交到服务器中去了,然后服务器端存储在session中的这个标识就可以和用户提交过来的这个标识进行比较了,如果一致的话,那么服务器就可以执行数据库处理了,而且服务器同时要从session里面把那个原来的标识用随机生成法替换掉,这样一来,用户就不可能无意识的通过不小心的刷新来重复提交了。这样用户再想通过正式的表单页面再次提交信息的话,这个表单中的隐藏域同时也被服务器端的session用表达式语言给更新了。
如果不用Struts,而是手工做的话:(注意第一次进入这个页面的时候由于session里面的标识符没有准备好,所以不能直接进入这个表单页面!而是进入一个Action,在这个Action里面先把Session的标识符准备好,然后再到表单页面)<action path="/toRegister" type="cn.itcast.struts.action.ToRegisterAction" />
</action>public ActionForward execute(………………){ HttpSession session = request.getSession() ; session.setAttribute("token", new Float(Math.random())) ; return mapping.findForward("success");}
<input type="hidden" name="token" value="${sessionScope.token}" />
接着在表单处理Action的execute方法里面:Float token = new Float(request.getParameter("token")) ;//取出请求中的tokenFloat tokenInSession = (Float)request.getSession().getAttribute("token");//取出session中存储的tokenif(!token.equals(tokenInSession)){ errors.add("resubmit",new ActionMessage("重复提交",false)) ; addErrors(request,errors) ; request.getSession.setAttribute("token", new Float(Math.random())) ;//将Session中原来的标识符换掉! //其实标准的生成标识符的方法应该是通过时间和sessionID配合进行数学运算,把结果弄成一个十六进制数后是这个标识符。 return mapping.findForward(target) ;//这里转到一个错误页面去!而不要转回原来的表单处理页面,否则再次点击这个表单的话就提交上去了。 //这里记住,无论是插入成功还是失败,session里面的标识符都必须要换!所以finally中也要换,最好做到catch中也换一下 //以防万一,其实原则就是不管服务器处理表单数据以什么样的结果返回,session中的标识符都要进行改变!}request.getSession.setAttribute("token", new Float(Math.random())) ;//在if分支的外面马上改变session的标识
这样就防止了用户通过“后退”按钮回到表单页面或是通过“刷新”按钮再次重复提交的问题,你后退回去得到的是浏览器缓存里面已经生成的html代码,所以不涉及和服务器交互的问题,自然也不会涉及到从服务器那里拿session中存储的标识符问题,所以肯定还是原来的标识符,提交上去之后自然就和服务器上已经更新的标识符不一样了。
这里有一个问题需要注意:如果我通过后退按钮的话,回到的表单页面里面的隐藏表单域里面一定还是原来的标识符,因为是在缓存里面的,所以不会再从session里面拿隐藏表单域的token。这种机制是没有问题的,不论asp、jsp、还是.net框架都是这么来用的,这是无可争议的。
防止表单的重复是每一个网站涉及到提交表单的时候都必须要用到的===========================================================================Struts中是如何去做的呢?依靠的是generateToken,而且还有一点,如果session中存储了token,那么html标签里面会自动给你添加一个隐藏表单域的!
Globals类的TRANSACTION_TOKEN_KEY就是存储token的属性名。
也就是如果在服务器端Action的execute方法中设置好:session.setAttribute(Globals.TRANSACTION_TOKEN_KEY,generateToken(request));客户端的html标签就会自动生成个隐藏表单域。
