SSH框架开发之控制层(Struts2)

前言:###

很多Java开发人员觉得SSH框架开发没有SSM框架开发那么得心应手,从入门程度来讲的确是这样,那么试问你是愿意用很多人都会的,还是愿意用不太好掌握的、用的人相对较少的呢?
我们必须清楚,这个社会就是这样,“可替代性越普遍你被别人替代的可能就越高”。这,是不争的事实。
如果你想了解SSH框架,不妨先看看我这篇关于Struts2框架文章;如果你还是一名在校软件工程系的大学生,那么想不想面试的时候加分呢?那么,请慢慢看下去:

配图.jpg

步骤:

一、Struts和Struts2的背景

二、Struts2的工作原理

三、案例:利用Struts2接收页面参数

四、Struts2拦截器


一、Struts和Struts2的背景

PS:看到这里的小伙伴们,请务必抱着好奇心继续看下去哦!

什么是Struts呢?
我们这样认为:Struts是流行和成熟的基于MVC设计模式的Web应用程序框架,能够帮助我们减少用MVC设计模式来开发Web应用的时间。

可能有些小伙伴会问了,什么是MVC呢?如下:

MVC模型.png

那什么又是Struts2呢?
我这样理解的:Struts2是结合Struts1和webwork的一个升级版,在稳定性以及性能等各个方面都比Struts1和webwork好,可谓集两者之所长。

二、Struts2的工作原理

struts2的工作原理.png

上面这幅图是Struts2的工作原理图,Struts2是在我们的web.xml中进行配置的一个过滤器,当我们web项目启动的时候,过滤器就会生效。

首先,用户通过HttpServletRequest用户请求,经过一系列的Struts2核心的过滤器向下执行。

①.ActionContextCleanUp是其中的一个可选的过滤器,非必须的哦;
②.Other filters(SiteMesh,etc)过滤器主要是用于与其他的框架进行集成;③.FilterDispatcher也是Struts2的一个核心过滤器,我们需要知道的是在Struts2.1.2之前是FilterDispatcher,而在Struts2.1.3版本之上被改为StrutsPrepareAndExecuteFilter。

可能有人要问了,为什么FilterDispatcher会被StrutsPrepareAndExecuteFilter替代呢?

举个例子:假如我们现在想写一个过滤器,我们往往会放在Struts2核心的过滤器的顶端,也就是在ActionContextCleanUp执行之前,写我们自己的Filter;假如说我们需要在Struts2拦截之后再写过滤器,也就是说我在执行Action之前,编写过滤器。通过FilterDisoatcher是做不到的, 而升级版的StrutsPrepareAndExecuteFilter就可以做到在执行Action之前,添加我们自己的过滤器。


然后,如果后缀名为.action的就会进入ActionMapper,请求并在ActionMapper查找我们这个请求有没有指定的一个Action,假如说有的话,就返回上一个过滤器并向左边走。当走到ActionProxy的时候,ActionProxy就可以通过ConfigurationManager读取到struts.xml,并找到具体的Action类,又通过ActionProxy的代理,创建我们action的一个反向的实例。
再然后,经过一系列的拦截器之前,执行到我们的Action,返回到Result(也是一个字符串对象),这个字符串对应的就是我们的视图,也就是图上的Template,包括jsp,FreeMarker等等。再经过一系列的拦截器之后,通过HttpServletResponse返回到HttpServletRequest中,也就是返回到用户的实例进行显示。

PS:关于Struts2拦截器我会在后面介绍,请大家务必理解并牢记Struts2的工作原理,不论是在工作还是面试都会有所涉及哦!假如你面试的公司正在使用SSH框架,那恭喜你,在面试官面前认可度+1了哦。

三、案例:利用Struts2接收页面参数

首先,我们分别讨论使用三种方式接受参数:
1.使用Action的属性接收参数
2.使用DomainModel接收参数
3.使用ModelDriven接收参数

准备工作:

success.jsp:

<%@ page language="java" import="java.util.*" contentType="text/html; charset=utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    <title>My JSP 'success.jsp' starting page</title>
  </head>

  <body>
    This is success.jsp. <br>
  </body>
</html>

index.jsp:

<%@ page language="java" import="java.util.*" contentType="text/html; charset=utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    <title>My JSP 'index.jsp' starting page</title>
  </head>
  
  <body>
    <form action="Loginaction.action" method="post">
        用户名:<input type="text" name="username" /><br>
        密码:<input type="password" name="password" /><br>
        <input type="submit" value="提交" />
    </form>
  </body>
</html>

配置struts.xml:

    <action name="Loginaction" method="login" class="com.action.IndexAction">
            <result>/success.jsp</result>
    </action>

1.使用Action的属性接收参数
首先我们新建一个IndexAction.java并继承ActionSupport:

