Struts2学习笔记_拦截器栈&标签库

Struts2_拦截器栈&标签库

一、拦截器栈

1. 拦截器

Java里的拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行,同时也提供了一种可以提取action中可重用部分的方式。在AOP(Aspect-Oriented Programming)中拦截器用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。

几个关键字:

  • 拦截Action:Struts2 拦截器在访问某个 Action 方法之前或之后实施拦截,(在action之前调用的称之为前置拦截器,之后也称之为后置拦截)
  • 阻止执行:由于拦截器可以在Action执行之前执行,那么如果不想让某个action执行,可以阻断其执行。
  • 可重用部分:重复的代码抽取出来形成拦截器,拦截器是可插拔的。
  • AOP(Aspect-Oriented Programming):一种编程思想,该思想的简单理解就是:在不改变原来代码的情况下,对原来的代码功能进行增强(增加或减少)。它的通常是采用代理的机制实现(代理对目标代码进行控制和增强 )

问题: 拦截器和过滤器的区别?

过滤器(filter)是javaweb阶段的知识点,拦截服务器端所有资源的访问 (静态、 动态)。在web.xml配置。

拦截器(Interceptor),在struts2框架内部,只对Action访问进行拦截 (默认拦截器 ,无法拦截静态web资源, 
如果要拦截静态资源,比如html、jsp,可以将静态web资源放入WEB-INF\xxx, 通过Action间接访问)

2. 拦截器栈

拦截器栈(Interceptor Stack):是将拦截器按照一定的顺序连接成一条链后的一个称呼。

在访问被拦截的方法时,拦截器链的中拦截器就会按照其之前定义的顺序被一次调用。
Struts2 将拦截器定义拦截器栈,作用于目标Action,拦截器栈的名字为defaultStack

defaultStackStruts2默认执行的拦截器栈。

img21.png
2.1 Struts2运行原理的底层分析(了解)
img03.png
1. 当web.xml被加载后,经过Struts2的前端控制器,会进入StrutsPrepareAndExecuteFilter,它会调用init
方法进行初始化,准备Struts2的相关环境,加载相应的配置文件,(6个,包括struts.xml)--它会将所有action的name
都加载到Struts2的环境中。

2. 当有请求访问时,前端控制器拦截访问doFilter方法,ActionMapping mapping = prepare.findActionMapping(request, response, true);
会在查找要访问的action的name是否有配置,如果没有配置,直接过滤拦截,忽略后面所有的拦截器与action的执行,
并告知action映射不存在,不往下运行。

如果存在,execute.executeAction(request, response, mapping);准备执行action

3. 准备执行action :
 ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
                namespace, name, method, extraContext, true, false);

生成action的代理对象---增强---使用过滤器进行增强

继续向下走 : proxy.execute();

invocation.invoke();

ActionInvocation增强器里面的invoke方法,判断if (interceptors.hasNext())-配置的那些拦截器有没有
执行完,如果没有执行完,就执行:
resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this)
这个具体的拦截器,执行之后 return invocation.invoke();,返回到原来的调用对象,原来的
调用对象又会自动调用invoke方法。--(链式递归)递归调用。

4. 当拦截器都执行完成之后(增强完成之后),resultCode = invokeActionOnly();让它去执行具体的Action:
invokeAction(getAction(), proxy.getConfig());返回结果集视图。

3. 自定义拦截器

![Upload img22.png failed. Please try again.]

![Upload img23.png failed. Please try again.]

img24.png
  • 程序中每个拦截器 都必须实现 Interceptor 接口
  • 也可以继承 AbstractInterceptor 只需要覆盖 intercept 方法
  • 也可以继承 MethodFilterInterceptor ,只需要覆盖 doIntercept 方法
    可以设置哪些方法 不进行过滤拦截(功能最强,推荐)

3.1 实现自定义拦截器

编写测试的Action(被拦截的Action):

@Override
//通过拦截器增强这个Action
public String execute() throws Exception {
    System.out.println("TestAction执行了................");
    return NONE;
}

编写一个自定义拦截器:

编写一个类,继承MethodFilterInterceptor,实现doFilter方法。
在doFilter方法内部编写要对其增强的代码。

public class MyInterceptor extends MethodFilterInterceptor {

    @Override
    //目标 :使用拦截器对Action进行增强
    protected String doIntercept(ActionInvocation invocation) throws Exception {
        
        System.out.println("拦截器执行了,增强Action方法.................");
        //增强后,放行将执行权交给下个拦截器或Action
        return invocation.invoke();
    }
}

