Struts 2开发模式基础和简单示例程序
一、Model 2模式的缺陷
之前的文章中已经介绍了Model 2开发模式(JSP+Servlet+JavaBean+JDBC)的优越性,它通过分离系统各部分模块的功能职责,成功地克服了Model 1的缺点,但是这种开发方式仍然存在弊端,原因在于:它是以重新引入原始Servlet编程为代价的,程序员在编写程序时必须继承HttpServlet、覆盖doGet()和doPost()方法,严格遵守Servlet代码的编写规范。
二、Servlet Filter技术
Servlet过滤器是在Java Servlet规范中定义的,它能够对过滤器关联的URL请求和响应进行检查和修改。过滤器能够在Servlet被调用之前检查Request对象,修改Request Header和Request内容;在Servlet被调用之后检查Response对象,修改Response Header和Response内容。过滤器过滤的URL资源可以是Servlet、JSP、HTML文件,或者是整个路径下的任何资源。多个过滤器可以构成一个过滤器链,当请求过滤器关联的URL时,过滤器链上的过滤器会挨个发生作用。
如图所示为过滤器处理请求的过程,图中显示了正常请求、加过滤器请求和加过滤器链请求的处理过程。过滤器可以对Request对象和Response对象进行处理。
1、所有的过滤器类都必须实现java.Servlet.Filter接口,它含有3个过滤器类必须实现的方法。
① init(FilterConfig)。
这是过滤器的初始化方法,Servlet容器创建过滤器实例后将调用这个方法。在这个方法中可以通过FilterConfig参数读取web.xml文件中过滤器的初始化参数。
② doFilter(ServletRequest,ServletResponse,FilterChain)。
这个方法完成实际的过滤操作,当用户请求与过滤器关联的URL时,Servlet容器将先调用过滤器的doFilter方法,在返回响应之前也会调用此方法。FilterChain参数用于访问过滤器链上的下一个过滤器。
③ destroy()。 Servlet容器在销毁过滤器实例前调用该方法,这个方法可以释放过滤器占用的资源。
过滤器编写完成后,要在web.xml进行配置,格式如下:
<filter>
<filter-name>过滤器名称</filter-name>
<filter-class>过滤器对应的类</filter-class>
<!--初始化参数-->
<init-param>
<param-name>参数名称</param-name>
<param-value>参数值</param-value>
</init-param>
</filter>
2、在web.xml中配置过滤器与URL关联的方法
① 与一个URL资源关联。
<filter-mapping>
<filter-name>过滤器名</filter>
<url-pattern>xxx.jsp</url.pattern>
</filter-mapping>
② 与一个URL目录下的所有资源关联。
<filter-mapping>
<filter-name>过滤器名</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
③ 与一个Servlet关联。
<filter-mapping>
<filter-name>过滤器名</filter-name>
<Servlet-name>Servlet名称</Servlet-name>
</filter-mapping>
3、过滤器主要功能
① 权限控制。通过过滤器实现访问的控制,当用户访问某个链接或者某个目录时,可利用过滤器判断用户是否有访问权限。
② 字符集处理。可以在过滤器中处理request和response的字符集,而不用在每个Servlet或者JSP中单独处理。
③ 其他一些场合。过滤器非常有用,可以利用它完成很多适合的工作,如计数器、数据加密、访问触发器、日志、用户使用分析等。
三、Struts 2内部机制
Struts 2的设计思想:用Servlet Filter技术将Servlet API隐藏于框架之内,一个请求在Struts 2框架内被处理,大致分为以下几个步骤
1、工作原理如下:
① 客户端初始化一个指向Servlet容器(如Tomcat)的请求。
② 这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个为ActionContextCleanUp,它对于Struts 2和其他框架的集成很有帮助,如SiteMesh Plugin)。
③ 接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请求是否需要调用某个Action。
④ 如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy。
⑤ ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类。
⑥ ActionProxy创建一个ActionInvocation的实例。
⑦ ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及相关拦截器(Interceptor)的调用。
⑧ 一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或FreeMarker的模板。在表示的过程中可以使用Struts 2 框架中继承的标签,在这个过程中还要涉及ActionMapper。
2、Struts 2框架工作流程
Struts 2的基本流程如下。
① Web浏览器请求一个资源。
② 过滤器Dispatcher查找请求,确定适当的Action。
③拦截器自动对请求应用通用功能,如验证和文件上传等操作。
④Action的execute()方法通常用来存储和(或)重新获得信息(通过数据库)。
⑤结果被返回到浏览器。可能是HTML、图片、PDF或其他。
四、Struts2示例程序(采用JSP+Struts 2+JavaBean+JDBC方式开发一个Web登录程序。)
1、创建一个Java EE项目jsp_struts2_javabean_jdbc,下载Struts2的jar包
http://struts.apache.org/,下载Struts2最小核心依赖库(大小仅为4.44 MB),单击页面中“Essential Dependencies Only”,本例使用Struts2.5.18,可通过资源下载。
在其目录struts-2.5.18-min-lib\struts-2.5.18\lib下看到有8个jar包,包括以下内容。
1)Struts 2的4个基本类库
struts2-core-2.5.18.jar
ognl-3.1.15.jar
log4j-api-2.10.0.jar
freemarker-2.3.26-incubating.jar
2)附加的4个库
commons-io-2.5.jar
commons-lang3-3.6.jar
javassist-3.20.0-GA.jar
commons-fileupload-1.3.3.jar
以上Struts 2包连同数据库驱动jar包一共是9个jar包,
将它们一起复制到项目的\WebRoot\WEB-INF\lib路径下。
2、配置Struts2项目的web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_9" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<filter>
<filter-name>struts-prepare</filter-name>
<filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareFilter</filter-class>
</filter>
<filter>
<filter-name>struts-execute</filter-name>
<filter-class>org.apache.struts2.dispatcher.filter.StrutsExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts-prepare</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>struts-execute</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<display-name>jsp_struts2_javabean_jdbc</display-name>
<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
</web-app>
3、在src下构造JavaBean、创建JDBC
JavaBean代码如下:
package org.easybooks.test.model.vo;
public class UserTable {
//Fields
private Integer id;
private String username;
private String password;
//Property accessors
//属性 id 的 get/set 方法
public Integer getId(){
return this.id;
}
public void setId(Integer id){
this.id=id;
}
//属性 username 的 get/set 方法
public String getUsername(){
return this.username;
}
public void setUsername(String username){
this.username=username;
}
//属性 password 的 get/set 方法
public String getPassword(){
return this.password;
}
public void setPassword(String password){
this.password=password;
}
}
JDBC代码如下;
package org.easybooks.test.jdbc;
import java.sql.*;
public class SqlSrvDBConn {
private Statement stmt;
private Connection conn;
ResultSet rs;
public SqlSrvDBConn(){
stmt=null;
try{
//加载SQL server数据库驱动与下面连接mysql二选一
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
conn=DriverManager.getConnection("jdbc:sqlserver://localhost:1433;databaseName=TEST","sa","123456");
//加载MySQL数据库驱动
Class.forName("com.mysql.jdbc.Driver");
conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/TEST","root","123456");
}catch(Exception e){
e.printStackTrace();
}
rs=null;
}
public ResultSet executeQuery(String sql)
{
try
{
stmt=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
rs=stmt.executeQuery(sql);
}catch(SQLException e){
System.err.println("Data.executeQuery: " + e.getMessage());
}
return rs;
}
public void closeStmt()
{
try
{
stmt.close();
}catch(SQLException e){
System.err.println("Data.executeQuery: " + e.getMessage());
}
}
public void closeConn()
{
try
{
conn.close();
}catch(SQLException e){
System.err.println("Data.executeQuery: " + e.getMessage());
}
}
}
4、编写jsp代码
login.jsp
<%@ page language="java" pageEncoding="gb2312"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<html>
<head>
<title>简易留言板</title>
</head>
<body bgcolor="#E3E3E3">
<s:form action="main" method="post" theme="simple">
<table>
<caption>用户登录</caption>
<tr>
<td>
用户名:<s:textfield name="user.username" size="20"/>
</td>
</tr>
<tr>
<td>
密 码:<s:password name="user.password" size="21"/>
</td>
</tr>
<tr>
<td>
<s:submit value="登录"/>
<s:reset value="重置"/>
</td>
</tr>
</table>
</s:form>
如果没注册单击<a href="">这里</a>注册!
</body>
</html>
main.jsp
<%@ page language="java" pageEncoding="gb2312"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<html>
<head>
<title>留言板信息</title>
</head>
<body>
<s:set var="user" value="#session['user']"/>
<s:property value="#user.username"/>,您好!欢迎登录留言板。
</body>
</html>
error.jsp
<%@ page language="java" pageEncoding="gb2312"%>
<html>
<head>
<title>出错</title>
</head>
<body>
登录失败!单击<a href="login.jsp">这里</a>返回
</body>
</html>
5、实现控制器Action
基于Struts 2框架的Java EE应用程序使用自定义的Action(控制器)来处理深层业务逻辑。本例定义名为“main”的控制器。
MainAction.java
package org.easybooks.test.action;
import java.sql.*;
import java.util.*;
import org.easybooks.test.model.vo.*;
import org.easybooks.test.jdbc.SqlSrvDBConn;
import com.opensymphony.xwork2.*;
public class MainAction extends ActionSupport{
private UserTable user;
//处理用户请求的 execute 方法
public String execute() throws Exception{
String usr=user.getUsername(); //获取提交的用户名
String pwd=user.getPassword(); //获取提交的密码
boolean validated=false; //验证成功标识
SqlSrvDBConn sqlsrvdb=new SqlSrvDBConn();
ActionContext context=ActionContext.getContext();
Map session=context.getSession(); //获得会话对象,用来保存当前登录用户的信息
UserTable user1=null;
//先获得 UserTable 对象,如果是第一次访问该页,用户对象肯定为空,但如果是第二次甚至是第三次,就直接登录主页而无须再次重复验证该用户的信息
user1=(UserTable)session.get("user");
//如果用户是第一次进入,会话中尚未存储 user1 持久化对象,故为 null
if(user1==null){
//查询 userTable 表中的记录
String sql="select * from userTable";
ResultSet rs=sqlsrvdb.executeQuery(sql); //取得结果集
try {
while(rs.next())
{
if((rs.getString("username").trim().compareTo(usr)==0)&&(rs.getString("password").compareTo(pwd)==0)){
user1=new UserTable(); //创建持久化的 JavaBean 对象 user1
user1.setId(rs.getInt(1));
user1.setUsername(rs.getString(2));
user1.setPassword(rs.getString(3));
session.put("user", user1); //把 user1 对象存储在会话中
validated=true; //标识为 true 表示验证成功通过
}
}
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
sqlsrvdb.closeStmt();
sqlsrvdb.closeConn();
}
else{
validated=true; //该用户在之前已登录过并成功验证,故标识为 true 表示无须再验了
}
if(validated)
{
//验证成功返回字符串"success"
return "success";
}
else{
//验证失败返回字符串"error"
return "error";
}
}
public UserTable getUser(){
return user;
}
public void setUser(UserTable user){
this.user=user;
}
}
6、配置Action
在编写好Action(控制器)的代码之后,还需要进行配置才能让Struts 2识别这个Action。在src下创建文件struts.xml(注意文件位置和大小写),输入如下的配置代码:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
<!-- START SNIPPET: xworkSample -->
<struts>
<package name="default" extends="struts-default">
<!-- 用户登录 -->
<action name="main" class="org.easybooks.test.action.MainAction">
<result name="success">main.jsp</result>
<result name="error">error.jsp</result>
</action>
</package>
<constant name="struts.i18n.encoding" value="gb2312"/>
</struts>
<!-- END SNIPPET: xworkSample -->
7、配置Tomcat服务器后即可成功运行
五、Struts 2在其中所起的作用
可以发现,我们用Action模块(MainAction)取代了原来的Servlet类(MainServlet),这样做的好处在于:屏蔽了Servlet原始的API,简化了代码结构,而改用Struts 2核心来自动地控制JSP页面跳转,系统的结构所示。
相比较以加深理解,可以很清楚地看出:这里用Struts 2取代了原Servlet的位置,而且业务逻辑处理的功能由用户自定义编写Action去实现,与Struts 2的控制核心相分离,这就进一步降低了系统中各部分组件的耦合度。