public class IndexAction  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);
        return SUCCESS;
    }
}

然后启动tomact,运行程序,我们可以看到后台有输出,并且页面成功跳转。这说明了我们已经能够通过这种方式获取参数了,但是假如我们有些页面非常的大,有几十个甚至上百个,那么这个时候我们是不是要建上百个属性呢?所以这种方法对于我们开发是非常复杂的,当然也不利于维护。

曾经我们说过,Java是一种面向对象的语言,那么我们能不能把这些属性放在一个对象里面,来实现各方面的开发呢?答案是肯定可以的。这就牵扯到我们的第二种方式:

2.使用DomainModel接收参数
修改后的IndexAction.java:

public class IndexAction  extends ActionSupport{
    private User user;

        public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }

    public String login(){
        System.out.println(user.getUsername());
        return SUCCESS;
    }
}

独立出来的用户(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;
    }
}

现在我们停下来想一下,假如说我们现在什么都不改,那么我们通过jsp中的提交方式能不能自动往private User user;里面传递参数呢?答案是肯定不行的,因为假如说我们有多个对象,每个对象都有这样的参数的话,它传递到private User user;里面的参数都赋值的话,就肯定会乱。那么我们怎么指定呢?我们可以修改index.jsp里面的属性名称:

<form action="Loginaction.action" method="post">
用户名:<input type="text" name="user.username" /><br>
密码:<input type="password" name="user.password" /><br>
<input type="submit" value="提交" />
</form>

这样就代表我们的username和password是出入到private User user;这个参数对象里面的,而不是其他的参数对象里面。

然后启动tomact,运行程序,我们可以看到后台有输出,并且页面成功跳转。说明这种方式也是可行的。

3.使用ModelDriven接收参数
修改后的IndexAction.java:

public class IndexAction  extends ActionSupport implements ModelDriven<User>{
    private User user=new User();//必须实例化,并且去掉getters和setters方法

    public String login(){
        System.out.println(user.getUsername());
        return SUCCESS;
    }
    @Override
    public User getModel() {
        return user;
    }
}

然后启动tomact,运行程序,我们可以看到后台有输出,并且页面成功跳转。

如果说我们传入的参数是一个集合怎么办呢?我们可以这样:
向User.java里面添加一个成员变量,并实现getters和setters方法:

    private List<String> booklist;
    public List<String> getBooklist() {
        return booklist;
    }
    public void setBooklist(List<String> booklist) {
        this.booklist = booklist;
    }

修改后的index.jsp:

        <form action="Loginaction.action" method="post">
        用户名:<input type="text" name="username" /><br>
        密码:<input type="password" name="password" /><br>
        书籍1:<input type="text" name="booklist[0]" /><br>
        书籍2:<input type="text" name="booklist[1]" /><br>
        <input type="submit" value="提交" />
        </form>

修改后的IndexAction.java:

public class IndexAction  extends ActionSupport implements ModelDriven<User>{
    private User user=new User();

    public String login(){
        System.out.println(user.getUsername());
                System.out.println(user.getBookList().get(0));
                System.out.println(user.getBookList().get(1));
        return SUCCESS;
    }
    @Override
    public User getModel() {
        return user;
    }
}

再次启动tomcat并运行程序,控制台成功打印输出。那么如果private List<String> booklist→private List<User> booklist呢?也就是说传入的是一个对象呢?怎么修改index.jsp和IndexAction.java来测试是否成功呢?这个问题就留给读者们思考啦。

经过三种传参的对比,在实际应用中我们一般使用ModelDriven接收参数,为什么呢?因为低耦合高内聚啊。
PS:别问我什么是低耦合高内聚是什么,我会满脸黑线的...

四、Struts2拦截器

什么是拦截器?
拦截器是Struts2里面非常重要的一个概念,是Struts2的核心功能实现。拦截器方法在Action执行之前或者之后执行,这里我们举个例子,比如说在数据转移、类型转换、数据校验之前或者之后做一些处理操作。

试想一下,如果多个拦截器组合在一起呢?这就是我们常说的拦截器栈
①.从结构上看,拦截器栈相当于多个拦截器的组合。
②.从功能上看,拦截器栈也是拦截器,在用法上基本一致。

拦截器的工作原理:
其实,也就一句话,拦截器的执行过程是一个递归的过程。怎么理解呢?学过数学的应该都知道什么是递归思想吧,也就是说:从最外层的拦截器开始依次向内部(Action)深入,得到结果集(Result)并进行视图(jsp)的匹配,再依次经过从内向外的拦截器。

案例<使用拦截器实现权限的控制>:

准备工作:
在/WEB-INF/page/目录下创建manager.jsp:

<html>
  <head>
    <base href="<%=basePath%>">
    <title>My JSP 'manager.jsp' starting page</title>
  </head>
  
  <body>
    这是一个后台管理页面,只有已登录的用户才能访问!
  </body>
</html>

login.jsp:

<html>
  <head>
    <base href="<%=basePath%>">
    <title>My JSP 'login.jsp' starting page</title>
  </head>
  
  <body>
    <h2>用户登录</h2>
    <form action="login.action" method="post">
    用户名:<input type="text" name="username" /><br/>
    密码:<input type="password" name="password" /><br/>
    <input type="submit" value="登录" />
    </form>
  </body>
</html>

LoginAction.java:

public class LoginAction extends ActionSupport implements SessionAware {
    private String username;//用户名
    private String password;//密码
    private Map<String, Object> session;//通过实现SessionAware接口,定义session获取jsp页面的信息

    @Override
    public void setSession(Map<String, Object> session) {
        this.session=session;
    }

    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() {
        if ("admin".equals(username) && "admin".equals(password)) {
            session.put("loginInfo", username);
            return SUCCESS;
        }else {
            session.put("loginError", "用户名或者密码不正确!");
            return ERROR;
        }
    }
}

通过这个Action,我们可以配置Struts.xml了:

<!-- 通过此action访问后台管理页面,需要判断用户是否已经登录,如果没有登录跳转到登陆界面 -->
        <action name="auth">
            <result>/WEB-INF/page/manager.jsp</result>
        </action>

        <action name="login" class="com.action.LoginAction" method="login">
            <result name="success">/WEB-INF/page/manager.jsp</result>
            <result name="error">/login.jsp</result>
        </action>

配置好Struts.xml以后,我们需要在用户登录界面配置用户登录失败的信息:
修改后的login.jsp:

<html>
  <head>
    <base href="<%=basePath%>">
    <title>My JSP 'login.jsp' starting page</title>
  </head>
  
  <body>
    <h2>用户登录</h2>
    ${loginError }
    <form action="login.action" method="post">
    用户名:<input type="text" name="username" /><br/>
    密码:<input type="password" name="password" /><br/>
    <input type="submit" value="登录" />
    </form>
  </body>
</html>

接下来开始编写我们的拦截器:
AuthInterceptor.java:

public class AuthInterceptor extends AbstractInterceptor {

    @Override
    public String intercept(ActionInvocation invocation) throws Exception {
        /*
         * 首先我们要判断用户是否已经登录,那么就需要得到前面定义的session,
         * 怎么得到session呢?可以通过ActionContext的getContext()方法获取上下文。
         */
        ActionContext context = ActionContext.getContext();
        Map<String, Object> session = context.getSession();
        if (session.get("loginInfo") != null) {//说明用户已登录
            String result = invocation.invoke();
            return result;
        } else {//说明用户未登录
            return "login";
        }
    }
}

然后还得修改Struts.xml:

<!-- 注册拦截器 -->
    <interceptors>
        <interceptor name="auth" class="com.interceptor.AuthInterceptor"></interceptor>
        <!-- 自定义拦截器栈mystack,组合了默认拦截器defaultStack和auth -->
        <interceptor-stack name="mystack">
            <interceptor-ref name="defaultStack"></interceptor-ref>
            <interceptor-ref name="auth"></interceptor-ref>
        </interceptor-stack>
    </interceptors>
    
    <!-- 通过此action访问后台管理页面,需要判断用户是否已经登录,如果没有登录跳转到登陆界面 -->
        <action name="auth">
            <result>/WEB-INF/page/manager.jsp</result>
            <!-- 如果AuthInterceptor里面返回的是login字符串,返回登陆界面 -->
            <result name="login">/login.jsp</result>
            <!-- 只需要引用自定义拦截器栈就可以了 -->
            <interceptor-ref name="mystack"></interceptor-ref>
        </action>
        
        <action name="login" class="com.action.LoginAction" method="login">
            <result name="success">/WEB-INF/page/manager.jsp</result>
            <result name="error">/login.jsp</result>
        </action>

这里我们需要明白的是,当我们手动添加了一个拦截器,默认的拦截器将不会生效,所以我们可以自定义一个拦截器栈将这两个拦截器放入里面。就实际项目而言,会有十几个或者更多的拦截器,如果不使用拦截器栈,那么每一个拦截器你都需要在action标签里面配置,的确是一件很麻烦的事。

这里,我们这个权限校验也就完成了。已经能够完成登录的用户可以查看后台信息,未登录的用户是不可以查看后台信息的。

如果大家觉得我写的对你有帮助,请顺手点个赞支持一下呗;如果大家觉得我有写的不对的地方,欢迎大家多多发言。谢谢!

转载请注明作者及文章出处噢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容