Java Web架构实验报告

实验一 Struts 2的拦截器技术

一.    实验原理

拦截器的英文名为Interceptor,原来是WebWork框架中一个很好的支持国际化、校验、类型转换的工具。现在WebWork和Struts合并成Struts 2之后,理所当然也成为了Struts 2的一部分。

拦截器本身是一个普通的Java对象,它的功能是动态拦截Action调用,在Action执行前后执行拦截器本身提供的各种各样的Web项目需求。当然也可以阻止Action的执行,同时也可以提取Action中可以复用的部分。

在Struts 2中还有个拦截器栈的概念,其实它就是拦截器的一个集合。它把多个拦截器集合起来,按照在栈中的配置的顺序执行,特别是针对Action可以拦截相应的方法或者字段。

二.    技术要点

l  拦截器类和被拦截类内容。

l  运用反射机制调用类和类方法。

l  设置拦截器处理类,配置拦截器在何时执行以及拦截器类和被拦截器类执行的先后顺序。

l  设置代理对象类实现拦截器拦截功能。

l  测试程序运行结果显示拦截功能正常执行情况。

三.    实现代码

功能执行类:

package com.example.struts.interceptor;

//执行功能类

publicclass ExecuteFunction implements ExecuteFunctionInterface {

       //执行功能类执行方法

       publicvoid execute() {

              System.out.println("execute something...");

       }

}

功能执行接口:

package com.example.struts.interceptor;

//执行功能接口

publicinterface ExecuteFunctionInterface {

       publicvoid execute();

}

拦截器类:

package com.example.struts.interceptor;

//拦截器类

publicclass Interceptor {

//拦截执行功能类之前执行方法

       publicvoid beforeDoing(){

              System.out.println("before doing Something...");

       }

//拦截执行功能类之后执行方法

       publicvoid afterDoing(){

              System.out.println("after doing Something...");

       }

}

拦截器处理类:

package com.example.struts.interceptor;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

//拦截器处理类

public class InterceptorHandler implements InvocationHandler {

       private Object object;

       private Interceptor inter = new Interceptor();

       //设置需要拦截的执行功能类

       public void setObject(Object object) {

              this.object = object;

       }

       //接口invoke方法,proxy是代理实例,method是实例方法,args是代理类传入的方法参数。

       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

              inter.beforeDoing();

             

              //object提供该方法的类实例,args是调用方法所需的参数值的数组

              Object returnObject = method.invoke(object, args);

              inter.afterDoing();

              return returnObject;

       }

}

代理对象类:

package com.example.struts.interceptor;

import java.lang.reflect.Proxy;

publicclass ProxyObject {

       private InterceptorHandler handler = new InterceptorHandler();

       //得到执行类的代理对象实例

       public Object getProxy(Object object) {

              handler.setObject(object);

              //创建对象实例

              return Proxy.newProxyInstance(ExecuteFunction.class.getClassLoader(), object.getClass()

                            .getInterfaces(), handler);

       }

}

测试程序:

package com.example.struts.interceptor;

//测试执行类和拦截器类是否执行

publicclass TestInterceptor {

       publicstaticvoid main(String[] args) {

              ExecuteFunctionInterface test = new ExecuteFunction();

              //得到执行类的一个代理对象实例

              ExecuteFunctionInterface resultObject = (ExecuteFunctionInterface) new ProxyObject()

                            .getProxy(test);

              //代理对象执行

              resultObject.execute();

       }

}

在TestInterceptor.java文件上单击鼠标右键,然后单击Run As|Java Aplication命令。在MyEclipse控制台中显示代码中定义的打印方法。测试程序运行结果如图1。

图1

四.    源程序解读

(1)ExcuteFuction是一个正常执行业务逻辑类的Java类,它是继承接口ExcuteFuctionInterface,其中的execute方法,作为示例,笔者只是调用打印方法打印了“execute something···”这一行字。还定义了Interceptor拦截器类,该类中有两个方法也是为了示例,这两个方法都是简单打印了一行字。

如图1所示,如果其中beforeDoing方法在ExcuteFuction类的execute方法打印“execute something···”之前打印“before doing  Something···”,afterDoing方法在其后打印“after doing  Something···”就达到了拦截器在功能执行类前后执行拦截的目的。

(2)为了让拦截器类和被拦截的功能执行类发生关联关系,使用Java中的反射机制。在InterceptorHandler类中,通过扩展java.lang.reflect.InvocationHandler接口,覆盖重写invoke方法,该方法使用method.invoke来调用再通过setObject方法传入的功能执行类对象的方法。比如这里通过setObject方法传入的是ExcuteFuction对象,该对象中包含一个execute方法,而且是无参的,因此method.invoke调用的就是execute方法,只是执行并打印出“execute something···”,同时将已经被设置为私有类变量的拦截器类中的两个方法在其前后执行。

