本文是针对struts2的struts-tags中的s:url标签的使用进行扩展。
在J2EE开发中,使用struts2的时候我们很多时候会使用"/"来做URL地址定义,即使用项目的绝对路径。因为如果使用相对路径的话会十分麻烦,谁叫struts2中的"相对",指的并不是存放的目录结构,相对的是目标是指action的命名空间。
而由于实际服务器环境中的一些原因,可能会造成s:url生成后的地址的访问资源并不存在!
举个例子,当你部署的应用的服务器,在a机器的8080端口中。你可以在内网中使用http://ip:8080/a/ 访问,但外网访问时,却被映射到 http://www.foo.com/abc/a/ 中。
这样的情况下,通过s:url生成的url会变成是 /a 开头,而实际上是/abc/a/ 才能访问到你的应用。
陷入这个困境2天了,找不到好的解决方案,唯一的方案就是把应用映射到root,然后把应用名改成abc,前面用apache做proxy,这样的方案使用AJP的话会失败,但直接做跳转就成功,不过就丢失了request的IP。
所以最后只好把原因是使用s:url这个标签的生成不够自由的关系(没办法,我真的努力了,学艺不精啊)
基于把责任都推在s:url上的这个前提上就好办了,我决定重构一下他的标签,变得更加适合我用。
查了下源码,最后把目标定于org.apache.struts2.components.ComponentUrlProvider.java(其实方案比较多的,个人喜欢吧)
直接上源码
package org.apache.struts2.components;
import java.util.Map;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import com.foo.utils.file.PropertiesHelper;
import com.opensymphony.xwork2.util.ValueStack;
/**
* Override org.apache.struts2.components.ComponentUrlProvider(struts2.core.jar)
*
* @author KennyLee <br />
* @version 1.0.0<br/>
*/
public class ComponentUrlProvider implements UrlProvider {
private static final String URL_SEPARATOR = "/";
private static final String PROPERTY_FILE_NAME = "config.properties";
private static final String URL_CONTEXT_PATH_KEY = "url.context.path";
private static final String URL_SERVER_NAME_KEY = "url.server.name";
private static boolean isInitPro = false;
private static String forceContextPath = "";
private static String forceServerName = "";
protected HttpServletRequest httpServletRequest;
protected HttpServletResponse httpServletResponse;
protected String includeParams;
protected String scheme;
protected String value;
protected String action;
protected String namespace;
protected String method;
protected boolean encode = true;
protected boolean includeContext = true;
protected boolean escapeAmp = true;
protected String portletMode;
protected String windowState;
protected String portletUrlType;
protected String anchor;
protected boolean forceAddSchemeHostAndPort;
protected String urlIncludeParams;
protected ExtraParameterProvider extraParameterProvider;
protected UrlRenderer urlRenderer;
protected Component component;
@SuppressWarnings("rawtypes")
private Map parameters;
/**
*
* @param component
* The component used to delagete some calls to
* @param parameters
* parameters passed from <param...>
*/
public ComponentUrlProvider(Component component,
@SuppressWarnings("rawtypes") Map parameters) {
this.component = component;
this.parameters = parameters;
}
@Override
public String determineActionURL(String action, String namespace,
String method, HttpServletRequest req, HttpServletResponse res,
@SuppressWarnings("rawtypes") Map parameters, String scheme,
boolean includeContext, boolean encodeResult,
boolean forceAddSchemeHostAndPort, boolean escapeAmp) {
// XXX add by KennyLee 2012-05-08 01:21:37, fix URL by Action.
String url = null;
String ori_path = component.determineActionURL(action, namespace,
method, req, res, parameters, scheme, includeContext,
encodeResult, forceAddSchemeHostAndPort, escapeAmp);
url = ori_path;
String forceContextPath = getForceContextPath();
if (StringUtils.isNotBlank(ori_path)
&& StringUtils.isNotBlank(forceContextPath)) {
if (ori_path.startsWith(URL_SEPARATOR)) {
url = new StringBuilder().append(URL_SEPARATOR)
.append(forceContextPath).append(ori_path).toString();
} else if (ori_path.startsWith("http")) {
String[] splits = StringUtils.split(ori_path, URL_SEPARATOR);
int count = 0;
for (String string : splits) {
count++;
url += string;
if (count == 3) {// At after add serverName
url += URL_SEPARATOR;
url += forceContextPath;
}
if (count != splits.length) {
url += URL_SEPARATOR;
}
}
}
}
return url;
}
public String determineNamespace(String namespace, ValueStack stack,
HttpServletRequest req) {
return component.determineNamespace(namespace, stack, req);
}
public String findString(String expr) {
return component.findString(expr);
}
@SuppressWarnings("rawtypes")
public Map getParameters() {
return parameters;
}
public HttpServletRequest getHttpServletRequest() {
return httpServletRequest;
}
public void setHttpServletRequest(HttpServletRequest httpServletRequest) {
this.httpServletRequest = httpServletRequest;
}
public HttpServletResponse getHttpServletResponse() {
return httpServletResponse;
}
public void setHttpServletResponse(HttpServletResponse httpServletResponse) {
this.httpServletResponse = httpServletResponse;
}
public String getIncludeParams() {
return includeParams;
}
public void setIncludeParams(String includeParams) {
this.includeParams = includeParams;
}
public String getScheme() {
return scheme;
}
public void setScheme(String scheme) {
this.scheme = scheme;
}
public boolean isPutInContext() {
return component instanceof ContextBean;
}
public String getVar() {
return isPutInContext() ? ((ContextBean) component).getVar() : null;
}
public String getValue() {
// XXX add by KennyLee 2012-05-08 00:43:48, fix URL by value.
if (StringUtils.isNotBlank(value)) {
StringBuilder link = new StringBuilder();
String forceContextPath = getForceContextPath();
String forceServerName = getForceServerName();
if (StringUtils.startsWith(value, URL_SEPARATOR)
&& StringUtils.isNotBlank(forceContextPath)) {
HttpServletRequest request = httpServletRequest;
String path = request.getContextPath();
String serverName = request.getServerName();
if (StringUtils.isNotBlank(forceServerName)) {
serverName = forceServerName;
}
String basePath = request.getScheme() + "://" + serverName
+ ":" + request.getServerPort();
link.append(basePath).append(URL_SEPARATOR)
.append(forceContextPath).append(path).append(value);
} else {
link.append(value);
}
return link.toString();
}
return value;
}
/**
* <p>
* get forceServerName
* </p>
*
* @return
*/
private String getForceServerName() {
if (StringUtils.isBlank(forceServerName)) {
initProValues();
}
return forceServerName;
}
/**
* <p>
* get forceContextPath
* </p>
*
* @return
*/
private String getForceContextPath() {
if (StringUtils.isBlank(forceContextPath)) {
initProValues();
}
return forceContextPath;
}
/**
* <p>
* init property values
* </p>
*/
private synchronized void initProValues() {
if (!isInitPro) {
Properties pro = PropertiesHelper.getInstance()
.getPropertiesInstance(PROPERTY_FILE_NAME, false);
if (pro != null) {
String contextPath = pro.getProperty(URL_CONTEXT_PATH_KEY, "");
String serverName = pro.getProperty(URL_SERVER_NAME_KEY, "");
if (StringUtils.isNotBlank(contextPath)) {
forceContextPath = contextPath;
}
if (StringUtils.isNotBlank(serverName)) {
forceServerName = serverName;
}
}
isInitPro = true;
}
}
public void setValue(String value) {
this.value = value;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public boolean isEncode() {
return encode;
}
public void setEncode(boolean encode) {
this.encode = encode;
}
public boolean isIncludeContext() {
return includeContext;
}
public void setIncludeContext(boolean includeContext) {
this.includeContext = includeContext;
}
public boolean isEscapeAmp() {
return escapeAmp;
}
public void setEscapeAmp(boolean escapeAmp) {
this.escapeAmp = escapeAmp;
}
public String getPortletMode() {
return portletMode;
}
public void setPortletMode(String portletMode) {
this.portletMode = portletMode;
}
public String getWindowState() {
return windowState;
}
public void setWindowState(String windowState) {
this.windowState = windowState;
}
public String getPortletUrlType() {
return portletUrlType;
}
public ValueStack getStack() {
return component.getStack();
}
public void setPortletUrlType(String portletUrlType) {
this.portletUrlType = portletUrlType;
}
public String getAnchor() {
return anchor;
}
public void setAnchor(String anchor) {
this.anchor = anchor;
}
public boolean isForceAddSchemeHostAndPort() {
return forceAddSchemeHostAndPort;
}
public void setForceAddSchemeHostAndPort(boolean forceAddSchemeHostAndPort) {
this.forceAddSchemeHostAndPort = forceAddSchemeHostAndPort;
}
public void putInContext(String result) {
if (isPutInContext()) {
((ContextBean) component).putInContext(result);
}
}
public String getUrlIncludeParams() {
return urlIncludeParams;
}
public void setUrlIncludeParams(String urlIncludeParams) {
this.urlIncludeParams = urlIncludeParams;
}
public ExtraParameterProvider getExtraParameterProvider() {
return extraParameterProvider;
}
public void setExtraParameterProvider(
ExtraParameterProvider extraParameterProvider) {
this.extraParameterProvider = extraParameterProvider;
}
public UrlRenderer getUrlRenderer() {
return urlRenderer;
}
public void setUrlRenderer(UrlRenderer urlRenderer) {
this.urlRenderer = urlRenderer;
}
}
说明:
回头想想我刚刚举的例子,其中最主要是那个我们预想之外的abc子域名,如果我们能把它都添加在应用名之前就完毕了,而且以后就算前面加多少个子域名目录,也依然可以正常访问。
所以,这里我用一个forceContextPath来存放这个值。
而另外一个关键的地方是 serverName,即我们访问时用的IP或者域名。用原生的requet方法getServerName的话,有些时候会造成跟访问域名不一致的情况。例如你用 www.foo.com 访问的,但request.getServerName得出的结果是 192.168.1.1 。这种情况下,有时候可能影响变大,但如果域名做了多映射的条件下,也会造成访问障碍,为了安全起见,也提供强制定义的方式来定义serverName。这也是为什么我不使用原生的forceAddSchemeHostAndPort参数来获取完整路径的原因。
即另外一个重要的参数值 forceServerName。
这个两个值我是放在classes根目录下的config.properties目录下,相信如果专注于J2EE开发的人对这类型文件不会陌生吧。forceContextPath在config.properties中的key为url.context.path,而forceServerName的key为url.server.name。
Q:为什么使用value构造URL时,我把http和域名等信息都加上了?
A:对于这点,其实我也挺无奈的。因为如果不这样做,我遇到会产生forceContextPath被重叠了两次的情况。即变成了 /abc/abc/ 但我实际需要的只是 /abc/ (注:我的应用映射到root中,而应用名跟子级域名一致,即应用名也叫abc)。也想过用javascript来解决这个问题,不过最后还是懒得再继续探究下去了。因为其实上线环境中,绑定域名其实没多大问题的。
附上上面代码中使用到的一个工具类PropertiesHelper的的代码
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
/**
* <b>类名称:</b>PropertiesHelper<br/>
* <b>类描述:</b>java.util.Properties的工具类<br/>
* <b>创建时间:</b>2009-10-12 下午2:48:05<br/>
* <b>备注:</b><br/>
*
* @author KennyLee <br />
* @version 1.0.0<br/>
*/
public class PropertiesHelper {
private volatile static PropertiesHelper uniqueInstance;
private static final Map<String, Properties> propertiesMap = new HashMap<String, Properties>();
private PropertiesHelper() {
};
public static PropertiesHelper getInstance() {
if (uniqueInstance == null) {
synchronized (PropertiesHelper.class) {
if (uniqueInstance == null) {
uniqueInstance = new PropertiesHelper();
}
}
}
return uniqueInstance;
}
/**
* <p>
* Get properties instance by fileName.
* </p>
*
* @param fileName
* @param isKeepProperties
* 是否保存Properties对象到全局。
* @return
*/
public Properties getPropertiesInstance(String fileName,
boolean isKeepProperties) {
Properties resultInstence = null;
if (StringUtils.isBlank(fileName))
return null;
resultInstence = propertiesMap.get(fileName);
if (resultInstence == null) {
resultInstence = new Properties();
InputStream inputStream = null;
try {
inputStream = this.getClass().getClassLoader()
.getResourceAsStream(fileName);
resultInstence.load(inputStream);
} catch (IOException e) {
resultInstence = null;
e.printStackTrace();
} finally {
IOUtils.closeQuietly(inputStream);
}
if (resultInstence != null && isKeepProperties)
propertiesMap.put(fileName, resultInstence);
}
return resultInstence;
}
/**
* <p>
* Get properties instance by fileName.
* </p>
*
* @param fileName
* @return
*/
public Properties getPropertiesInstance(String fileName) {
return getPropertiesInstance(fileName, true);
}
}
希望对大家有用,或者给你带来一定的启发!
EOF