注册拦截器

定义全局拦截器:

写在package内,action前

<interceptors>
    <!--注册自定义拦截器 -->
    <interceptor name="myInterceptor" class="com.itdream.struts2.interceptor.MyInterceptor" />
    
    <!-- 注册自定义拦截器栈 -->
    <interceptor-stack name="myStack">
        <!-- 先执行自己的拦截器(顺序看需求) -->
        <interceptor-ref name="myInterceptor" />
        <!-- 执行默认的拦截器栈 -->
        <interceptor-ref name="defaultStack" />
    </interceptor-stack>
</interceptors>

<!-- 自定义拦截器栈覆盖Struts2的默认拦截栈,使其生效 -->
<default-interceptor-ref name="myStack" />

-------------------------------------------------------------------------

继承MethodFilterInterceptor有一个强大的功能,可以设置哪些Action不进行拦截。
如何使用 :

    <interceptor-ref name="myInterceptor">
        <!-- 排除哪些方法,不拦截它们,多个方法间用逗号隔开 -->
        <param name="excludeMethods">execute</param>
        <!-- 包含哪些方法,只有这些方法才被拦截,多个方法间用逗号隔开.与上面的排除配置互斥 -->
        <!-- <param name="includeMethods">execute</param> -->
    </interceptor-ref>  

定义局部拦截器 :

首先也要在package内,action注册自拦截器:

<interceptors>
    <!--注册自定义拦截器 -->
    <interceptor name="myInterceptor" class="com.itdream.struts2.interceptor.MyInterceptor" />
</interceptors>

然后在指定action内局部使用该拦截器:

<!-- 
    局部拦截器:写在指定action内部,只对这个action有效
    局部拦截器会覆盖全局配置
 -->
 <interceptor-ref name="myInterceptor">
 <interceptor-ref name="defaultStack"/>

结论:我们在自定义拦截器后,需要将其进行注册,并且使用它.使用时都不会抛弃Struts2的拦截器栈。

实际上,我们自定义拦截器后基本上都是注册全局拦截器让其对所有的Action生效。

二、 自定义拦截器,拦截未登陆用户访问

拦截器只能拦截访问Action请求,不能拦截静态Web资源(jsp,html等),如果要想拦截它们,使用Action间接访问这些资源即可。

目标:用户在未登录的情况下,不允许访问系统的功能,让其跳转到用户登录页面。

前提:所有的页面请求都经过Action,自定义拦截器进行Action请求拦截,在执行Action之前进行判断操作。

1. 修改新增客户,让其通过Action访问add.jsp

menu.jsp :
    href="${pageContext.request.contextPath }/customer_showAdd.action"

CustomerAction动作类:
    //跳转添加客户页面
    public String showAdd() {
        return "addjsp";
    }

struts.xml配置结果集视图跳转页面:
    <!-- 跳转添加客户页面 -->
    <result name="addjsp" type="redirect">/jsp/customer/add.jsp</result>

2. 完成用户登录功能

  • 创建用户的数据库表user
    • 默认给定用户名和密码
  • 创建用户持久化类User
  • 完成用户持久化类与数据库表user的映射文件mapping
  • 用户在页面输入用户名密码,数据库校验是否存在
  • 登陆成功跳转首页,登陆失败,跳转回登陆页面,告知提示信息
2.1 ORM关系创建(省略)
2.2 修改登录页面login.jsp
<FORM id=form1 name=form1 method="post" action="${pageContext.request.contextPath }/user_login.action">

表单中name属性修改与模型类的属性一致,用于Struts2框架的拦截器封装数据。(省略)

struts.xml配置访问的Action:
<action name="user_*" class="com.itdream.crm.web.action.UserAction" method="{1}"></action>
2.3 Action处理请求,查询数据库
Action动作类处理请求:

public class UserAction extends ActionSupport implements ModelDriven<User> {

    // 创建一个模型对象用于封装参数
    private User user = new User();

    @Override
    // 提供getter方法Struts2框架获取model对象
    public User getModel() {
        return user;
    }