这样invoke方法已经将拦截器类中的两个方法在功能执行类的方法执行前后执行了,现在要做的只是如何让该类中的invoke方法被测试程序使用。

(3)创建ProxyObject对象是想通过使用设计模式中的代理模式来生成功能执行类的一个代理对象实例。通过newProxyInstance方法调用InteceptorHandler类。说的再明白点就是如果这个代理对象也执行功能执行类的execute方法时,newProxyInstance方法的作用是把execute方法指派给InteceptorHandler类执行,通过反射,InteceptorHandler类执行execute方法是在invoke方法中执行。

因此在method.invoke方法前后的拦截器类的beforeDoing和afterDoing方法也会执行。从而就会按照图1所示的顺序打印三个方法的显示内容。

(4)测试程序通过创建一个代理对象类,并调用getProxy方法得到ExecuteFunction的一个代理对象实例,然后在这个代理对象执行execute方法时,因为在之前已经说明其实创建代理对象实例时,已经是调用了InterceptorHandler类的invoke方法,就实现了动态代理机制。

仔细看测试程序就知道在getProxy方法执行前,只是创建了ExecuteFunctionInterface接口对象,并没有创建代理对象,这里创建代理对象的目的就是让execute方法执行拦截器类的方法,让拦截器类的方法也被执行,并且执行顺序也是根据InterceptorHandler类的invoke方法中定义的顺序执行。拦截器相当于在功能执行类前后都拦截了它,并执行了自己的方法。

实验二Struts 2类型转换技术

一.    实验原理

Struts 2的类型转换几乎支持Java中各种数据类型的转换。甚至开发者还可以自定义自己的类型转换功能。我们不推荐开发人员开发自定义的类型类型转换功能。原因有二。一是遵循IT界著名名言“不重复发明轮子”,不在前人的成果上再次浪费时间。二是类型转换本身在开发工作中就不应该占用大量时间和人力。况且自定义自己的类型转换,项目风险也有可能增加。从项目管理角度上讲对时间、成本、风险的管理都存在负面效应。

Struts 2本身所具有的类型转换大致分为以下转换:

n  int、boolen、double等Java基本类型转换。

n  Date类型转换。

n  List类型转换。

n  Set类型转换。

n  数组类型转换。

除了数组的类型转换不大实用以外,其他几种类型转换都是比较常用的。而且Date类型转换也是属于单个Java变量的转换。而List、Set可以算作集合类型的转换即多个Java变量封装成单个集合的类型转换。

二.    技术要点

l  基本类型转换Action中的使用方式。

l  基本类型转换在视图界面中的使用方式。

三.    实现代码

使用的Action文件:

package com.action;

import com.model.Material;

import com.opensymphony.xwork2.ActionSupport;

public class AddMaterialAction extends ActionSupport {

       //属性类型需要类型转换的对象

       private Material material;

       public Material getMaterial() {

              return material;

       }

       public void setMaterial(Material material) {

              this.material = material;

       }

       public String execute() throws Exception {

              return SUCCESS;

       }

}

配置文件中的导航定义:

<?xml version="1.0" encoding="gb2312"?>

<!DOCTYPE struts PUBLIC

"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"

"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

       <!-- Action所在包定义 -->

       <package name="C07.1.1" extends="struts-default">        

              <action name="addMaterial"

                     class="com.action.AddMaterialAction">

                     <result name="input">/jsp/addMaterial.jsp</result>

                     <result name="success">/jsp/showMaterial.jsp</result>

              </action>

       </package>

</struts>

类型转换的数据输入JSP文件:

<%@ page language="java" pageEncoding="gb2312"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<!-- struts2标签库调用声明 -->

<%@taglib prefix="s" uri="/struts-tags"%>

<html>

       <head>

              <title>添加材料</title>

              <s:head />

       </head>

       <body>

              <h3 align="left">

                     Struts2类型转换使用范例

              </h3>

              <!-- 材料输入表单 -->

                     <table>

                     <s:form id="materialForm" action="addMaterial">

                     <s:textfield name="material.material" label="材料名"></s:textfield>

                     <s:textfield name ="material.bid" label="价格"></s:textfield>

                     <s:textfield name ="material.mount" label="库存量"></s:textfield>

                     <s:datetimepicker name ="material.expireDate" label="过期日期"></s:datetimepicker>

                     <s:submit value="提交"></s:submit>

                     </s:form>

                     </table>        

       </body>

</html>

数据转换的显示数据JSP文件:

<%@ page language="java" pageEncoding="gb2312"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<!-- struts2标签库调用声明 -->

<%@taglib prefix="s" uri="/struts-tags"%>

