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
defaultStack
是Struts2
默认执行的拦截器栈。

2.1 Struts2运行原理的底层分析(了解)

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.]

- 程序中每个拦截器 都必须实现 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>

#####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功能
问题: 如何修改主题

开发中,在struts.xml 配置常量,修改默认主题样式,对所有form生效
<!-- 简单主题 -->
<constant name="struts.ui.theme" value="simple"/>
将主题修改为简单样式后,效果如下图:

#####2.6 小结
Struts2的标签有两种:通用标签、表单界面标签。
注意:这两种标签属性支持ognl表达式的属性名字是不一样。
**表单标签:name是支持ognl表达式,value不支持,直接显示值。**
**但,除此之外,其他的所有通用标签,value是支持ognl表达式的。**
网友评论