    // 用户登录
    public String login() {
        // 获取参数(模型驱动)
        // 调用业务层查询是否有符合账号密码的User存在
        UserService service = new UserServiceImpl();
        User loginUser = service.findUserByUsernameAndPassword(user.getUsername(), user.getPassword());

        if (loginUser != null) { // 登陆成功
            // 将User对象存入Session域中
            ServletActionContext.getRequest().getSession().setAttribute("user", loginUser);
            // 跳转首页
            return "loginSuccess";
        }
        // 登陆失败
        addActionError("用户名或密码不正确,请重新输入");
        return LOGIN;
    }
}

---------------------------------------------------------------------------------

Service层:

public class UserServiceImpl implements UserService {

    @Override
    //根据账号密码查询用户
    public User findUserByUsernameAndPassword(String username, String password) {
        //获取Session对象
        Session session = HibernateUtils.getCurrentSession();
        //开启事务
        Transaction transaction = session.beginTransaction();
        User user = null;
        try {
            //业务逻辑
            //调用dao层
            UserDAO dao = new UserDAOImpl();
            user = dao.findUserByUsernameAndPassword(username,password);
            
        } catch (Exception e) {
            //回滚事务
            transaction.rollback();
            e.printStackTrace();
        }finally {
            //提交事务
            transaction.commit();
        }
        return user;
    }
}

--------------------------------------------------------------------------

dao层:

public class UserDAOImpl implements UserDAO {

    @Override
    //根据用户名和密码查询User
    public User findUserByUsernameAndPassword(String username, String password) {
        //获取Session对象
        Session session = HibernateUtils.getCurrentSession();
        
        //使用Criteria查询(面向对象的无语句查询)
        Criteria criteria = session.createCriteria(User.class);
        System.out.println("username:"+username);
        System.out.println("password:"+password);
        //添加限制条件
        criteria.add(Restrictions.eq("username", username));
        criteria.add(Restrictions.eq("password", password));
        //执行查询
        User user = (User) criteria.uniqueResult();
        return user;
    }
}
2.4 自定义登陆拦截器
用户未登录的情况下访问服务器的资源时,跳转到登陆页面,并提示用户登录。
1. 继承MethodFilterInterceptor完成自定义拦截器。
2. 将需要写回的信息放入ActionError集合中在jsp页面回显。
3. 最后需要放行,invocation.invoke()。

自定义拦截器,拦截Action请求:

public class LoginInterceptor extends MethodFilterInterceptor {

    @Override
    //拦截未登录用户
    protected String doIntercept(ActionInvocation invocation) throws Exception {
        
        //获取Session域中的user是否存在来判断是否登陆
        User user = (User) ServletActionContext.getRequest().getSession().getAttribute("user");
        
        if (user == null) { //如果未登陆,执行拦截,跳转到登录页面
            //使用Struts2的大管家获取拦截的Action,写回友好信息
            ActionSupport action = (ActionSupport) invocation.getAction();
            action.addActionError("对不起,您还没有登陆");
            
            //跳转到登陆页面
            return action.LOGIN;
        }
        
        //否则就放行,将执行权交到下一个拦截器或者Action
        return invocation.invoke();
    }
}


页面回显提示:
    先引入Struts2的标签库。
    <%@ taglib prefix="s" uri="/struts-tags" %>
    友好提示:
    <s:actionerror/>
2.5 struts2.xml最终版本
1. Action的结果集视图跳转对应页面。
    1. 这里配置了一个全局结果集"login",未登陆用户访问服务器资源/登陆失败,让其全跳回登陆页

2. 注册自定义拦截器,并使用自定义拦截器栈覆盖默认拦截器栈


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
    "http://struts.apache.org/dtds/struts-2.3.dtd">

