Struts是Apache Group的一个优秀的MVC的前台框架,目前也使用的非常广泛。
但仅仅停留在使用的层次上是没有挑战性的,研究其源码才是真正乐趣所在。
本文以一个很小的例子入手,将这个例子中牵涉到的Struts源码做了一次剖析,
豁然发现就在这个不起眼的例子中,也有许多值得学习的东西,故将其整理成文,目的旨在抛砖引玉,希望有兴趣者同乐。
本文的例子就来自Struts自带的一个Example(struts-example.war)。在这个例子中的第一个JSP(index.jsp),一开始就有这样的一段代码:
==============================================
==============================================
从文档上的描述来看,这段代码主要有如下的作用:
1、由于本sample的数据库是一个database.xml文件,所以在JSP的一开头就用这段代码来验证database.xml文件是否已被装载成功
2、如果装载不成功,那么将打印中间那一段出错信息
OK,由以上的描述,很自然的会想到,这个标签大概做的事情可能就是在/WEB-INF/目录下查找database.xml,成功就返回成功,错误就
返回失败,如此如此,顺理成章
可是Struts的源代码是否就是按照这样的逻辑写的呢?答案显然是否
下面就开始层层剖析Struts的源码
首先当然是从这个标签类入手,打开org.apache.struts.taglib.logic.NotPresentTag类,发现如下代码:
==============================================
==============================================
看来封装的不错,初步估计应该是这样:
1、NotPresentTag继承自PresentTag类,但Tag标签类必须有doStartTag这样的方法啊,那自然就是在PresentTag类里面有了
2、有一个condition方法,看来是PresentTag类的doStartTag方法调用了condition方法,正好这里利用面向对象的“多态”,调用到了这个方法,而不是
PresentTag中的condition方法
继续,打开PresentTag类,看到如下代码:
==============================================
==============================================
通过这段代码,又看到了如下的东西:
1、PresentTag又继承自ConditionalTagBase,抽象的比较精彩
2、代码中的一大堆变量如name, cookie, role等等,这些正好是logic:notPresent元素的属性
3、这些变量没有在PresengTag中定义,看来是定义在了ConditionalTagBase里面了
4、留心看name这个属性的处理,因为我们研究的那段代码(第一段代码),属性中最关键的也就是那个name="database"了
看到她调用了isBeanPresent这个方法,而这个方法又调用了TagUtils.getInstance().lookup这个方法
OK,到此,可以看到,研究方向转移到了TagUtils.getInstance().lookup这个方法,在开始看这个方法之前,还是把ConditionalTagBase
这个类也看一看,说不定还有意外的收获,如下:
==============================================
public abstract class ConditionalTagBase extends TagSupport {
//。。。中间省略了那些属性(name, role, scope, user等等元素属性)的get/set方法
//这些get/set方法将被自动调用,所以第一段代码中的name="database"中,database这个值将被自动赋给name变量
==============================================
由以上的代码,虽然没有意外收获,但是确认了不少之前的想法,如下:
1、condition这个方法是一个抽象方法,具体实现在PresentTag和NotPresentTag类中
2、doStartTag方法中调用了condition方法,导致了PresentTag和NotPresentTag类中condition方法的被调用
猜测已被确认,接下来继续看TagUtils.getInstance().lookup这个方法,找到源代码如下:
==============================================
==============================================
但仅仅停留在使用的层次上是没有挑战性的,研究其源码才是真正乐趣所在。
本文以一个很小的例子入手,将这个例子中牵涉到的Struts源码做了一次剖析,
豁然发现就在这个不起眼的例子中,也有许多值得学习的东西,故将其整理成文,目的旨在抛砖引玉,希望有兴趣者同乐。
本文的例子就来自Struts自带的一个Example(struts-example.war)。在这个例子中的第一个JSP(index.jsp),一开始就有这样的一段代码:
==============================================
- Code: Select all
<logic:notPresent name="database" scope="application">
<font color="red">
ERROR: User database not loaded -- check servlet container logs
for error messages.
</font>
<hr>
</logic:notPresent>
==============================================
从文档上的描述来看,这段代码主要有如下的作用:
1、由于本sample的数据库是一个database.xml文件,所以在JSP的一开头就用这段代码来验证database.xml文件是否已被装载成功
2、如果装载不成功,那么将打印中间那一段出错信息
OK,由以上的描述,很自然的会想到,这个标签大概做的事情可能就是在/WEB-INF/目录下查找database.xml,成功就返回成功,错误就
返回失败,如此如此,顺理成章
可是Struts的源代码是否就是按照这样的逻辑写的呢?答案显然是否
下面就开始层层剖析Struts的源码
首先当然是从这个标签类入手,打开org.apache.struts.taglib.logic.NotPresentTag类,发现如下代码:
==============================================
- Code: Select all
public class NotPresentTag extends PresentTag {
// ------------------------------------------------------ Protected Methods
/**
* Evaluate the condition that is being tested by this particular tag,
* and return <code>true</code> if the nested body content of this tag
* should be evaluated, or <code>false</code> if it should be skipped.
* This method must be implemented by concrete subclasses.
*
* @exception JspException if a JSP exception occurs
*/
protected boolean condition() throws JspException {
return (condition(false));
}
}
==============================================
看来封装的不错,初步估计应该是这样:
1、NotPresentTag继承自PresentTag类,但Tag标签类必须有doStartTag这样的方法啊,那自然就是在PresentTag类里面有了
2、有一个condition方法,看来是PresentTag类的doStartTag方法调用了condition方法,正好这里利用面向对象的“多态”,调用到了这个方法,而不是
PresentTag中的condition方法
继续,打开PresentTag类,看到如下代码:
==============================================
- Code: Select all
public class PresentTag extends ConditionalTagBase {
//。。。中间有省略
/**
* Evaluate the condition that is being tested by this particular tag,
* and return <code>true</code> if the nested body content of this tag
* should be evaluated, or <code>false</code> if it should be skipped.
* This method must be implemented by concrete subclasses.
*
* @exception JspException if a JSP exception occurs
*/
protected boolean condition() throws JspException {
return (condition(true));
}
/**
* Evaluate the condition that is being tested by this particular tag,
* and return <code>true</code> if the nested body content of this tag
* should be evaluated, or <code>false</code> if it should be skipped.
* This method must be implemented by concrete subclasses.
*
* @param desired Desired outcome for a true result
*
* @exception JspException if a JSP exception occurs
*/
protected boolean condition(boolean desired) throws JspException {
// Evaluate the presence of the specified value
boolean present = false;
HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
if (cookie != null) {
present = this.isCookiePresent(request);
} else if (header != null) {
String value = request.getHeader(header);
present = (value != null);
} else if (name != null) {
present = this.isBeanPresent();
} else if (parameter != null) {
String value = request.getParameter(parameter);
present = (value != null);
} else if (role != null) {
StringTokenizer st = new StringTokenizer(role, ROLE_DELIMITER, false);
while (!present && st.hasMoreTokens()) {
present = request.isUserInRole(st.nextToken());
}
} else if (user != null) {
Principal principal = request.getUserPrincipal();
present = (principal != null) && user.equals(principal.getName());
} else {
JspException e = new JspException
(messages.getMessage("logic.selector"));
TagUtils.getInstance().saveException(pageContext, e);
throw e;
}
return (present == desired);
}
/**
* Returns true if the bean given in the <code>name</code> attribute is found.
* @since Struts 1.2
*/
protected boolean isBeanPresent() {
Object value = null;
try {
if (this.property != null) {
value = TagUtils.getInstance().lookup(pageContext, name, this.property, scope);
} else {
value = TagUtils.getInstance().lookup(pageContext, name, scope);
}
} catch (JspException e) {
value = null;
}
return (value != null);
}
/**
* Returns true if the cookie is present in the request.
* @since Struts 1.2
*/
protected boolean isCookiePresent(HttpServletRequest request) {
Cookie cookies[] = request.getCookies();
if (cookies == null) {
return false;
}
for (int i = 0; i < cookies.length; i++) {
if (this.cookie.equals(cookies.getName())) {
return true;
}
}
return false;
}
==============================================
通过这段代码,又看到了如下的东西:
1、PresentTag又继承自ConditionalTagBase,抽象的比较精彩
2、代码中的一大堆变量如name, cookie, role等等,这些正好是logic:notPresent元素的属性
3、这些变量没有在PresengTag中定义,看来是定义在了ConditionalTagBase里面了
4、留心看name这个属性的处理,因为我们研究的那段代码(第一段代码),属性中最关键的也就是那个name="database"了
看到她调用了isBeanPresent这个方法,而这个方法又调用了TagUtils.getInstance().lookup这个方法
OK,到此,可以看到,研究方向转移到了TagUtils.getInstance().lookup这个方法,在开始看这个方法之前,还是把ConditionalTagBase
这个类也看一看,说不定还有意外的收获,如下:
==============================================
public abstract class ConditionalTagBase extends TagSupport {
//。。。中间省略了那些属性(name, role, scope, user等等元素属性)的get/set方法
//这些get/set方法将被自动调用,所以第一段代码中的name="database"中,database这个值将被自动赋给name变量
- Code: Select all
/**
* Perform the test required for this particular tag, and either evaluate
* or skip the body of this tag.
*
* @exception JspException if a JSP exception occurs
*/
public int doStartTag() throws JspException {
if (condition())
return (EVAL_BODY_INCLUDE);
else
return (SKIP_BODY);
}
/**
* Evaluate the remainder of the current page normally.
*
* @exception JspException if a JSP exception occurs
*/
public int doEndTag() throws JspException {
return (EVAL_PAGE);
}
/**
* Release all allocated resources.
*/
public void release() {
super.release();
cookie = null;
header = null;
name = null;
parameter = null;
property = null;
role = null;
scope = null;
user = null;
}
// ------------------------------------------------------ Protected Methods
/**
* Evaluate the condition that is being tested by this particular tag,
* and return <code>true</code> if the nested body content of this tag
* should be evaluated, or <code>false</code> if it should be skipped.
* This method must be implemented by concrete subclasses.
*
* @exception JspException if a JSP exception occurs
*/
protected abstract boolean condition() throws JspException;
==============================================
由以上的代码,虽然没有意外收获,但是确认了不少之前的想法,如下:
1、condition这个方法是一个抽象方法,具体实现在PresentTag和NotPresentTag类中
2、doStartTag方法中调用了condition方法,导致了PresentTag和NotPresentTag类中condition方法的被调用
猜测已被确认,接下来继续看TagUtils.getInstance().lookup这个方法,找到源代码如下:
==============================================
- Code: Select all
/**
* Locate and return the specified bean, from an optionally specified
* scope, in the specified page context. If no such bean is found,
* return <code>null</code> instead. If an exception is thrown, it will
* have already been saved via a call to <code>saveException()</code>.
*
* @param pageContext Page context to be searched
* @param name Name of the bean to be retrieved
* @param scopeName Scope to be searched (page, request, session, application)
* or <code>null</code> to use <code>findAttribute()</code> instead
* @return JavaBean in the specified page context
* @exception JspException if an invalid scope name
* is requested
*/
public Object lookup(PageContext pageContext, String name, String scopeName)
throws JspException {
if (scopeName == null) {
return pageContext.findAttribute(name);
}
try {
return pageContext.getAttribute(name, instance.getScope(scopeName));
} catch (JspException e) {
saveException(pageContext, e);
throw e;
}
}
/**
* Locate and return the specified property of the specified bean, from
* an optionally specified scope, in the specified page context. If an
* exception is thrown, it will have already been saved via a call to
* <code>saveException()</code>.
*
* @param pageContext Page context to be searched
* @param name Name of the bean to be retrieved
* @param property Name of the property to be retrieved, or
* <code>null</code> to retrieve the bean itself
* @param scope Scope to be searched (page, request, session, application)
* or <code>null</code> to use <code>findAttribute()</code> instead
* @return property of specified JavaBean
*
* @exception JspException if an invalid scope name
* is requested
* @exception JspException if the specified bean is not found
* @exception JspException if accessing this property causes an
* IllegalAccessException, IllegalArgumentException,
* InvocationTargetException, or NoSuchMethodException
*/
public Object lookup(
PageContext pageContext,
String name,
String property,
String scope)
throws JspException {
// Look up the requested bean, and return if requested
Object bean = lookup(pageContext, name, scope);
if (bean == null) {
JspException e = null;
if (scope == null) {
e = new JspException(messages.getMessage("lookup.bean.any", name));
} else {
e =
new JspException(
messages.getMessage("lookup.bean", name, scope));
}
saveException(pageContext, e);
throw e;
}
if (property == null) {
return bean;
}
// Locate and return the specified property
try {
return PropertyUtils.getProperty(bean, property);
} catch (IllegalAccessException e) {
saveException(pageContext, e);
throw new JspException(
messages.getMessage("lookup.access", property, name));
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t == null) {
t = e;
}
saveException(pageContext, t);
throw new JspException(
messages.getMessage("lookup.target", property, name));
} catch (NoSuchMethodException e) {
saveException(pageContext, e);
throw new JspException(
messages.getMessage("lookup.method", property, name));
}
}
==============================================
由上,可以看到:
1、TagUtils是一个Singleton的类
2、代码Object bean = lookup(pageContext, name, scope);的用意显然是在寻找name变量绑定的对象
3、2步骤查找的过程是首先查看scope变量是否为空,如果为空,将调用pageContext.findAttribute方法
4、findAttribute方法将依次在page, request, session, application四个范围内查找name变量绑定的对象(来自Servlet API文档的解释)
5、如果scope变量不为空(在我们的例子中,scope="Application"),那么直接调用pageContext.getAttribute方法,在scope里面查找
name变量绑定的对象
6、如果这个对象找到了,并且property属性没有被赋值(在我们的例子中确实没有赋值),那么直接将对象返回
7、如果对象不为空的返回到了PresentTag的condition(boolean desire)方法,那么最终结果就是存在,notPresent这个标签里面的出错信息
就不会被显示
通过以上的分析,可以看到,现在的关键问题已经转移到看Application范围内是否有一个对象绑定一个key叫做"database"(看步骤5的描述)
但是这个事情Struts的这个Example程序又是在什么时候做的呢?
通过观察和分析,发现在struts-config配置中,将这项工作作为了一个PlugIn进行了配置,如下:
==============================================
==============================================
事到如今,终于离目标越来越近了,MemoryDatabasePlugIn这个类在Tomcat启动的时候启动,读取了database.xml文件,
将其中的值读取出来并构建出了一个对象,然后将这个对象绑定到database这个key,放在了Application范围内的一个容器中
这样的解释,终于可以和前面研究的结果相衔接起来了,不是么?:)
迫不及待的打开MemoryDatabasePlugIn类,看到了如下代码:
==============================================
==============================================
果然不出所料,这段代码做了如下的事情:
1、生成了MemoryUserDatabase这个对象,并调用其setPathname和open方法,读取database.xml的值并填充到成员变量中
2、将这个对象绑定到了ServletContext中,Constants.DATABASE_KEY这个常量就是"database"
3、ServletContext是Servlet的一个上下文环境,也是ServletConfig的一个访问接口。ServletConfig中定义了Servlet和Servlet容器
沟通时的一些信息(如Servlet的方法定义,参数等等)。这个上下文在一个JVM、一个Web应用中一般只会存在一份,所以显然是
Application范围内的东西
至此,终于搞清楚了Struts中这个logic:notPresent标签的整个运行过程,和我们的想像不完全相同的是,Struts是将database
的配置在启动的时候就读取出来然后绑定到一个对象;然后这个标签中的代码去尝试查找这个对象,如果找到,表明database装载
成功,否则就出错。
另外,有一个收获是,标签的书写中,scope="Application"可以不写,因为如果不写scope属性值,Struts会依次在page, request, session,
application四个范围内进行查找,一样可以找到这个对象,但有些地方还是有点不妥的,下面将提到。
依次查找这个动作是通过pageContext类的一个抽象方法findAttribute方法来完成的,由于是抽象方法,所以这个方法的具体实现应该在Servlet
容器的源代码中,也就是说,这个方法的实现是和容器相关的。另外,依次查找这个动作肯定没有指定scope查找来得快,所以性能上也会有影响
我想这就是Struts的这个example中将scope手动指定的缘故吧。
OK,让我们刨根问底,再来看findAttribute方法的实现,这里摘录的是Tomcat4.1的源代码,这个方法的实现在Tomcat的
org.apache.jasper.runtime.PageContextImpl这个类中,代码如下:
==============================================
==============================================
不用再解释了,再清楚不过了,的确是一个一个的找了过来
至此,我们已经从logic:notPresent这个标签开始,做了一次全程历险,而且最后还到Tomcat家里去小坐了一下
总结来说,logic:notPresent这个标签是在指定scope中查找name变量绑定的一个对象,可以用来定位一些资源是否被装载
但这个标签没有强大到自动去找指定的文件或其他资源的地步,需要我们在这个标签被调用之前,将资源装载并绑定到
page, request, session或Application中
Struts是一个优秀的开源项目,除了一整套Web运行机制外,她还带了一个大标签库。这个标签库中有很多比较实用的标签,
但如果理解不透,很容易发生错用的现象。所以研读Struts的源码无其他目的,只是想好好搞清楚这些标签的作用而已。
1、TagUtils是一个Singleton的类
2、代码Object bean = lookup(pageContext, name, scope);的用意显然是在寻找name变量绑定的对象
3、2步骤查找的过程是首先查看scope变量是否为空,如果为空,将调用pageContext.findAttribute方法
4、findAttribute方法将依次在page, request, session, application四个范围内查找name变量绑定的对象(来自Servlet API文档的解释)
5、如果scope变量不为空(在我们的例子中,scope="Application"),那么直接调用pageContext.getAttribute方法,在scope里面查找
name变量绑定的对象
6、如果这个对象找到了,并且property属性没有被赋值(在我们的例子中确实没有赋值),那么直接将对象返回
7、如果对象不为空的返回到了PresentTag的condition(boolean desire)方法,那么最终结果就是存在,notPresent这个标签里面的出错信息
就不会被显示
通过以上的分析,可以看到,现在的关键问题已经转移到看Application范围内是否有一个对象绑定一个key叫做"database"(看步骤5的描述)
但是这个事情Struts的这个Example程序又是在什么时候做的呢?
通过观察和分析,发现在struts-config配置中,将这项工作作为了一个PlugIn进行了配置,如下:
==============================================
- Code: Select all
<plug-in className="org.apache.struts.webapp.example.memory.MemoryDatabasePlugIn">
<set-property property="pathname" value="/WEB-INF/database.xml"/>
</plug-in>
==============================================
事到如今,终于离目标越来越近了,MemoryDatabasePlugIn这个类在Tomcat启动的时候启动,读取了database.xml文件,
将其中的值读取出来并构建出了一个对象,然后将这个对象绑定到database这个key,放在了Application范围内的一个容器中
这样的解释,终于可以和前面研究的结果相衔接起来了,不是么?:)
迫不及待的打开MemoryDatabasePlugIn类,看到了如下代码:
==============================================
- Code: Select all
/**
* Initialize and load our initial database from persistent storage.
*
* @param servlet The ActionServlet for this web application
* @param config The ApplicationConfig for our owning module
*
* @exception ServletException if we cannot configure ourselves correctly
*/
public void init(ActionServlet servlet, ModuleConfig config)
throws ServletException {
log.info("Initializing memory database plug in from '" +
pathname + "'");
// Remember our associated configuration and servlet
this.config = config;
this.servlet = servlet;
// Construct a new database and make it available
database = new MemoryUserDatabase();
try {
String path = calculatePath();
if (log.isDebugEnabled()) {
log.debug(" Loading database from '" + path + "'");
}
database.setPathname(path);
database.open();
} catch (Exception e) {
log.error("Opening memory database", e);
throw new ServletException("Cannot load database from '" +
pathname + "'", e);
}
// Make the initialized database available
servlet.getServletContext().setAttribute(Constants.DATABASE_KEY,
database);
// Setup and cache other required data
setupCache(servlet, config);
}
==============================================
果然不出所料,这段代码做了如下的事情:
1、生成了MemoryUserDatabase这个对象,并调用其setPathname和open方法,读取database.xml的值并填充到成员变量中
2、将这个对象绑定到了ServletContext中,Constants.DATABASE_KEY这个常量就是"database"
3、ServletContext是Servlet的一个上下文环境,也是ServletConfig的一个访问接口。ServletConfig中定义了Servlet和Servlet容器
沟通时的一些信息(如Servlet的方法定义,参数等等)。这个上下文在一个JVM、一个Web应用中一般只会存在一份,所以显然是
Application范围内的东西
至此,终于搞清楚了Struts中这个logic:notPresent标签的整个运行过程,和我们的想像不完全相同的是,Struts是将database
的配置在启动的时候就读取出来然后绑定到一个对象;然后这个标签中的代码去尝试查找这个对象,如果找到,表明database装载
成功,否则就出错。
另外,有一个收获是,标签的书写中,scope="Application"可以不写,因为如果不写scope属性值,Struts会依次在page, request, session,
application四个范围内进行查找,一样可以找到这个对象,但有些地方还是有点不妥的,下面将提到。
依次查找这个动作是通过pageContext类的一个抽象方法findAttribute方法来完成的,由于是抽象方法,所以这个方法的具体实现应该在Servlet
容器的源代码中,也就是说,这个方法的实现是和容器相关的。另外,依次查找这个动作肯定没有指定scope查找来得快,所以性能上也会有影响
我想这就是Struts的这个example中将scope手动指定的缘故吧。
OK,让我们刨根问底,再来看findAttribute方法的实现,这里摘录的是Tomcat4.1的源代码,这个方法的实现在Tomcat的
org.apache.jasper.runtime.PageContextImpl这个类中,代码如下:
==============================================
- Code: Select all
public Object findAttribute(String name) {
Object o = attributes.get(name);
if (o != null)
return o;
o = request.getAttribute(name);
if (o != null)
return o;
if (session != null) {
o = session.getAttribute(name);
if (o != null)
return o;
}
//这里的context就是ServletContext的一个实例
return context.getAttribute(name);
}
==============================================
不用再解释了,再清楚不过了,的确是一个一个的找了过来
至此,我们已经从logic:notPresent这个标签开始,做了一次全程历险,而且最后还到Tomcat家里去小坐了一下
总结来说,logic:notPresent这个标签是在指定scope中查找name变量绑定的一个对象,可以用来定位一些资源是否被装载
但这个标签没有强大到自动去找指定的文件或其他资源的地步,需要我们在这个标签被调用之前,将资源装载并绑定到
page, request, session或Application中
Struts是一个优秀的开源项目,除了一整套Web运行机制外,她还带了一个大标签库。这个标签库中有很多比较实用的标签,
但如果理解不透,很容易发生错用的现象。所以研读Struts的源码无其他目的,只是想好好搞清楚这些标签的作用而已。