JBUILDER的光标定位不准确的最佳解决方案是:进入%JBUILDER_HOME%/bin目录下,用写字板编辑jbuilder.config,把下面的配置加进去: vmparam -Dprimetime.editor.useVariableWidthFont=true |
posted @
2005-12-06 11:02 rkind 阅读(231) |
评论 (0) |
编辑 收藏
Ant的简介:类似make工具,但可以支持多平台
Ant的安装:配置ant的准备工作:ant_home 指Ant的安装目录,在path中加入%ant_home%/bin,用于命令行下
运 行ant
Ant的结构:主要是通过对build.xml的配置,
Ant内置任务: 描述
property 设置name/value的属性
mkdir 创建目录
copy 拷贝
delete 删除
javac 编绎
war 打包
下面是一个简单build.xml的示例:
<project name="bookstore" default="about" basedir=".">
<target name="init">
<tstamp/>
<property name="build" value="build" />
<property name="src" value="src" />
<property environment="myenv" />
<property name="servletpath" value="${myenv.CATALINA_HOME}/common/lib/servlet-api.jar" />
<property name="mysqlpath" value="WEB-INF/lib/mysqldriver.jar" />
<mkdir dir="${build}" />
<mkdir dir="${build}\WEB-INF" />
<mkdir dir="${build}\WEB-INF\classes" />
<copy todir="${build}" >
<fileset dir="${basedir}" >
<include name="*.jsp" />
<include name="*.bmp" />
<include name="WEB-INF/**" />
<exclude name="build.xml" />
</fileset>
</copy>
</target>
<target name="compile" depends="init">
<javac srcdir="${src}"
destdir="${build}/WEB-INF/classes"
classpath="${servletpath}:${mysqlpath}">
</javac>
</target>
<target name="bookstorewar" depends="compile">
<war warfile="${build}/bookstore.war" webxml="${build}/WEB-INF/web.xml">
<lib dir="${build}/WEB-INF/lib"/>
<classes dir="${build}/WEB-INF/classes"/>
<fileset dir="${build}"/>
</war>
</target>
<target name="about" >
<echo>
This build.xml file contains targets
for building bookstore web application
</echo>
</target>
</project>
从示例我们看出来,整个xml是一个project,project下有几个为init,compile,的target
运行时首先在这个目录下打开dos窗口,以这个xml为准,如果你只运行ant那么只会输出echo中的内容
因为project的default是about; 如果运行ant complie 它会执行两个target: init 和complie,因为complie是依靠init
的。
用了以后发现ant 原来很简单,当然现在只是学了个皮毛而已。
posted @
2005-11-25 14:53 rkind 阅读(256) |
评论 (0) |
编辑 收藏
RMI,远程方法调用(Remote Method Invocation)是Enterprise JavaBeans的支柱,是建立分布式Java应用程序的方便途径。RMI是非常容易使用的,但是它非常的强大。
RMI的基础是接口,RMI构架基于一个重要的原理:定义接口和定义接口的具体实现是分开的。下面我们通过具体的例子,建立一个简单的远程计算服务和使用它的客户程序
一个正常工作的RMI系统由下面几个部分组成:
● 远程服务的接口定义
● 远程服务接口的具体实现
● 桩(Stub)和框架(Skeleton)文件
● 一个运行远程服务的服务器
● 一个RMI命名服务,它允许客户端去发现这个远程服务
● 类文件的提供者(一个HTTP或者FTP服务器)
● 一个需要这个远程服务的客户端程序
下面我们一步一步建立一个简单的RMI系统。首先在你的机器里建立一个新的文件夹,以便放置我们创建的文件,为了简单起见,我们只使用一个文件夹存放客户端和服务端代码,并且在同一个目录下运行服务端和客户端。
如果所有的RMI文件都已经设计好了,那么你需要下面的几个步骤去生成你的系统:
1、 编写并且编译接口的Java代码
2、 编写并且编译接口实现的Java代码
3、 从接口实现类中生成桩(Stub)和框架(Skeleton)类文件
4、 编写远程服务的主运行程序
5、 编写RMI的客户端程序
6、 安装并且运行RMI系统
1、 接口
第一步就是建立和编译服务接口的Java代码。这个接口定义了所有的提供远程服务的功能,下面是源程序:
//Calculator.java
//define the interface
import java.rmi.Remote;
public interface Calculator extends Remote
{
public long add(long a, long b)
throws java.rmi.RemoteException;
public long sub(long a, long b)
throws java.rmi.RemoteException;
public long mul(long a, long b)
throws java.rmi.RemoteException;
public long div(long a, long b)
throws java.rmi.RemoteException;
}
注意,这个接口继承自Remote,每一个定义的方法都必须抛出一个RemoteException异常对象。
建立这个文件,把它存放在刚才的目录下,并且编译。
>javac Calculator.java
2、 接口的具体实现
下一步,我们就要写远程服务的具体实现,这是一个CalculatorImpl类文件:
//CalculatorImpl.java
//Implementation
import java.rmi.server.UnicastRemoteObject
public class CalculatorImpl extends UnicastRemoteObject implements Calculator
{
// 这个实现必须有一个显式的构造函数,并且要抛出一个RemoteException异常
public CalculatorImpl()
throws java.rmi.RemoteException {
super();
}
public long add(long a, long b)
throws java.rmi.RemoteException {
return a + b;
}
public long sub(long a, long b)
throws java.rmi.RemoteException {
return a - b;
}
public long mul(long a, long b)
throws java.rmi.RemoteException {
return a * b;
}
public long div(long a, long b)
throws java.rmi.RemoteException {
return a / b;
}
}
同样的,把这个文件保存在你的目录里然后编译他。
这个实现类使用了UnicastRemoteObject去联接RMI系统。在我们的例子中,我们是直接的从UnicastRemoteObject这个类上继承的,事实上并不一定要这样做,如果一个类不是从UnicastRmeoteObject上继承,那必须使用它的exportObject()方法去联接到RMI。
如果一个类继承自UnicastRemoteObject,那么它必须提供一个构造函数并且声明抛出一个RemoteException对象。当这个构造函数调用了super(),它久激活UnicastRemoteObject中的代码完成RMI的连接和远程对象的初始化。
3、 桩(Stubs)和框架(Skeletons)
下一步就是要使用RMI编译器rmic来生成桩和框架文件,这个编译运行在远程服务实现类文件上。
>rmic CalculatorImpl
在你的目录下运行上面的命令,成功执行完上面的命令你可以发现一个Calculator_stub.class文件,如果你是使用的Java2SDK,那么你还可以发现Calculator_Skel.class文件。
4、 主机服务器
远程RMI服务必须是在一个服务器中运行的。CalculatorServer类是一个非常简单的服务器。
//CalculatorServer.java
import java.rmi.Naming;
public class CalculatorServer {
public CalculatorServer() {
try {
Calculator c = new CalculatorImpl();
Naming.rebind("rmi://localhost:1099/CalculatorService", c);
} catch (Exception e) {
System.out.println("Trouble: " + e);
}
}
public static void main(String args[]) {
new CalculatorServer();
}
}
建立这个服务器程序,然后保存到你的目录下,并且编译它。
5、 客户端
客户端源代码如下:
//CalculatorClient.java
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.net.MalformedURLException;
import java.rmi.NotBoundException;
public class CalculatorClient {
public static void main(String[] args) {
try {
Calculator c = (Calculator)
Naming.lookup(
"rmi://localhost
/CalculatorService");
System.out.println( c.sub(4, 3) );
System.out.println( c.add(4, 5) );
System.out.println( c.mul(3, 6) );
System.out.println( c.div(9, 3) );
}
catch (MalformedURLException murle) {
System.out.println();
System.out.println(
"MalformedURLException");
System.out.println(murle);
}
catch (RemoteException re) {
System.out.println();
System.out.println(
"RemoteException");
System.out.println(re);
}
catch (NotBoundException nbe) {
System.out.println();
System.out.println(
"NotBoundException");
System.out.println(nbe);
}
catch (
java.lang.ArithmeticException
ae) {
System.out.println();
System.out.println(
"java.lang.ArithmeticException");
System.out.println(ae);
}
}
}
保存这个客户端程序到你的目录下(注意这个目录是一开始建立那个,所有的我们的文件都在那个目录下),并且编译他。
6、 运行RMI系统
现在我们建立了所有运行这个简单RMI系统所需的文件,现在我们终于可以运行这个RMI系统啦!来享受吧。
我们是在命令控制台下运行这个系统的,你必须开启三个控制台窗口,一个运行服务器,一个运行客户端,还有一个运行RMIRegistry。
首先运行注册程序RMIRegistry,你必须在包含你刚写的类的那么目录下运行这个注册程序。
>rmiregistry
好,这个命令成功的话,注册程序已经开始运行了,不要管他,现在切换到另外一个控制台,在第二个控制台里,我们运行服务器CalculatorService,因为RMI的安全机制将在服务端发生作用,所以你必须增加一条安全策略。以下是对应安全策略的例子
grant {
permission java.security.AllPermission "", "";
};
注意:这是一条最简单的安全策略,它允许任何人做任何事,对于你的更加关键性的应用,你必须指定更加详细安全策略。
现在为了运行服务端,你需要除客户类(CalculatorClient.class)之外的所有的类文件。确认安全策略在policy.txt文件之后,使用如下命令来运行服务器。
> java -Djava.security.policy=policy.txt CalculatorServer
这个服务器就开始工作了,把接口的实现加载到内存等待客户端的联接。好现在切换到第三个控制台,启动我们的客户端。
为了在其他的机器运行客户端程序你需要一个远程接口(Calculator.class) 和一个stub(CalculatorImpl_Stub.class)。 使用如下命令运行客户端
prompt> java -Djava.security.policy=policy.txt CalculatorClient
如果所有的这些都成功运行,你应该看到下面的输出:
1
9
18
3
如果你看到了上面的输出,恭喜你,你成功了,你已经成功的创建了一个RMI系统,并且使他正确工作了。即使你运行在同一个计算机上,RMI还是使用了你的网络堆栈和TCP/IP去进行通讯,并且是运行在三个不同的Java虚拟机上。这已经是一个完整的RMI系统。
posted @
2005-11-15 18:46 rkind 阅读(229) |
评论 (0) |
编辑 收藏
在一个有密码保护的Web应用中,正确处理用户退出过程并不仅仅只需调用HttpSession的invalidate()方法。现在大部分浏览器上都有后退和前进按钮,允许用户后退或前进到一个页面。如果在用户在退出一个Web应用后按了后退按钮浏览器把缓存中的页面呈现给用户,这会使用户产生疑惑,他们会开始担心他们的个人数据是否安全。许多Web应用强迫用户退出时关闭整个浏览器,这样,用户就无法点击后退按钮了。还有一些使用javascript,但在某些客户端浏览器这却不一定起作用。这些解决方案都很笨拙且不能保证在任一情况下100%有效,同时,它也要求用户有一定的操作经验。
这篇文章以示例阐述了正确解决用户退出问题的方案。作者Kevin Le首先描述了一个密码保护Web应用,然后以示例程序解释问题如何产生并讨论解决问题的方案。文章虽然是针对JSP页面进行阐述,但作者所阐述的概念很容易理解切能够为其他Web技术所采用。最后作者展示了如何用Jakarta Struts优雅地解决这一问题。
大部分Web应用不会包含象银行账户或信用卡资料那样机密的信息,但一旦涉及到敏感数据,我们就需要提供一类密码保护机制。举例来说,一个工厂中工人通过Web访问他们的时间安排、进入他们的训练课程以及查看他们的薪金等等。此时应用SSL(Secure Socket Layer)有点杀鸡用牛刀的感觉,但不可否认,我们又必须为这些应用提供密码保护,否则,工人(也就是Web应用的使用者)可以窥探到工厂中其他雇员的私人机密信息。
与上述情形相似的还有位处图书馆、医院等公共场所的计算机。在这些地方,许多用户共同使用几台计算机,此时保护用户的个人数据就显得至关重要。设计良好编写优秀的应用对用户专业知识的要求少之又少。
我们来看一下现实世界中一个完美的Web应用是如何表现的:一个用户通过浏览器访问一个页面。Web应用展现一个登陆页面要求用户输入有效的验证信息。用户输入了用户名和密码。此时我们假设用户提供的身份验证信息是正确的,经过了验证过程,Web应用允许用户浏览他有权访问的区域。用户想退出时,点击退出按钮,Web应用要求用户确认他是否则真的需要退出,如果用户确定退出,Session结束,Web应用重新定位到登陆页面。用户可以放心的离开而不用担心他的信息会泄露。另一个用户坐到了同一台电脑前,他点击后退按钮,Web应用不应该出现上一个用户访问过的任何一个页面。事实上,Web应用在第二个用户提供正确的验证信息之前应当一直停留在登陆页面上。
通过示例程序,文章向您阐述了如何在一个Web应用中实现这一功能。
JSP示例
为了更为有效地阐述实现方案,本文将从展示一个示例应用logoutSampleJSP1中碰到的问题开始。这个示例代表了许多没有正确解决退出过程的Web应用。logoutSampleJSP1包含了下述jsp页面:login.jsp, home.jsp, secure1.jsp, secure2.jsp, logout.jsp, loginAction.jsp, and logoutAction.jsp。其中页面home.jsp, secure1.jsp, secure2.jsp, 和logout.jsp是不允许未经认证的用户访问的,也就是说,这些页面包含了重要信息,在用户登陆之前或者退出之后都不应该出现在浏览器中。login.jsp包含了用于用户输入用户名和密码的form。logout.jsp页包含了要求用户确认是否退出的form。loginAction.jsp和logoutAction.jsp作为控制器分别包含了登陆和退出代码。
第二个示例应用logoutSampleJSP2展示了如何解决示例logoutSampleJSP1中的问题。然而,第二个应用自身也是有疑问的。在特定的情况下,退出问题还是会出现。
第三个示例应用logoutSampleJSP3在第二个示例上进行了改进,比较完善地解决了退出问题。
最后一个示例logoutSampleStruts展示了Struts如何优美地解决登陆问题。
注意:本文所附示例在最新版本的Microsoft Internet Explorer (IE), Netscape Navigator, Mozilla, FireFox和Avant浏览器上测试通过。
Login action
Brian Pontarelli的经典文章《J2EE Security: Container Versus Custom》讨论了不同的J2EE认证途径。文章同时指出,HTTP协议和基于form的认证并未提供处理用户退出的机制。因此,解决途径便是引入自定义的安全实现机制。
自定义的安全认证机制普遍采用的方法是从form中获得用户输入的认证信息,然后到诸如LDAP (lightweight directory access protocol)或关系数据库的安全域中进行认证。如果用户提供的认证信息是有效的,登陆动作往HttpSession对象中注入某个对象。HttpSession存在着注入的对象则表示用户已经登陆。为了方便读者理解,本文所附的示例只往HttpSession中写入一个用户名以表明用户已经登陆。清单1是从loginAction.jsp页面中节选的一段代码以此阐述登陆动作:
Listing 1 //... //initialize RequestDispatcher object; set forward to home page by default RequestDispatcher rd = request.getRequestDispatcher("home.jsp");
//Prepare connection and statement rs = stmt.executeQuery("select password from USER where userName = '" + userName + "'"); if (rs.next()) { //Query only returns 1 record in the result set; only 1 password per userName which is also the primary key if (rs.getString("password").equals(password)) { //If valid password session.setAttribute("User", userName); //Saves username string in the session object } else { //Password does not match, i.e., invalid user password request.setAttribute("Error", "Invalid password.");
rd = request.getRequestDispatcher("login.jsp"); } } //No record in the result set, i.e., invalid username else {
request.setAttribute("Error", "Invalid user name."); rd = request.getRequestDispatcher("login.jsp"); } }
//As a controller, loginAction.jsp finally either forwards to "login.jsp" or "home.jsp" rd.forward(request, response); //... |
本文所附示例均以关系型数据库作为安全域,但本文所阐述的观点对任何类型的安全域都是适用的。
Logout action
退出动作就包含了简单的删除用户名以及对用户的HttpSession对象调用invalidate()方法。清单2是从loginoutAction.jsp页面中节选的一段代码以此阐述退出动作:
Listing 2 //... session.removeAttribute("User"); session.invalidate(); //... |
阻止未经认证访问受保护的JSP页面
从form中获取用户提交的认证信息并经过验证后,登陆动作简单地往 HttpSession对象中写入一个用户名,退出动作则做相反的工作,它从用户的HttpSession对象中删除用户名并调用invalidate()方法销毁HttpSession。为了使登陆和退出动作真正发挥作用,所有受保护的JSP页面都应该首先验证HttpSession中是否包含了用户名以确认当前用户是否已经登陆。如果HttpSession中包含了用户名,也就是说用户已经登陆,Web应用则将剩余的JSP页发送给浏览器,否则,JSP页将跳转到登陆页login.jsp。页面home.jsp, secure1.jsp, secure2.jsp和logout.jsp均包含清单3中的代码段:
Listing 3 //... String userName = (String) session.getAttribute("User"); if (null == userName) { request.setAttribute("Error", "Session has ended. Please login."); RequestDispatcher rd = request.getRequestDispatcher("login.jsp"); rd.forward(request, response); } //... //Allow the rest of the dynamic content in this JSP to be served to the browser //... |
在这个代码段中,程序从HttpSession中减缩username字符串。如果字符串为空,Web应用则自动中止执行当前页面并跳转到登陆页,同时给出Session has ended. Please log in.的提示;如果不为空,Web应用则继续执行,也就是把剩余的页面提供给用户。
运行logoutSampleJSP1
运行logoutSampleJSP1将会出现如下几种情形:
1) 如果用户没有登陆,Web应用将会正确中止受保护页面home.jsp, secure1.jsp, secure2.jsp和logout.jsp的执行,也就是说,假如用户在浏览器地址栏中直接敲入受保护JSP页的地址试图访问,Web应用将自动跳转到登陆页并提示Session has ended.Please log in.
2) 同样的,当一个用户已经退出,Web应用也会正确中止受保护页面home.jsp, secure1.jsp, secure2.jsp和logout.jsp的执行
3) 用户退出后,如果点击浏览器上的后退按钮,Web应用将不能正确保护受保护的页面——在Session销毁后(用户退出)受保护的JSP页重新在浏览器中显示出来。然而,如果用户点击返回页面上的任何链接,Web应用将会跳转到登陆页面并提示Session has ended.Please log in.
阻止浏览器缓存 上述问题的根源在于大部分浏览器都有一个后退按钮。当点击后退按钮时,默认情况下浏览器不是从Web服务器上重新获取页面,而是从浏览器缓存中载入页面。基于Java的Web应用并未限制这一功能,在基于PHP、ASP和.NET的Web应用中也同样存在这一问题。
在用户点击后退按钮后,浏览器到服务器再从服务器到浏览器这样通常意思上的HTTP回路并没有建立,仅仅只是用户,浏览器和缓存进行了交互。所以,即使包含了清单3上的代码来保护JSP页面,当点击后退按钮时,这些代码是不会执行的。
缓存的好坏,真是仁者见仁智者见智。缓存的确提供了一些便利,但通常只在使用静态的HTML页面或基于图形或影响的页面你才能感受到。而另一方面,Web应用通常是基于数据的,数据通常是频繁更改的。与从缓存中读取并显示过期的数据相比,提供最新的数据才是更重要的!
幸运的是,HTTP头信息“Expires”和“Cache-Control”为应用程序服务器提供了一个控制浏览器和代理服务器上缓存的机制。HTTP头信息Expires告诉代理服务器它的缓存页面何时将过期。HTTP1.1规范中新定义的头信息Cache-Control可以通知浏览器不缓存任何页面。当点击后退按钮时,浏览器重新访问服务器已获取页面。如下是使用Cache-Control的基本方法:
1) no-cache:强制缓存从服务器上获取新的页面
2) no-store: 在任何环境下缓存不保存任何页面
HTTP1.0规范中的Pragma:no-cache等同于HTTP1.1规范中的Cache-Control:no-cache,同样可以包含在头信息中。
通过使用HTTP头信息的cache控制,第二个示例应用logoutSampleJSP2解决了logoutSampleJSP1的问题。logoutSampleJSP2与logoutSampleJSP1不同表现在如下代码段中,这一代码段加入进所有受保护的页面中:
//... response.setHeader("Cache-Control","no-cache"); //Forces caches to obtain a new copy of the page from the origin server response.setHeader("Cache-Control","no-store"); //Directs caches not to store the page under any circumstance response.setDateHeader("Expires", 0); //Causes the proxy cache to see the page as "stale" response.setHeader("Pragma","no-cache"); //HTTP 1.0 backward compatibility String userName = (String) session.getAttribute("User"); if (null == userName) { request.setAttribute("Error", "Session has ended. Please login."); RequestDispatcher rd = request.getRequestDispatcher("login.jsp"); rd.forward(request, response); } //... |
通过设置头信息和检查HttpSession中的用户名确保了浏览器不缓存页面,同时,如果用户未登陆,受保护的JSP页面将不会发送到浏览器,取而代之的将是登陆页面login.jsp。
运行logoutSampleJSP2 运行logoutSampleJSP2后将回看到如下结果:
1) 当用户退出后试图点击后退按钮,浏览器并不会显示受保护的页面,它只会现实登陆页login.jsp同时给出提示信息Session has ended. Please log in.
2) 然而,当按了后退按钮返回的页是处理用户提交数据的页面时,IE和Avant浏览器将弹出如下信息提示:
警告:页面已过期……(你肯定见过)
选择刷新后前一个JSP页面将重新显示在浏览器中。很显然,这不是我们所想看到的因为它违背了logout动作的目的。发生这一现象时,很可能是一个恶意用户在尝试获取其他用户的数据。然而,这个问题仅仅出现在后退按钮对应的是一个处理POST请求的页面。
记录最后登陆时间 上述问题之所以出现是因为浏览器将其缓存中的数据重新提交了。这本文的例子中,数据包含了用户名和密码。无论是否给出安全警告信息,浏览器此时起到了负面作用。
为了解决logoutSampleJSP2中出现的问题,logoutSampleJSP3的login.jsp在包含username和password的基础上还包含了一个称作lastLogon的隐藏表单域,此表单域动态的用一个long型值初始化。这个long型值是调用System.currentTimeMillis()获取到的自1970年1月1日以来的毫秒数。当login.jsp中的form提交时,loginAction.jsp首先将隐藏域中的值与用户数据库中的值进行比较。只有当lastLogon表单域中的值大于数据库中的值时Web应用才认为这是个有效的登陆。
为了验证登陆,数据库中lastLogon字段必须以表单中的lastLogon值进行更新。上例中,当浏览器重复提交数据时,表单中的lastLogon值不比数据库中的lastLogon值大,因此,loginAction转到login.jsp页面,并提示Session has ended.Please log in.清单5是loginAction中节选的代码段:
清单5
//... RequestDispatcher rd = request.getRequestDispatcher("home.jsp"); //Forward to homepage by default //... if (rs.getString("password").equals(password)) { //If valid password long lastLogonDB = rs.getLong("lastLogon"); if (lastLogonForm > lastLogonDB) { session.setAttribute("User", userName); //Saves username string in the session object stmt.executeUpdate("update USER set lastLogon= " + lastLogonForm + " where userName = '" + userName + "'"); } else { request.setAttribute("Error", "Session has ended. Please login."); rd = request.getRequestDispatcher("login.jsp"); } } else { //Password does not match, i.e., invalid user password request.setAttribute("Error", "Invalid password."); rd = request.getRequestDispatcher("login.jsp"); } //... rd.forward(request, response); //... |
为了实现上述方法,你必须记录每个用户的最后登陆时间。对于采用关系型数据库安全域来说,这点可以可以通过在某个表中加上lastLogin字段轻松实现。LDAP以及其他的安全域需要稍微动下脑筋,但很显然是可以实现的。
表示最后登陆时间的方法有很多。示例logoutSampleJSP3利用了自1970年1月1日以来的毫秒数。这个方法在许多人在不同浏览器中用一个用户账号登陆时也是可行的。
运行logoutSampleJSP3 运行示例logoutSampleJSP3将展示如何正确处理退出问题。一旦用户退出,点击浏览器上的后退按钮在任何情况下都不会是受保护的页面在浏览器上显示出来。这个示例展示了如何正确处理退出问题而不需要额外的培训。
为了使代码更简练有效,一些冗余的代码可以剔除掉。一种途径就是把清单4中的代码写到一个单独的JSP页中,通过标签<jsp:include>其他页面也可以引用。
Struts框架下的退出实现 与直接使用JSP或JSP/servlets相比,另一个可选的方案是使用Struts。为一个基于Struts的Web应用添加一个处理退出问题的框架可以优雅地不费气力的实现。这部分归功于Struts是采用MVC设计模式的因此将模型和视图清晰的分开。另外,Java是一个面向对象的语言,其支持继承,可以比JSP中的脚本更为容易地实现代码重用。在Struts中,清单4中的代码可以从JSP页面中移植到Action类的execute()方法中。
此外,我们还可以定义一个继承Struts Action类的基本类,其execute()方法中包含了清单4中的代码。通过使用类继承机制,其他类可以继承基本类中的通用逻辑来设置HTTP头信息以及检索HttpSession对象中的username字符串。这个基本类是一个抽象类并定义了一个抽象方法executeAction()。所有继承自基类的子类都应实现exectuteAction()方法而不是覆盖它。清单6是基类的部分代码:
清单6
public abstract class BaseAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.setHeader("Cache-Control","no-cache"); //Forces caches to obtain a new copy of the page from the origin server response.setHeader("Cache-Control","no-store"); //Directs caches not to store the page under any circumstance response.setDateHeader("Expires", 0); //Causes the proxy cache to see the page as "stale" response.setHeader("Pragma","no-cache"); //HTTP 1.0 backward compatibility
if (!this.userIsLoggedIn(request)) { ActionErrors errors = new ActionErrors(); errors.add("error", new ActionError("logon.sessionEnded")); this.saveErrors(request, errors); return mapping.findForward("sessionEnded"); } return executeAction(mapping, form, request, response); }
protected abstract ActionForward executeAction(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException;
private boolean userIsLoggedIn(HttpServletRequest request) { if (request.getSession().getAttribute("User") == null) { return false; }
return true; } } |
清单6中的代码与清单4中的很相像,仅仅只是用ActionMapping findForward替代了RequestDispatcher forward。清单6中,如果在HttpSession中未找到username字符串,ActionMapping对象将找到名为sessionEnded的forward元素并跳转到对应的path。如果找到了,子类将执行其实现了executeAction()方法的业务逻辑。因此,在配置文件struts-web.xml中为所有子类声明个一名为sessionEnded的forward元素是必须的。清单7以secure1 action阐明了这样一个声明:
清单7
<action path="/secure1" type="com.kevinhle.logoutSampleStruts.Secure1Action" scope="request"> <forward name="success" path="/WEB-INF/jsps/secure1.jsp"/> <forward name="sessionEnded" path="/login.jsp"/> </action> |
继承自BaseAction类的子类Secure1Action实现了executeAction()方法而不是覆盖它。Secure1Action类不执行任何退出代码,如清单8:
public class Secure1Action extends BaseAction { public ActionForward executeAction(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
HttpSession session = request.getSession(); return (mapping.findForward("success")); } } |
只需要定义一个基类而不需要额外的代码工作,上述解决方案是优雅而有效的。不管怎样,将通用的行为方法写成一个继承StrutsAction的基类是许多Struts项目的共同经验,值得推荐。
结论 本文阐述了解决退出问题的方案,尽管方案简单的令人惊讶,但却在所有情况下都能有效地工作。无论是对JSP还是Struts,所要做的不过是写一段不超过50行的代码以及一个记录用户最后登陆时间的方法。在Web应用中混合使用这些方案能够使拥护的私人数据不致泄露,同时,也能增加用户的经验。
posted @
2005-11-14 16:48 rkind 阅读(219) |
评论 (0) |
编辑 收藏
Beanutils用了魔术般的反射技术,实现了很多夸张有用的功能,都是C/C++时代不敢想的。无论谁的项目,始终一天都会用得上它。我算是后知后觉了,第一回看到它的时候居然错过。
1.属性的动态getter,setter
在这框架满天飞的年代,不能事事都保证执行getter,setter函数了,有时候属性是要需要根据名字动态的取得的,就像这样:
BeanUtils.getProperty(myBean,"code");
而BeanUtils更强的功能是直接访问内嵌对象的属性,只要使用逗号分割。
BeanUtils.getProperty(orderBean, "address.city");
其他类库的BeanUtils通常都很简单,不能访问内嵌的对象,所以经常要用Jakata的BeanUtils替换它们。
BeanUtils还支持List和Map类型的属性。如下面的语法即可取得顾客列表中第一个顾客的名字
BeanUtils.getProperty(orderBean, "customers[1].name");
其中BeanUtils会使用ConvertUtils类把字符串转为Bean属性的真正类型,方便从HttpServletRequest等对象中提取bean,或者把bean输出到页面。
而PropertyUtils就会原色的保留Bean原来的类型。
2.beanCompartor 动态排序
还是通过反射,动态设定Bean按照哪个属性来排序,而不再需要在bean的Compare接口进行复杂的条件判断。 List peoples = ...; // Person对象的列表 Collections.sort(peoples, new BeanComparator("age")); }}} 如果要支持多个属性的复合排序,如"Order By lastName,firstName" ArrayList sortFields = new ArrayList();
sortFields.add(new BeanComparator("lastName"));
sortFields.add(new BeanComparator("firstName"));
ComparatorChain multiSort = new ComparatorChain(sortFields);
Collections.sort(rows,multiSort);
其中ComparatorChain属于jakata commons-collections包。
如果age属性不是普通类型,构造函数需要再传入一个comparator对象为age变量排序。
3.Converter 把Request中的字符串转为实际类型对象
经常要从request,resultSet等对象取出值来赋入bean中,下面的代码谁都写腻了,如果不用MVC框架的绑定功能的话。
String a = request.getParameter("a");
bean.setA(a);
String b = ....
不妨改为
MyBean bean = ...;
HashMap map = new HashMap();
Enumeration names = request.getParameterNames();
while (names.hasMoreElements())
{
String name = (String) names.nextElement();
map.put(name, request.getParameterValues(name));
}
BeanUtils.populate(bean, map);
其中BeanUtils的populate方法或者getProperty,setProperty方法其实都会调用convert进行转换。
但Converter只支持一些基本的类型,甚至连java.util.Date类型也不支持。而且它比较笨的一个地方是当遇到不认识的类型时,居然会抛出异常来。
对于Date类型,我参考它的sqldate类型实现了一个Converter,而且添加了一个设置日期格式的函数。要把这个Converter注册,需要如下语句:
ConvertUtilsBean convertUtils = new ConvertUtilsBean();
DateConverter dateConverter = new DateConverter();
convertUtils.register(dateConverter,Date.class);
//因为要注册converter,所以不能再使用BeanUtils的静态方法了,必须创建BeanUtilsBean实例
BeanUtilsBean beanUtils = new BeanUtilsBean(convertUtils,new PropertyUtilsBean());
beanUtils.setProperty(bean, name, value);
!4 其他功能
4.1 PropertyUtils,当属性为Collection,Map时的动态读取:
Collection: 提供index
{{{ BeanUtils.getIndexedProperty(orderBean,"items",1);
或者
BeanUtils.getIndexedProperty(orderBean,"items[1]");
Map: 提供Key Value
BeanUtils.getMappedProperty(orderBean, "items","111");//key-value goods_no=111
或者
BeanUtils.getMappedProperty(orderBean, "items(111)");
4.2 PropertyUtils,获取属性的Class类型
public static Class getPropertyType(Object bean, String name)
4.3 ConstructorUtils,动态创建对象
public static Object invokeConstructor(Class klass, Object arg)
4.4 DynaClass动态类
其中ComparatorChain属于jakata commons-collections包。 经常要从request,resultSet等对象取出值来赋入bean中,下面的代码谁都写腻了,如果不用MVC框架的绑定功能的话。 不妨改为 其中BeanUtils的populate方法或者getProperty,setProperty方法其实都会调用convert进行转换。 或者 4.2 PropertyUtils,获取属性的Class类型
posted @
2005-11-14 15:12 rkind 阅读(146) |
评论 (0) |
编辑 收藏
Struts的Token(令牌)机制能够很好的解决表单重复提交的问题,基本原理是:服务器端在处理到达的请求之前,会将请求中包含的令牌值与保存在当前用户会话中的令牌值进行比较,看是否匹配。在处理完该请求后,且在答复发送给客户端之前,将会产生一个新的令牌,该令牌除传给客户端以外,也会将用户会话中保存的旧的令牌进行替换。这样如果用户回退到刚才的提交页面并再次提交的话,客户端传过来的令牌就和服务器端的令牌不一致,从而有效地防止了重复提交的发生。
这时其实也就是两点,第一:你需要在请求中有这个令牌值,请求中的令牌值如何保存,其实就和我们平时在页面中保存一些信息是一样的,通过隐藏字段来保存,保存的形式如: 〈input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="6aa35341f25184fd996c4c918255c3ae"〉,这个value是TokenProcessor类中的generateToken()获得的,是根据当前用户的session id和当前时间的long值来计算的。第二:在客户端提交后,我们要根据判断在请求中包含的值是否和服务器的令牌一致,因为服务器每次提交都会生成新的Token,所以,如果是重复提交,客户端的Token值和服务器端的Token值就会不一致。下面就以在数据库中插入一条数据来说明如何防止重复提交。
在Action中的add方法中,我们需要将Token值明确的要求保存在页面中,只需增加一条语句:saveToken(request);,如下所示:
public ActionForward add(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
//前面的处理省略
saveToken(request);
return mapping.findForward("add");
}在Action的insert方法中,我们根据表单中的Token值与服务器端的Token值比较,如下所示:
public ActionForward insert(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
if (isTokenValid(request, true)) {
// 表单不是重复提交
//这里是保存数据的代码
} else {
//表单重复提交
saveToken(request);
//其它的处理代码
}
}
其实使用起来很简单,举个最简单、最需要使用这个的例子:
一般控制重复提交主要是用在对数据库操作的控制上,比如插入、更新、删除等,由于更新、删除一般都是通过id来操作(例如:updateXXXById, removeXXXById),所以这类操作控制的意义不是很大(不排除个别现象),重复提交的控制也就主要是在插入时的控制了。
先说一下,我们目前所做项目的情况:
目前的项目是用Struts+Spring+Ibatis,页面用jstl,Struts复杂View层,Spring在Service层提供事务控制,Ibatis是用来代替JDBC,所有页面的访问都不是直接访问jsp,而是访问Structs的Action,再由Action来Forward到一个Jsp,所有针对数据库的操作,比如取数据或修改数据,都是在Action里面完成,所有的Action一般都继承BaseDispatchAction,这个是自己建立的类,目的是为所有的Action做一些统一的控制,在Struts层,对于一个功能,我们一般分为两个Action,一个Action里的功能是不需要调用Struts的验证功能的(常见的方法名称有add,edit,remove,view,list),另一个是需要调用Struts的验证功能的(常见的方法名称有insert,update)。
就拿论坛发贴来说吧,论坛发贴首先需要跳转到一个页面,你可以填写帖子的主题和内容,填写完后,单击“提交”,贴子就发表了,所以这里经过两个步骤:
1、转到一个新增的页面,在Action里我们一般称为add,例如:
public ActionForward add(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
//这一句是输出调试信息,表示代码执行到这一段了
log.debug(":: action - subject add");
//your code here
//这里保存Token值
saveToken(request);
//跳转到add页面,在Structs-config.xml里面定义,例如,跳转到subjectAdd.jsp
return mapping.findForward("add");
}
2、在填写标题和内容后,选择 提交 ,会提交到insert方法,在insert方法里判断,是否重复提交了。
public ActionForward insert(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response){
if (isTokenValid(request, true)) {
// 表单不是重复提交
//这里是保存数据的代码
} else {
//表单重复提交
saveToken(request);
//其它的处理代码
}
}
下面更详细一点(注意,下面所有的代码使用全角括号):
1、你想发贴时,点击“我要发贴”链接的代码可以里这样的:
〈html:link action="subject.do?method=add"〉我要发贴〈/html:link〉
subject.do 和 method 这些在struct-config.xml如何定义我就不说了,点击链接后,会执行subject.do的add方法,代码如上面说的,跳转到subjectAdd.jsp页面。页面的代码大概如下:
〈html:form action="subjectForm.do?method=insert"〉
〈html:text property="title" /〉
〈html:textarea property="content" /〉
〈html:submit property="发表" /〉
〈html:reset property="重填" /〉
〈html:form〉
如果你在add方法里加了“saveToken(request);”这一句,那在subjectAdd.jsp生成的页面上,会多一个隐藏字段,类似于这样〈input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="6aa35341f25184fd996c4c918255c3ae"〉,
2、点击发表后,表单提交到subjectForm.do里的insert方法后,你在insert方法里要将表单的数据插入到数据库中,如果没有进行重复提交的控制,那么每点击一次浏览器的刷新按钮,都会在数据库中插入一条相同的记录,增加下面的代码,你就可以控制用户的重复提交了。
if (isTokenValid(request, true)) {
// 表单不是重复提交
//这里是保存数据的代码
} else {
//表单重复提交
saveToken(request);
//其它的处理代码
}
注意,你必须在add方法里使用了saveToken(request),你才能在insert里判断,否则,你每次保存操作都是重复提交。
记住一点,Struts在你每次访问Action的时候,都会产生一个令牌,保存在你的Session里面,如果你在Action里的函数里面,使用了saveToken(request);,那么这个令牌也会保存在这个Action所Forward到的jsp所生成的静态页面里。
如果你在你Action的方法里使用了isTokenValid,那么Struts会将你从你的request里面去获取这个令牌值,然后和Session里的令牌值做比较,如果两者相等,就不是重复提交,如果不相等,就是重复提交了。
由于我们项目的所有Action都是继承自BaseDispatchAction这个类,所以我们基本上都是在这个类里面做了表单重复提交的控制,默认是控制add方法和insert方法,如果需要控制其它的方法,就自己手动写上面这些代码,否则是不需要手写的,控制的代码如下:
public abstract class BaseDispatchAction extends BaseAction {
protected ActionForward perform(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
String parameter = mapping.getParameter();
String name = request.getParameter(parameter);
if (null == name) { //如果没有指定 method ,则默认为 list
name = "list";
}
if ("add".equals(name)) {
if ("add".equals(name)) {
saveToken(request);
}
} else if ("insert".equals(name)) {
if (!isTokenValid(request, true)) {
resetToken(request);
saveError(request, new ActionMessage("error.repeatSubmit"));
log.error("重复提交!");
return mapping.findForward("error");
}
}
return dispatchMethod2(mapping, form, request, response, name);
}
}
posted @
2005-11-14 14:32 rkind 阅读(314) |
评论 (0) |
编辑 收藏
SELECT COUNT(*) AS Expr1 FROM History
DELETE FROM History WHERE (Id > 0)
truncate table youtable;
为什么要用TRUNCATE TABLE 语句代替DELETE语句?当你使用TRUNCATE TABLE语句时,记录的删除是不作记录的。也就是说,这意味着TRUNCATE TABLE 要比DELETE快得多。
posted @
2005-11-11 11:25 rkind 阅读(291) |
评论 (0) |
编辑 收藏
我今年39岁了,25岁研究生毕业,工作14年,回头看看,应该说走了不少的弯路,有一些经验和教训。现在开一个小公司,赚的钱刚够养家糊口的。看看这些刚毕业的学生,对前景也很迷茫,想抛砖引玉,谈谈自己的看法,局限于理工科的学生,我对文科的不懂,身边的朋友也没有这一类型的。
91年研究生毕业,那时出路就是1种:留在北京的国营单位,搞一个北京户口,这是最好的选择。到后来的2~3年内,户口落定了,又分成4条出路:
1、 上国内的大企业,如:华为
2、 自己做公司,做产品开发;
3、 上外企,比如:爱立信、诺基亚
4、 自己做公司,做买卖;
5、 移民加拿大
我想,首先要看自己适合做什么?做技术还是做买卖。
做技术,需要你对技术感兴趣。我掰着数了一遍,我们研究生班的30来号人,实际上,适合做技术的,大概只有3、4个人,这几个人,1个现在还在华为,3个移民加拿大了,现在这4个人混的还可以,在华为的同学也移民加拿大了,他在华为呆了6年,在华为奖金工资加起来大概30万吧,还有华为的股票,再过几年,华为的股票一上市,也能值个100~200万。要是一毕业就去华为,那现在就绝对不是这个数字了。
要是做技术,最好的就是上大公司,国内的大型企业,象华为中兴肯定是首选,能学到很多东西。华为虽然累,但是,年轻人不能怕累,要是到老了,还需要去打拼,那才是真的累啊。
在外企,我想他们主要就是技术支持和销售,但是技术是学不到的,当然不能一概而论,我指的是象爱立信和诺基亚,真正的研发不会在中国做的,学到的也不如在华为多,其它的中兴我不是很了解,我想应该也不错啊。
一个人都有一技之长,有傍身之技,那是最好的,走到哪里,都能有一口饭吃,还吃的不错,这是传统的观点。
任何技术都是要在某个行业去应用,这个行业市场越大当然越好;要在一个领域之内,做深做精,成为绝对的专家,这是走技术道路的人的选择。不要跳来跳去,在中国,再小的行业你要做精深了,都可以产生很大的利润。
研究生刚毕业的时候,做产品开发的有不少人,都是自己拍拍脑子,觉得这个产品有市场,就自己出来做。现在看来,我的这些同学,做产品开发的成功的没有一例,为什么?资源不足。
1. 资金,刚毕业的学生啊,就是没钱;没钱,也意味着你开发的东西都是小产品;而且只能哥几个自己上,研发、生产、销售都是一个人或者几个人自己来,没有积累,什么都是重新来过。
2. 人脉,任何一个行业,要想进去,需要有很深的人脉,否则,谁会用你的东西啊?谁敢用你的东西啊?
我看到的,我这个班上开发产品的,自己还在坚持的,只剩下一个人了,说实在的,到现在,没有自己的汽车,也没有自己的房子,混的挺惨的。现在出国的不说了,在外企、在华为,至少都是几十万的年薪了,还有各种福利,就是产品开发成功了,又能如何?也就是这样了,但是以前那些年,都没有金钱的积累,等于白干。
我身边的一个自动化系的研究生班的同学,能靠自己开发产品活得还可以的,也只有2个人。说明这条路不是那么好走的啊。
其次就是上外企。我的2个同学,一个上了爱立信,一个先到爱立信后到诺基亚,都混的不错。到诺基亚的后来利用在诺基亚结识的人脉(就是哪些电信的头头脑脑),自己开了公司,也赚了不少的钱。
外企最大的好处就是除了能学到比较规范的管理外,还能给你的职业生涯镀金。到了一个外企外,再到同行业的外企我想就很容易了。而且外企的收入高啊。
自己做公司,做买卖,一开始有3~4个人走这条路,但是真正发财的只有一个人,其他人后来上外企了。做买卖,还是要有一定的天赋,还有机遇。要有对金钱的赤裸裸的欲望,要有商业上的头脑。后来我们同学在一起谈,说,我们即使给自己这个机遇,也未必能做的好。何况当时那个同学看好的产品(做一个台湾产品的代理),我们大家都没有看好,说明,真理还是掌握在少数人手里。
到后来,同学们纷纷移民移民加拿大。
移民加拿大对搞技术的人来说,还是一个不错的选择,但是要尽早,练了几年的技术,就赶紧出去,大概是在1996年走了不到10个,现在都还可以,买了房子和车了。要是晚了,语言再学也难了,而且在国内都混的还可以了,也就没有必要出去了。
我自己呢,先是在国营的研究所混了4年,后来到一家公司干了6年,2002年出来自己做公司,现在也就是混了一个温饱吧,算是有房有车,有点积蓄,但是不多,还有一个可爱的女儿。回首这10来年,有一些经验和教训。
1. 要有一个职业生涯的规划。首先需要定位自己做什么合适,是做买卖还是做技术,一条路走到黑;当然,做了技术,后来改行也行;
2. 做技术,就是要做精做深,成为这个行业的这个技术的专家;最好就是去国内的大公司,才能全面学到东西,能够给你培训的机会;如果大公司进不去,先到小公司练技术,找机会再到大公司去镀金,学高深的技术。千万不要自己做产品,要做也是对这个行业熟悉了,再去做。
3. 积极争取机会。积极争取学习和进步的机会。比如,做技术,就需要多锻炼,多学习,来提高自己的水平。一门技术,只要有机会去学习,都能学的会;要是没有机会,天才也没有办法学到这个技术。柳传志就说,杨元庆就是“哭着喊着要进步”,实际上,就是争取自己的机会;当然,这种强烈的进步欲望,也是领导看重的地方。每一步都走在前面,积累10年,你就有了比其他人更多的机会了。
4. 积累个人的信誉。从你的职业生涯的第一天,就要按照诚信的原则办事。要做到,当人们提起你的名字的时候,说,这哥们还不错,做事还行。
5. 注意利用资源。如果你有有钱的亲戚、成功的长辈或者朋友,可以充分利用这些机会,得到更加顺利的发展前景。
6. 注意财富的不断积累。人生要想得到自由,财富是很关键的。否则,永远仰人鼻息,永远看人脸色。人都是势利眼。今后的家庭、职业生涯,金钱的积累很重要,没有钱,永远不能开张自己的事业,得到更多的机会;财富要做到逐年积累,你才能家庭生活幸福。没有钱是不可能有幸福的家庭的。
7. 注意人脉的积累。最终,事业要靠在社会上的人脉的资源。要注意认识在你这个行业的人,结交他们,最终他们会成为你事业上的助力。
8. 寻求贵人相助。要找大老板来帮助你,得到大老板的赏识。想想看,大蛋糕,切一点就够了,小蛋糕,都给你也吃不饱啊。
9. 多听听成功的前辈和成功的朋友的意见。注意少听家里长辈的意见,尤其是都已经退休的长辈,他们对社会的认识还停留在很久以前,而这个社会已经发生很大的变化呢。最重要的是,长辈有时候会强求你做一些事情,但是,最终的结果他们是不负责的。只有你才能对自己负责。
posted @
2005-11-10 15:02 rkind 阅读(248) |
评论 (0) |
编辑 收藏
滚动公告栏的实现
用到如下html标签:
《MARQUEE id=mar onmouseout=this.start(); direction=up height=150 name="mar"
onmouseover="this.stop();" scrollAmount="1" scrollDelay="0"
style="LINE-HEIGHT: 100%; PADDING-LEFT: 0px; PADDING-RIGHT:0px"width="100%">
1净化社会环境
2也是净化社会平步青云
3净化社会环境
4也是净化社会平步青云
5净化社会环境
6也是净化社会平步青云
7净化社会环境
8也是净化社会平步青云
其中marquee中height中的属性可以更改每次显示的行数,……
posted @
2005-11-02 08:43 rkind 阅读(420) |
评论 (0) |
编辑 收藏
天凉好个秋
对我来说,如果只是降温的话,我未必会觉得秋来了,可是昨天早上走在路上,看到叶子在空中飞舞,地上布满了落叶,秋终于还是来了,这一年也快要过去了,我做了什么,我还能做什么。
昨天新装了个系统,换了个mac的主题,用着很舒服,不过装别的软件又浪费了半天的时候,终究还是忘了做个镜像了,

装好Sever-u后,测试什么问题都没有,后来从别的机子那里一访问,居然不行,怀疑卡巴的事,这个杀毒软件太BT,不怀疑不行啊,关了以后还是不行,难道是windows防火墙?禁掉果然OK
,那总不能禁掉它吧,暂且不说那个提示很烦,别外要装防火墙是让我很不爽的事,一共就这么点内存,哪够用啊

。看看防火墙里面有个例外选项,我把端口20,21,22,全都加上了,结果只能连上,但是不能建立socket,让人不爽的是,居然不能范围添加端口。
然后选例外程序,是在这里,server-u目录下有三个程序文件tray.exe有来系统托盘监控,dameon主程序后台运行,admini管理,这里我把dameon加上以后,一连接,果然ok,
起先加错了,加成tray.exe和admin,就没加dameon,结果怎么试都不行,还上网查了好长时间,真是……
看来我的英语,
posted @
2005-10-28 09:21 rkind 阅读(1494) |
评论 (2) |
编辑 收藏