struts是一个Java中基于MVC设计模式的WEB应用程序框架。在吸收struts1与webwork的基础上发展而来的新框架。其工作原理如下图所示:
新建一个Java web项目后首先引入所需的jar包,从struts官网https://struts.apache.org/download.cgi下载所需要的依赖包,选择最新版本2.5.22的All dependencies包,解压得到多个jar包,从其中选择如下几个必要的拷贝到项目lib文件夹下,并添加到依赖。项目的目录结构如下
接着在web.xml文件中配置struts2过滤器StrutsPrepareAndExecuteFilter,对所有的路径进行过滤
struts2
org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter
struts2
/*
接着定义Action类,在src目录下新建action.FirstAction,继承自ActionSupport类,
package action;
import com.opensymphony.xwork2.ActionSupport;
public class FirstAction extends ActionSupport {
@Override
public String execute() throws Exception {
System.out.println("Action执行");
return SUCCESS; //返回字符串SUCCESS
}
}
在src目录下创建struts.xml文件,该文件是struts2的核心配置文件,完成对action的配置,以及对应的result定义等功能。
此外可以使用
/success.jsp
启动项目访问firstaction,在控制台输出“Action执行”并且跳转到success.jsp页面,这样一个基本的struts项目便运行了起来。
Http请求来到通过过滤器来到action,执行操作后在返回结果,那么如何获取Request与Response对象呢?Struts提供了三种方法
第一种是通过实现相应的Aware接口,在接口的setXxx()方法中获取request、response和context对象。进而通过各自对应的方法设置和获取属性。这种方法与Servlet API严重耦合,不推荐使用
public class FirstAction extends ActionSupport
implements ServletRequestAware, ServletResponseAware, ServletContextAware { //实现相应接口
HttpServletRequest request;
HttpServletResponse response;
ServletContext application;
@Override
public void setServletRequest(HttpServletRequest httpServletRequest) {
this.request=httpServletRequest; //获取request对象
}
@Override
public void setServletResponse(HttpServletResponse httpServletResponse) {
this.response=httpServletResponse; //获取response对象
}
@Override
public void setServletContext(ServletContext servletContext) {
this.application=servletContext; //获取application对象
}
@Override
public String execute() throws Exception {
request.setAttribute("key1","value1"); //设置属性
System.out.println(request.getAttribute("key1")); //获取属性
return SUCCESS;
}
}
第二种方法是通过ServletActionContext类中的静态方法getXxx(),得到真正的Servlet相关Api。之后再以request、session、application对象的setAttribute、getAttribute方法设置和获取属性
HttpServletRequest request= ServletActionContext.getRequest(); //获取request对象
HttpSession session=request.getSession(); //获取session对象
ServletContext context=ServletActionContext.getServletContext();//获取全局application对象
session.setAttribute("key3","value3"); //设置属性值
System.out.println(session.getAttribute("key3")); //获取属性值
第三种是通过ActionContext类访问Servlet API,Struts2将Servlet对象重新使用Map集合进行了封装,可以直接通过Map集合操作application、session、request对象中的共享数据,存取值比较方便,是推荐使用的一种方式。通过ActionContext对象可以直接调用get/put方法对request中保存的对象进行获取和设置。通过getApplication()、getSession()、getParameters()可获取application对象、session对象与request请求中的parameter参数。由于这种方法获取的不是真正的Servlet API对象,其返回的数据是Map类型,因此可以通过Map的put/get方法对数据进行设置和获取。
ActionContext actionContext=ActionContext.getContext(); //创建Context对象
actionContext.put("key1","value1"); //向request中存储对象
System.out.println(actionContext.get("key1")); //获取request中存储的信息
Map application=actionContext.getApplication(); //获取application
application.put("key2","value2"); //向application对象存对象
System.out.println(application.get("key2")); //从application中取出对象
由于struts的package可以嵌套,所以访问的url路径也可以有多层,例如一个package1下有一个子包subpackage,其中包含firstaction,则其访问路径为http://localhost:8080/Struts2Demo/package1/subpackage/firstaction。如果在subpackage中没有找到firstaction,则会退回到上一级package1中查找,如果仍没有再返回上一级根目录下查找,如果仍没有找到才会报错404。
如果希望针对不同的url调用相同的action中的不同方法,则需要动态调用。第一种可以在
/login.jsp
但是如果每一个url都要定义一个action,太过重复和麻烦,可以使用通配符的形式根据不同的url定义不同的调用方法。例如定义action的name为"hello_*"用*代表通配符并用_和其他的url隔开,在method中用{1}代表通配符第一个位置。当我们访问的url为hello_login时,{1}代表login,则对应执行FirstAction中的login方法,login()返回字符串为login,在
regex:.*
/{1}.jsp
public class FirstAction extends ActionSupport{
public String login(){
return "login";
}
public String logout(){
return "logout";
}
}
通过default-action-ref标签可以指定package中默认访问的路径,即除了已定义的正常路径之外的其他请求都会跳转到该action。例如定义默认action为error,在action为error中定义跳转到error.jsp页面
/error.jsp
......
action的默认后缀为.action,在访问url时这个后缀可写可不写。在struts配置文件中,通过常量struts.action.extension为访问页面添加后缀,例如下面设置后缀为html,则所有的url结尾必须加上.html才能正常访问。
除了使用request接收请求中的参数外,还可以通过如下三种方法接收前端页面中的参数
第一种是直接通过Action属性来接收。在Action类中定义属性变量并实现其get/set方法,然后即可在action方法中直接使用属性变量。例如前端表单提交有name为username、password两个参数,在LoginAction中定义相应的变量以及get/set方法,然后在login()方法中就可以直接得到两个参数值了。
public class LoginAction extends ActionSupport {
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;
}
public String login(){
System.out.println(username+":"+password); //直接通过Action属性获取参数
return SUCCESS;
}
}
直接将属性放在action类中不利于对象的封装和管理,第二种方法DomainModel就是采用对象来接收参数。新建一个JavaBean类User用于储存username、password属性,然后在action中实例化一个user对象并定义set/get方法。在前端表单中设置属性name时添加对象名user,即为user.username、user.password。这样属性传到action文件中会自动完成对象赋值,可以直接使用user对象。
//JavaBean类User
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;
}
}
//Action中使用user对象接收参数
public class LoginAction extends ActionSupport {
private User user; //新建user对象
public User getUser() { //设置get方法
return user;
}
public void setUser(User user) {
this.user = user;
}
public String login(){ //通过User对象获取参数
System.out.println(user.getUsername()+":"+user.getPassword());
return SUCCESS;
}
}
第三种方法是使用ModelDriven接口,Action类实现ModelDriven接口并实现其getModel()方法获取user对象。这样就不用定义user对象的get/set方法,而且前端页面表单属性不必添加user对象前缀,降低了耦合性,推荐使用这种方法。
public class LoginAction extends ActionSupport implements ModelDriven { //实现接口
private User user=new User(); //需要实例化user对象
@Override
public User getModel() { //实现接口方法
return user;
}
public String login(){ //可以使用user对象获取参数
System.out.println(user.getUsername()+":"+user.getPassword());
return SUCCESS;
}
}
在
当前端传递的参数与action中接收的变量不能匹配,action会自动返回INPUT,也可以手动返回INPUT。例如对接收到username进行判断,若为空则添加FiledError并返回INPUT。可以在前端页面通过struts-tags显示fielderro标签,当action抛出FieldError并返回INPUT再次跳转回login.jsp时,fielderror标签就会显示FieldError中的错误信息。
public class LoginAction extends ActionSupport implements ModelDriven {
private User user=new User();
@Override
public User getModel() {
return user;
}
public String login(){
if (user.getUsername()==null||user.getUsername().isEmpty()) {
this.addFieldError("username", "用户名不能为空"); //添加错误信息
return INPUT;
}
System.out.println(user.getUsername()+":"+user.getPassword());
return SUCCESS;
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
登录界面
如下所示,struts-tags显示提示信息:
根据
/404.jsp
......
在struts原理图中可以看到拦截器(Interceptor)作用于请求到达Action之前,并且在返回Result结果后再反向经过拦截器。多个拦截器构成了拦截器栈,拦截器栈的执行是有顺序的,就像栈一样先进后出。所以我们可以使用拦截器对请求到达action之前进行预处理操作,并且在返回结果前执行进一步操作。
第一种方法是实现Interceptor接口。该接口有三个方法:
第二种方法是继承AbstractInterceptor类,该类提供了init()与destroy()方法的空实现,所以我们只需要实现intercept()方法即可,因此这种方法较为常用。如下所示定义一个拦截器对用户登录进行验证,如果session中username不为空,则拦截器放行到下一层,否则返回字符串“login”,跳转到登录界面。
public class AuthLog extends AbstractInterceptor {
@Override
public String intercept(ActionInvocation actionInvocation) throws Exception {
System.out.println("Action执行之前");
ActionContext actionContext=ActionContext.getContext();
Map session =actionContext.getSession();
if (session.get("username")!=null){
//执行下一个拦截器,若为最后一个则执行目标Action,然后返回结果
String result=actionInvocation.invoke();
System.out.println("执行Action之后");
//将结果返回上一层
return result;
}else {
return "login";
}
}
}
首先在struts.xml文件中注册拦截器,然后再要使用的action中引用拦截器。也可以将多个拦截器组合为一个拦截器栈。
如下所示首先定义一个拦截器authlog,指向AuthLog类。之后和默认拦截器组合为myStack拦截器栈。之后定义action为personalPage并为其添加拦截器myStack。当用户访问该action时,首先会经过myStack拦截器栈中的defaultStack,然后再经过authlog拦截器,在其中完成登陆验证,如果验证通过则返回success.jsp,否则返回login.jsp。
/success.jsp
/login.jsp
struts中内置了许多拦截器来实现其功能:
这些拦截器都在struts-core.jar包中struts-default.xml文件中进行了注册,并且集中打包为默认拦截器栈