<html>

    <head>

        <title>添加材料</title>

        <s:head />

    </head>

    <body>

        <h3 align="left">

            Struts2类型转换使用范例

        </h3>

        <!-- 材料数据显示 -->

            <table>           

                材料名: <s:property value="material.material" ></s:property>

                价格:   <s:property value="material.bid" ></s:property>

                库存量:  <s:property value="material.mount" ></s:property>

                过期日期: <s:property value="material.expireDate" ></s:property>        

            </table>       

    </body>

</html>

数据输入如图2所示。显示数据如图3所示。请注意各种Java类型数据在显示页面的输入页面的格式及显示的不同之处。

图2

图3

四.    源程序解读

(1)在页面上输入一个Material对象的所有属性值,并将它的属性值显示在页面上。由于之前很多示例中已经有过Material这个JavaBean对象代码,这里就没有写出给读者参阅。由已显示的示例代码也可知,Material对象由材料名、价格、库存量和材料过期日期四个属性组成。

恰好这4个属性的数据类型分别是String、double、int、date四个Java类型,因此由该示例可以知道Struts 2的类型转换是如何转换这些Java类型。由于Struts 2也是使用拦截器来进行类型转换,因此对这些基本的Java类型转换根本不需要开发人员编写任何类型转换代码。

(2)在输入数据的页面使用OGNL和Struts 2标签来建立一个数据输入的表单。使用的Material对象在Action中已定义完成,并建立getter、setter方法。这样在JSP页面可以设置该对象。在页面中按“提交”按钮后,就相当于“setMaterial()”方法。在系统根据struts.xml配置文件执行Action之前,Struts 2自带的类型转换拦截器就已经将Material对象中几个属性变量的类型由页面上输入时的String类型转换为Material对象属性变量被定义的数据类型。

(3)在数据显示页面上显示数据时,实际上做的事情是上述操作的一个逆向操作。Material对象中每个属性变量的数据类型又都转换为页面上需要显示的String类型。

(4)实际上在开发工作中,除非有根据特殊需求需要类型转换之外,在Struts 2中绝大部分类型转换都已经由Struts 2自己完成。因此给开发者节省了大量开发时间,除了Java基本类型转换之外,有时候在页面上需要批量输入一些数据,如果这些数据也像本示例的Material对象一样,那么可以使用Struts 2自带的对集合类型的转换功能来完成类型转换。

实验三Struts 2输入校验

一.    实验原理

在Web系统项目中有大量的视图页面需要用户自行输入很多数据。这些数据的类型有很多种。为了防止某些客户的恶意输入以及对Web项目的恶意破坏。必须引入输入校验像Windows操作系统中的防火墙一样,把一些“垃圾”数据过滤,挡在Web系统之外。

Struts 2的输入校验是以类型转换为基础。而且输入校验一般和Web系统的业务逻辑息息相关。

二.    技术要点

l  几种基本Java数据类型输入校验。

l  针对具体业务逻辑进行输入校验。

三.    实现代码

使用的Action文件:

/*

 * Generated by MyEclipse Struts

 * Template path: templates/java/JavaClass.vtl

 */

package action;

import java.util.Date;

import com.opensymphony.xwork2.ActionSupport;

/**

 *

 * @author frankwu

 *

 */

public class RegisterAction extends ActionSupport {

       // Action类公用私有变量,用来做页面导航标志

       private static String FORWARD = null;

       // 用户名属性

       private String username;

       // 密码属性

       private String password;

       // 确认密码属性

       private String repassword;

       // 生日属性

       private Date birthday;

       // 手机号码属性

       private String mobile;

       // 年龄属性

       private int age;

       // 取得用户名值

       public String getUsername() {

              return username;

       }

       // 设置用户名值

       public void setUsername(String username) {

              this.username = username;

       }

       // 取得密码值

       public String getPassword() {

              return password;

       }

       // 设置密码值

       public void setPassword(String password) {

              this.password = password;

       }

       // 取得确认密码值

       public String getRepassword() {

              return repassword;

       }

       // 设置确认密码值

       public void setRepassword(String repassword) {

              this.repassword = repassword;

       }

       // 取得生日值

       public Date getBirthday() {

              return birthday;

       }

       // 设置生日值

       public void setBirthday(Date birthday) {

              this.birthday = birthday;

       }

       // 取得手机号码值

       public String getMobile() {

              return mobile;

       }

       // 设置手机号码值

       public void setMobile(String mobile) {

              this.mobile = mobile;

       }

       // 取得年龄值

       public int getAge() {

              return age;

       }

       // 设置年龄值

       public void setAge(int age) {

              this.age = age;

       }

       // 执行注册方法

       public String execute() throws Exception {

              FORWARD = "success";

              return FORWARD;

       }

       // 校验方法,用来输入校验