<struts>
    <constant name="struts.devMode" value="true" />
    <!-- 简单样式 -->
    <constant name="struts.ui.theme" value="simple" />

    <package name="default" namespace="/" extends="struts-default">
    
        <!-- 自定义拦截器 -->
        <interceptors>
            <!-- 注册自定义拦截器 -->
            <interceptor name="loginInterceptor" class="com.itdream.crm.web.interceptor.LoginInterceptor"/>
            
            <!-- 注册自定义拦截器栈 -->
            <interceptor-stack name="myStack">
                <!-- 先执行登陆拦截,验证是否登陆需要拦截 -->
                <interceptor-ref name="loginInterceptor">
                    <!-- 如果是执行登录操作的方法,就不拦截.标签体内填写要排除的方法名 -->
                    <param name="excludeMethods">login</param>
                </interceptor-ref>
                <!-- 再执行Struts2默认拦截栈 -->
                <interceptor-ref name="defaultStack"/>
            </interceptor-stack>
        </interceptors>
        
        <!-- 使用自定义拦截器栈(自定义拦截器栈覆盖默认栈) -->
        <default-interceptor-ref name="myStack"/>
    
    
        <!-- 全局结果集,未登录用户访问任何页面都跳转login.jsp -->
        <global-results>
            <result name="login">/login.jsp</result>
        </global-results>
        
        <action name="customer_*" class="com.itdream.crm.web.action.CustomerAction"
            method="{1}">
            <!-- 跳转添加客户页面 -->
            <result name="addjsp" type="redirect">/jsp/customer/add.jsp</result>
            <result name="flushListCustomer" type="redirectAction">customer_list.action
            </result>
            <result name="listCustomer">/jsp/customer/list.jsp</result>
            <!-- 默认转发,传递customer数据 -->
            <result name="editjsp">/jsp/customer/edit.jsp</result>
        </action>

        <!-- 与User有关的action请求 -->
        <action name="user_*" class="com.itdream.crm.web.action.UserAction"
            method="{1}">
            <!-- 登陆成功 -->
            <result name="loginSuccess">/index.jsp</result>
        </action>
    </package>
</struts>

二、标签库

测试Struts2的标签库:
Struts.xml配置:
    <!-- 标签库测试 -->
    <action name="tag_*" class="com.itdream.struts2.taglib.TagAction" method="{1}">
        <result name="result">/result.jsp</result>
    </action>   

1. 通用(Generic)标签

1.1. <s:property/>标签

作用:将OGNL表达式的内容输出到页面

value:属性。接收OGNL表达式

default:属性, 如果OGNL表达式,取不到值,default设置显示默认值。默认为""

escapeHtml:属性, 是否对HTML标签转义输出 (默认是转义,可以关闭)

Action类:    

//测试property标签
public String property() {
    //模拟业务层返回的数据压入值栈,转发jsp页面接收
    // 获取值栈
    ValueStack valueStack = ActionContext.getContext().getValueStack();
    
    //压入root栈,匿名
    valueStack.push("push压入root栈");
    //压入root栈,有名字
    valueStack.set("msg", "set压入root栈");
    //压入map栈,匿名
    ActionContext.getContext().put("msg", "put压入map栈");

    //跳转页面(值栈和request的生命周期一样,采用转发方式)
    return "result";
}


result.jsp :

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

<h3>------------测试Property标签-------------------</h3>
<!-- 从值栈中取值 -->
<!-- 取压入root栈的匿名对象 -->
<s:property value="[1].top"/><br/>

<!-- 取压入root栈的有名字对象 
    1. 值栈的默认查找机制
    2. 索引获取值栈的对象,再通过key取值
    3. 神奇的request(el表达式,Struts2增强了request.getAttribute方法)。
        先到request域中查找,再使用值栈的默认查找机制到值栈中查找
-->
<s:property value="msg"/>|<s:property value="[0].top.msg"/>|${msg}<br/>

<!-- 取存入map栈的值
    1.可以使用值栈的默认查找机制取map栈的值,但如果root栈中有重名的key就取不到map栈的值
        因为值栈的默认查找机制默认先找root栈,root栈没有才查找map栈.
        这里因为root栈已经有msg了,就不能使用这种方法了
    2. 神奇的request,先找request,再走值栈默认查找
    3.使用#+key指定查找map栈的key,取出map栈的值
 -->
 <s:property value="#msg"/><br/>
 <hr/>

-----------------------------------------------------------------------------
1.2. <s:iterator/>标签

作用:遍历集合对象(可以是List、set和数组等),显示集合对象的数据。

遍历的过程:

每次遍历时,将遍历的值压入栈顶(匿名),并且在map栈中放入一个副本,key是var的变量名,map的value就是要遍历的值。
每次遍历完一个值,就将它从栈顶弹出,并删除map栈key为var变量名的键值对。

由于这种原理,因此取遍历的值有几种方式:

1. 直接使用栈顶取值.[0].top  
2. 使用值栈默认查找机制取map栈的值,key为var的变量名
3. 指定取map栈的值,#key
4. el表达式使用神奇的request(先找request,request没有就走值栈默认查找机制)

value:迭代的集合。支持OGNL表达式,如果没有设置该属性,则默认使用值栈栈顶的集合来迭代。(类似于jstl中c:foreach标签的items属性)

var:引用变量的名称,该变量是集合迭代时的子元素。

