0%

在使用Ajax与Action交互时,可以直接在Action里输出,而不是转发给JSP.struts.xml中声明action时,不再需要result标签

package com.struts2;

import java.io.PrintWriter;

import javax.servlet.http.HttpServletResponse;

import org.apache.struts2.ServletActionContext;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;

import com.opensymphony.xwork2.ActionSupport;

public class GetXMLAction extends ActionSupport {
    private String name;
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String execute() throws Exception {
        // TODO Auto-generated method stub
        People people=new People();
        people.setId(1);
        people.setName("zhangsan");
        people.setAddress("beijing");
        people.setAge(20);
        
        People people2=new People();
        people2.setId(2);
        people2.setName("lisi");
        people2.setAge(30);
        people2.setAddress("Hangzhou");
        
        //下面都是dom4j的使用方法,还是挺简单的
        org.dom4j.Document document=DocumentHelper.createDocument();
        
        Element rootElement=document.addElement("persons");
        
        rootElement.addComment("This is comment!!");
        Element e=rootElement.addElement("person");
        Element idElement=e.addElement("id");
        Element nameElement=e.addElement("name");
        Element ageElement=e.addElement("age");
        Element addressElement=e.addElement("address");
        
        if(name.equals("zhangsan"))
        {
            idElement.setText(String.valueOf(people.getId()));
            nameElement.setText(people.getName());
            ageElement.setText(String.valueOf(people.getAge()));
            addressElement.setText(people.getAddress());
        }else if(name.equals("lisi"))
        {
            idElement.setText(String.valueOf(people2.getId()));
            nameElement.setText(people2.getName());
            ageElement.setText(String.valueOf(people2.getAge()));
            addressElement.setText(people2.getAddress());
        }
        
        //取HttpServletResponse对象,直接输出
        HttpServletResponse resp=ServletActionContext.getResponse();
        resp.setContentType("text/xml;charset=utf-8");
        resp.setHeader("cache-control", "no-cache");
        PrintWriter out=resp.getWriter();
        OutputFormat format=OutputFormat.createPrettyPrint();
        format.setEncoding("utf-8");
        XMLWriter writer=new XMLWriter(out, format);
        writer.write(document);
        out.flush();
        out.close();
        //返回值可以随意,因为不再判断
        return null;
    }
}

这里说的文件下载指的是通过Java代码返回文件流给客户端,而不是直接链接一个具体的文件URL。 原理:Action中定义一个读取文件流的方法,返回值为InputStream,声明Action时,result类型为stream,参数指定读取文件流的方法以及返回给客户端的文件名。 下面的例子是让用户自己提交文件路径,Action再读取该文件并返回给客户端,当然,在真实的项目中,这样做是很不安全的。 DownloadAction2.java:

package com.struts2;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionSupport;

public class DownloadAction2 extends ActionSupport {
    
    private String fileName;
    public String getFileName() {
        return fileName;
    }
    public void setFileName(String fileName) {
        this.fileName = fileName;
    }
    public InputStream getDownloadFile()
    {
        //以下几行先把get得到的文件名转换编码,否则中文文件名取不到文件
        String utfFileName=null;
        try {
            utfFileName=new String(fileName.getBytes("iso8859_1"),"UTF-8");
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return ServletActionContext.getServletContext().getResourceAsStream(utfFileName);
    }
    @Override
    public String execute() throws Exception {
        // TODO Auto-generated method stub
        return super.execute();
    }
}

struts.xml配置:

        <!-- result的type设为stream,参数inputName值与Action中的返回InputStream的方法getDownloadFile对应
            contentDisposition参数attachment代表无论何种文件,均弹出下载框,而不是用浏览器打开,
            filename指定下载时默认的文件名,通过${属性名}取action中的属性
         -->
        <action name="downloadFile2" class="com.struts2.DownloadAction2">
            <result type="stream">
                <param name="inputName">downloadFile</param>
                <param name="contentDisposition">attachment;filename=${fileName}</param>
            </result>
        </action>

测试: http://localhost:8080/struts2/downloadFile2.do?fileName=index.jsp

需要去apache官网下载commons-fileupload和commons-io两个组件,导入项目中。上传的表单就不贴了,各种语言都一样,下面是处理上传的Servlet:

package com.servlet;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

public class UploadServlet extends HttpServlet {

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    // TODO Auto-generated method stub
    DiskFileItemFactory factory=new DiskFileItemFactory();
    String path=req.getRealPath("/upload/");
    String tempDir=req.getRealPath("/temp/");
    