       public void validate() {

              // 校验是否输入用户名

              if (getUsername() == null || getUsername().trim().equals("")) {              

                     addFieldError("username", "请输入用户名");

              }

              // 校验是否输入生日

              if(getBirthday()==null){

                     addFieldError("birthday", "请输入生日日期");

              }else

              // 校验是否输入正确的生日日期

              if(getBirthday().after(new Date())){

                     addFieldError("birthday", "请不要输入未来日期");

              }

              // 校验是否输入密码

              if (getPassword() == null || getPassword().trim().equals("")) {                

                     addFieldError("password", "请输入密码");

              }

              // 校验是否输入确认密码

              if (getRepassword() == null || getRepassword().trim().equals("")) {                

                     addFieldError("repassword", "请输入确认密码");

              }

              // 校验输入的密码和确认密码是否一致

              if (!getPassword().equals(getRepassword())) {                  

                     addFieldError("repassword", "确认密码和密码输入不一致");

              }

              // 校验输入的手机号码长度是否正确

              if (getMobile().length()!=11) {                  

                     addFieldError("mobile", "请输入正确的手机号码");

              }

              // 校验输入的年龄是否正确

              if (getAge()<1||getAge()>99) {                  

                     addFieldError("age", "请输入您的真实年龄");

              }           

       }

}

配置文件中的导航定义:

<?xml version="1.0" encoding="gb2312"?>

<!DOCTYPE struts PUBLIC

"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"

"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

       <!-- Action所在包定义 -->

       <package name="C08.1.1" extends="struts-default">

       <!-- Action名字,类以及导航页面定义 -->

              <!-- 通过Action类处理才导航的的Action定义 -->

              <action name="Register"

                     class="action.RegisterAction">

                     <result name="input">/jsp/register.jsp</result>

                     <result name="success">/jsp/success.jsp</result>

              </action>

              <!-- 直接导航的的Action定义 -->

              <action name="index" >

                     <result >/jsp/register.jsp</result>               

              </action>

       </package>

</struts>

输入校验的数据输入JSP文件:

<%@ page language="java" pageEncoding="gb2312"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<!-- struts2标签库调用声明 -->

<%@taglib prefix="s" uri="/struts-tags"%>

<html>

<head>

       <title>注册页面</title>

</head>

<body>

       <!-- 用户信息注册form表单 -->

       <s:form action="Register">

              <table width="60%" height="76" border="0">

                            <!-- 各标签定义 -->

                            <s:textfield name="username" label="用户名"/>

                            <s:password name="password" label="  " />

                            <s:password name="repassword" label="  码确认" />

                            <s:textfield name="birthday" label="生日"/>

                            <s:textfield name="mobile" label="手机号码"/>

                            <s:textfield name="age" label="年龄"/>

                            <s:submit value="注册" align="center"/>                        

              </table>

       </s:form>

</body>

</html>

数据不进行如何输入显示的出错信息如图4。输入密码不一致时的出错信息如图5。

图4

图5

四.    源程序解读

(1)Struts 2对输入校验这方面采用的最基本方法是在每个Action中继承ActionSupport类,并且重写它的输入校验方法validate()。本示例中的RegisterAction代码中也显示,根据页面上输入的各种校验将所有不符合输入校验规则的错误信息都由ActionSupport类中另一个方法addFieldError方法将错误信息加入到表单错误信息,并且在输入数据的页面显示,不会再由Action导航到注册成功页面。

struts.xml也定义了一个名字为“input”的result,它表明将所有输入失败的错误信息导航到一个特定页面。本示例中笔者还是将这个特定页面定义为数据输入的页面。

(2)再次阅读RegisterAction代码,可以发现在validate方法中笔者编写了很多if语句。每一个if语句中都针对表单中某一字段进行输入校验。如果发现不符合输入校验规则则都调用addFieldError方法。该方法中有两个参数,第一个参数都是表单中字段名,这里所有的名字都和输入数据的页面中每一个字段的name属性中内容相同。否则Struts 2找不到具体的错误信息是针对哪一个字段。第二个参数则是错误信息的内容。

这些内容就是在输入校验失败时显示在之前所说的特定页面中。

(3)validate方法中的各个if语句判断了表单中各个字段的输入数据是否符合输入校验的规则,这些规则也是开发人员根据特定业务逻辑定义的。比如其中数据是否输入,输入的生日信息是否在当前日期之前等。细心的读者又可以发现并没有对这些字段进行类型转换,但在Action中某些字段类型都已经变成Java的一些基本类型。比如生日字段,页面上输入时是字符串,在Action中已经变成Java中的Date类型。

页面上输入的数据已经都由字符串类型转换成Action中指定的Java类型。因此从这一点更加说明类型转换是输入校验的基础,也可以说是一种特定的输入校验。

相关推荐