begin: 开始的数字

end: 结束的数字

status:引用迭代时的状态对象IteraterStatus实例(类似于varstatus),其有如下几个方法:

1. int getCount(),返回当前迭代了几个元素;
2. int getIndex(),返回当前迭代元素的索引;
3. Boolean isEvent(),偶数
4. boolean isOdd(),奇数
5. boolean isFirst(),第一个
6. boolean isLast(),最后一个


Action 类:

// 测试iterator标签遍历集合
List<User> users = new ArrayList<>();
// username和password
User user1 = new User("tom", "123");
User user2 = new User("jerry", "123");
User user3 = new User("tony", "123");
User user4 = new User("lucy", "123");
users.add(user1);
users.add(user2);
users.add(user3);
users.add(user4);
// 将集合压入值栈
valueStack.push(users);
valueStack.set("users", users);
ActionContext.getContext().put("users", users);

// 跳转页面(值栈和request的生命周期一样,采用转发方式)
return "result";


jsp页面遍历集合:

<!-- 遍历action转发的集合,根据压入栈选择,这里我压入了三种,匿名的被有名字的压下去了,所以可以使用:
    索引获取集合:[1].top,取root栈:users,取map栈:#users
 -->
<s:iterator value="#users" var="user" status="status">
    <!-- 1.因为每次遍历的对象都在栈顶,可以直接获取栈顶对象的属性.(推荐)
        2. 神奇的request.通过el表达式默认直接获取到站定对象的属性值
        3. var的变量名是存入map栈的副本的key. 通过默认查找机制找,通过#直接取。通过request的el表达式取
    -->
    1<s:property value="[0].top.username"/>|2<s:property value="username"/>|3${username }|4<s:property value="#user.username"/>|5${user.username }<br/>
</s:iterator>

遇到的问题:

  • 可以直接获取栈顶对象的属性
  • <s:property/>标签会将value属性内的字符串当成一个整体去执行值栈的默认查找机制。即不能使用<s:property value="user.username" />企图通过默认查找机制获取map栈中的user对象,再通过getUsername获取属性值。(例外:[index].top.属性名不会当成一个整体字符串,即:<s:property value="[0].top.username" />它会先找到到Root栈的对象,再获取他的value值)

扩展了解:

1. 遍历集合时配合begin与end可以控制遍历一定数量的元素

2.<s:property/>如果没有value值,默认获取栈顶的对象


#####1.3. ```<s:if> <s:elseif> <s:else>```标签    

**支持OGNL表达式**

    //模拟代表用户状态的标识存入值栈
    valueStack.set("userRole", 1);
    // 跳转页面(值栈和request的生命周期一样,采用转发方式)
    return "result";


    <s:if test="userRole==0">管理员</s:if><br/>
    <s:elseif test="userRole==1">普通用户</s:elseif><br/>
    <s:else>游客</s:else>

#####1.4. ```<s:a>```标签 
作用:生成a标签链接

    <!-- Html超链接 -->
    <a href="${pageContext.request.contextPath }/product_find.action?name=水果">我是超链接</a>
    <br/>

    <!-- action是action的名字 -->
    <s:a action="product_find" namespace="/">
        <!-- name:是参数名,value:参数值
        原因:value:是个ognl表达式
         -->
        <s:param name="name" value="'苹果'"/>
        我是新的超链接
    </s:a>