    //文件临时目录,默认为System.getProperty("java.io.tmpdir")
    factory.setRepository(new File(tempDir));
    //设置文件大小上限,当文件大于此值时,先存储到临时目录,否则存在内存中
    factory.setSizeThreshold(1024*100);
    ServletFileUpload sfu=new ServletFileUpload(factory);
    try {
        List list=sfu.parseRequest(req);
        PrintWriter out=resp.getWriter();
        resp.setContentType("text/html; charset=utf-8");   
         resp.setCharacterEncoding("utf-8");   
        for(FileItem item:list)
        {
            if(item.isFormField())
            {
                out.println(item.getFieldName()+"="+item.getString()+"  ");
            }else{
                try {
                    String value=item.getName();
                    int start=value.lastIndexOf("//");
                    String filename=value.substring(start+1);
                    //使用write方法写入文件,自动处理临时文件
                    item.write(new File(path,filename));
                    
                    //使用文件流写入文件,需要手工删除临时文件,
                    FileOutputStream fos=new FileOutputStream(new File(path,"bb.jpg"));
                    InputStream is=item.getInputStream();
                    byte\[\] bs=new byte\[1024\];
                    int len=0;
                    while((len=is.read(bs, 0, 1024))>-1)
                    {
                        fos.write(bs,0,len);
                    }
                    fos.close();
                    is.close();
                    
                    
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    } catch (FileUploadException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    
}
    

}

拦截器类似于Filter,会在调用Action前和后执行。默认情况下,每个Action都会调用defaultStack这个拦截器栈,包含了多个常用的拦截器(在struts-default.xml中声明),但是当在Action声明中显式添加某个拦截器时,defaultStack就会失效,需要在最后一行手动添加. Interceptor1.java:

package com.interceptor;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;

public class Interceptor1 implements Interceptor {
    /*
     * 声明字符串变量,及setter方法,名字与param name属性对应,会自动从struts.xml读取配置
     */
    private String test;
    public void setTest(String test) {
        this.test = test;
    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

    @Override
    public void init() {
        // TODO Auto-generated method stub
        System.out.println("init invoked");
        System.out.println(test);
    }

    @Override
    public String intercept(ActionInvocation invocation) throws Exception {
        // TODO Auto-generated method stub
        System.out.println("before invoked");
        String result=invocation.invoke();
        System.out.println(result);
        System.out.println("after invoked");
        return result;
    }

}

也可以继承AbstractInterceptor,而不是实现Interceptor,该类对init和destory方法作了空实现 struts.xml中声明及使用:

        <interceptors>
            <interceptor name="interceptor1" class="com.interceptor.Interceptor1">
                <!-- 初始化参数,需要在拦截器类中定义相应的属性 -->
                <param name="test">asdfjweoifjljfsd</param>
            </interceptor>
        </interceptors>



        <action name="login" class="com.struts2.LoginAction">
            <result name="success">/result.jsp</result>
            <result name="input">/login.jsp</result>
            <result name="invalid.token">/1.jsp</result>
            <interceptor-ref name="interceptor1"></interceptor-ref>
            <interceptor-ref name="token"></interceptor-ref>
            <!-- 添加其他拦截器后,默认的defaultStack拦截器栈就会失效,需要显示添加 -->
            <interceptor-ref name="defaultStack"></interceptor-ref>
        </action>

防止表单重复提交,需要在表单中增加一段随机字符串,来验证是否重复提交。 表单使用struts标签库生成:

    <s:form action="login" theme="simple">
        username: <input type="text" name="username" />
        <br />
        password:<input type="password" name="password" />
        <s:token></s:token><!-- 插入token -->
        <input type="submit" value="提交" />
    </s:form>

Action类不需要作任何修改,只需修改struts.xml中配置:

        <action name="login" class="com.struts2.LoginAction">
            <result name="success">/result.jsp</result>
            <result name="input">/login.jsp</result>
            <result name="invalid.token">/1.jsp</result>
            <interceptor-ref name="token"></interceptor-ref>
            <interceptor-ref name="defaultStack"></interceptor-ref>
        </action>

增加三行,struts2在检测到重复提交时会返回name为invalid.token的结果,需要加上拦截器。

提交页面就不贴了,都一样。在Action中,struts2会自动把上传的文件转为File对象,所以只需要从该对象读取流,写入到硬盘中即可。

package com.struts2;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionSupport;

public class FileUpload extends ActionSupport {
    private String username;
    //上传文件会自动转成File类型,所以原理简单,只要将file以流读取,写入到指定位置即可
    private File file;
    //文件名:字段名+FieName,如果用File对象的getName()方法,取到的是临时文件的文件名
    private String fileFileName;
    //文件类型:字段名+ContentType,返回MIME
    private String fileContentType;
    
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public File getFile() {
        return file;
    }
    public void setFile(File file) {
        this.file = file;
    }
    public String getFileFileName() {
        return fileFileName;
    }
    public void setFileFileName(String fileFileName) {
        this.fileFileName = fileFileName;
    }

    public String getFileContentType() {
        return fileContentType;
    }

    public void setFileContentType(String fileContentType) {
        this.fileContentType = fileContentType;
    }
    

    @Override
    public String execute() throws Exception {
        // 写入硬盘
        String path=ServletActionContext.getRequest().getRealPath("/upload");
        FileOutputStream fos=new FileOutputStream(new File(path,fileFileName));
        FileInputStream fis=new FileInputStream(file);
        byte[] bs=new byte[1024];
        int len=0;
        while((len=fis.read(bs,0,1024))>-1)
        {
            fos.write(bs, 0, len);
        }
        fis.close();
        fos.close();
        return SUCCESS;
    }
    
}

如果是多文件上传,只需修改file,fileFileName,fileContentType类型为List(包括setter和getter方法),在execute方法中,循环保存文件即可。 默认情况下,只允许上传2M以内的文件,可以在struts.properties(src目录下)中添加一行:

struts.multipart.maxSize=104857600

允许上传100M,也可以在struts.xml中使用constant标签,name为struts.multipart.maxSize,value为104857600

redirectAction用于重定向到另一个Action,可以传递参数,但只能传字符串(参数加在地址后,GET请求)。 struts.xml配置:

<action name="login" class="com.struts2.LoginAction">
    <result name="success" type="redirectAction">
        <param name="actionName">action1</param>
        <param name="username">${user.username}</param>
        <param name="password">${user.password}</param>
    </result>
       <result name="input">/login.jsp</result>
</action>
<action name="action1" class="com.struts2.Action1">
    <result name="success">/1.jsp</result>
</action>

当LoginAction返回success时,重定向到Action1,并加上username和password参数,两参数值来源于LoginAction的user属性

HttpServletRequest request=ServletActionContext.getRequest();
HttpSession session=request.getSession();
HttpServletResponse response=ServletActionContext.getResponse();
ServletContext application=ServletActionContext.getServletContext();

模型驱动,与之前的属性驱动区别在于把所有的属性封装在一个JavaBean(模型)中,当用户提交数据时,自动调用模型属性的setter方法设置属性值,缺点在于没有属性驱动灵活。 User.java(模型):

package com.bean;

public class User {
    private String username;
    private String password;
    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;
    }
    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return this.username+" "+this.password;
    }
}

Action除了继承ActionSupport还要实现ModelDriven接口,该接口定义了一个getModel()方法,返回经过赋值的模型实例:

package com.struts2;

import com.bean.User;
import com.impl.LoginServiceImpl;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
import com.service.LoginService;

public class LoginAction extends ActionSupport implements ModelDriven {

    public User user=new User();
    private LoginService loginService=new LoginServiceImpl();
    //LoginService是登录的
    /**
     * getModel方法自动将提交的数据给user属性赋值
     */
    @Override
    public User getModel() {
        // TODO Auto-generated method stub
        return user;
    }
    
    public String execute() throws Exception
    {
        if(loginService.isLogin(user))
        {
            return SUCCESS;
        }
        this.addActionError("用户名或密码错误");
        return INPUT;
    }
}

struts.xml配置与原属性驱动无异。

接上文,,上文直接把出错信息在xml里写死了,如果你的应用是多语言的,就没办法了。可以把错误信息写在多个不同的properties文件中,每个properties文件对应一种语言,文件内key相同,然后在validation.xml文件中的message,key属性指定key,struts2会自动根据客户端语言加载相应的properties文件。 下面以英文,中文两种语言为例: 在Action所在包下建立package_en_US.properties和package_zh_CN.properties两文件,对应英文和中文。内容分别如下:

username.invalid=username invalid\!\!

username.invalid=\u7528\u6237\u540D\u4E0D\u80FD\u4E3A\u7A7A

下面的是”用户名不能为空”的unicode码。 在LoginAction-validation.xml中,把原来的

        <field-validator type="requiredstring">
            <message>用户名不能为空</message>
        </field-validator> 

修改为:

        <field-validator type="requiredstring">
            <message key="username.invalid"></message>
        </field-validator> 

最后修改浏览器语言测试。