![img25.png](http://upload-images.jianshu.io/upload_images/5303154-a67f7ff3462bd645.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


#####1.5 其他一些用到的标签

    <s:fielderror/> 
    <s:actionerror/>
    <s:actionmessage/>
    <s:i18n>
    <s:param>

####2. 用户界面(UI)标签
用户界面标签主要是包括表单类标签和其他类标签
#####2.1 ```<s:form>```标签
作用:生成form标签。

属性:

* action属性,对应 struts.xml <action>元素name属性;

* namespace属性,对象 struts.xml <package>元素 namespace属性

Html表单:
<form action="${pageContext.request.contextPath }/form.action" method="post"></form>

Struts2表单:

<s:form action="form" namespace="/" method="post"></form>

#####2.2 ```<s:textfield>, <s:password>, <s:hidden>, <s:textarea>```标签
    <s:textfield> 文本域 ,相当于 <input type=”text” >
    <s:password> 密码域 ,相当于<input type=”password” >
        showpassword:表单回显时是否显示密码
    <s:hidden> 隐藏域 , 相当于 <input type=”hidden” >
    <s:textarea> 文本框 , 相当于 <textarea></textarea>

    例:

    Html:
    <input type="hidden" name="name" value="jack"/>
    用户名:<input type="text" name="username"/>
    密码:<input type="password" name="password">
    备注:<textarea rows="3" cols="20" name="memo"></textarea>

    Struts2:
    <s:hidden name="name" value="jack"/><br/>
    用户名:<s:textfield name="username"/>
    密码:<s:password name="password"/>
    备注:<s:textarea name="memo" rows="3" cols="20"/>

#####2.3 ```<s:radio>、<s:checkboxlist>、<s:select>```标签
* ```<s:radio>``` 接收list或者map 生成一组单选按钮 
* ```<s:select>``` 接收list或者map ,生成一组下拉列表 
* ```<s:checkboxlist>``` 接收list或者map ,生成一组复选框

单选项 radio:
Html:
    性别:<input type="radio" name="sex" value="male"/>男
    <input type="radio" name="sex" value="female"/>女<br/>

Struts2:
    性别:<s:radio name="sex" list="{'男','女'}" value="{'male','female'}"/>
    //显示的内容与value的值相同时可以省略value(不推荐)

-----------------------------------------------------------------------------

下拉框 select:
Html:
    城市:<select name="city">
            <option value="">--请选择城市--</option>
            <option value="bj">北京</option>
            <option value="sz">深圳</option>
    </select><br/>

Struts2:
    <!-- 构建map集合 -->
    使用#{key:value构建map集合,key为html中value的值,这里的value就是显示的内容。以逗号隔开

    城市:<s:select name="city" list="#{'bj':'北京','sz':'深圳'}" headerKey="" headerValue="--请选择城市--"/><br/>

----------------------------------------------------------------------------

复选框 checkbox:
Html:
    爱好:<input type="checkbox" name="hobby" value="football"/>足球
    <input type="checkbox" name="hobby" value="basketball"/>篮球
    <input type="checkbox" name="hobby" value="pinpong"/>乒乓球<br/>

Struts2:
    <!-- 构建map集合 -->
    使用#{key:value构建map集合,key为html中value的值,这里的value就是显示的内容。以逗号隔开

    爱好:<s:checkboxlist name="hobby" list="#{'football':'足球','basketball':'篮球','pingpong':'乒乓球' }"/>

#####2.4 ```<s:file>、<s:submit>、<s:reset>```标签
* ```<s:file>对应html中input标签的file```
* ```<s:submit>、<s:reset>```分别对应html中的提交和重置

文件上传 file:
Html:
    头像:<input type="file" name="icon"/>

Struts2:
    头像:<s:file name="icon"/>

-------------------------------------------------------------------------

提交表单 submit:
Html:
    <input type="submit" value="提交"/>

Struts2:
    <s:submit value="提交"/>
-------------------------------------------------------------------------
重置表单 reset:
Html:
    <input type="reset" value="重置"/>
Struts2:
    <s:reset value="重置"/>
-------------------------------------------------------------------------

#####2.5 主题样式
经过上面表单标签的编写,我们会发现用```struts2标签库```编写完的表单页面不好看。这是因为Struts2提供了不同的主题。

Struts2 模板文件,支持两种Freemarker生成 (.ftl模板文件) , Velocity生成 (.vm 模板文件)

    struts2默认采用 Freemarker 。

    提供四种主题 :
        Simple 没有任何修饰效果,最简单主题 
        Xhtml 通过 布局表格 自动排版 (默认主题 )
        css_xhtml 通过CSS进行排版布局 
        ajax 以Xhtml模板为基础,增加ajax功能  

    问题: 如何修改主题 


![img26.png](http://upload-images.jianshu.io/upload_images/5303154-99cd7e5e990fd212.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


    开发中,在struts.xml 配置常量,修改默认主题样式,对所有form生效

    <!-- 简单主题 -->
    <constant name="struts.ui.theme" value="simple"/> 

将主题修改为简单样式后,效果如下图:


![img27.png](http://upload-images.jianshu.io/upload_images/5303154-1492f21411dcc079.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)



#####2.6 小结
Struts2的标签有两种:通用标签、表单界面标签。

注意:这两种标签属性支持ognl表达式的属性名字是不一样。

**表单标签:name是支持ognl表达式,value不支持,直接显示值。**

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

推荐阅读更多精彩内容