页面跳转到BackURL功能(基于struts2实现)

页面跳转到BackURL功能(基于struts2实现)

应用场景:
在查询用户列表/user!list.action时,点击新增用户,将跳转到/user!input.action
现在需要新增成功后跳转到/user!list.action
常规的做法是在UserAction里定义一个Result=RELOAD, location = "user!list.action", type = "redirect"

但是现在有这样的需求:
在查询用户列表/user!list.action,作了一些查询,分页的操作后,可能你的页面地址就变成了:
/user!list.action?page.no=5&page.size=20&searchParam=searchValue
如果此时你点击新增用户,需要新增完毕仍回到刚才操作过的页面,因为这个页面地址已经无法预料,你无法再Action里配置

所以现在需要实现这样的功能,能方便跳回之前操作的页面

目前实现的大致思路如下:

1.首先当跳转到新增页面时,一定要把跳回的URL地址带上,如:
/user!input.action?BackURL=/user!list.action?page.no=5&page.size=20&searchParam=searchValue
带上的地址为了不影响浏览器解析原地址,已改将BackURL的值URL Encode如下
/user!input.action?BackURL=%2Fuser%21list.action%3Fpage.no%3D5%26page.size%3D20%26searchParam%3DsearchValue

2.当新增页面提交时,要将BackURL一并提交给服务器,所以最简单的做法是在Form表单加上一个隐藏域:
<input type="hidden" name="BackURL" value="${param['BackURL']}">

或者提交表单时动态修改提交的URL为/user!save.action?BackURL=%2Fuser%21list.action%3Fpage.no%3D5%26page.size%3D20%26searchParam%3DsearchValue

3.新增用户页面/user!input.action会将请求提交给UserAciton的save方法处理
public String save() throws Exception {
	//...
	userService.save(user);
	//...
	return RELOAD;
}

4.为了做到通用,一般web项目都会自定义一个类似AuthorityInterceptor来鉴权的拦截器

public class AuthorityInterceptor extends AbstractInterceptor {


	public String intercept(ActionInvocation invocation) throws Exception {
		//...
		//此处注册一个Action结果前处理监听器,action的方法执行完,在返回结果视图之前会调用此监听器
		//我们的核心逻辑都在这个监听器BackURLPreResultListener完成。
		invocation.addPreResultListener(new BackURLPreResultListener());
		String invokeResult = invocation.invoke();
		return invokeResult;
	}
}

public class BackURLPreResultListener implements PreResultListener {


	@Override
	public void beforeResult(ActionInvocation invocation, String resultCode) {
		// handle backUrl
		HttpServletRequest request = ServletActionContext.getRequest();
		String backURL = extractBackURL(request);
		ServletActionContext.getResponse().sendRedirect(backURL);
		invocation.setResultCode(Action.NONE);
	}
}

从request里提取backUrl的方法extractBackURL每个人实现都不太一样,我的实现是这样的:

/**
 * 上一次请求的地址 
 * 1、先从request.parameter中查找BackURL 
 * 2、先从request.attribute中查找BackURL
 * 3、先从request.Referer header中查找BackURL,即当前请求是从哪个页面触发的
 */
private String extractBackURL(HttpServletRequest request) {
	String url = request.getParameter(Const.BACK_URL);
	if (url == null) {
		url = (String) request.getAttribute(Const.BACK_URL);
	}
	if (url == null) {
		url = (String) request.getHeader("Referer");
	}
	if (!StringUtils.isEmpty(url) && url.startsWith(request.getContextPath())) {
		url = getBasePath(request) + url;
	}
	return url;
}


private String getBasePath(HttpServletRequest req) {
	StringBuffer baseUrl = new StringBuffer();
	String scheme = req.getScheme();
	int port = req.getServerPort();
	baseUrl.append(scheme); // http, https
	baseUrl.append("://");
	baseUrl.append(req.getServerName());
	if ((scheme.equals("http") && port != 80) || (scheme.equals("https") && port != 443)) {
		baseUrl.append(':');
		baseUrl.append(req.getServerPort());
	}
	return baseUrl.toString();
}

到此跳转的基本功能都已实现,但是有几个问题,还需要更完善一下:

1.不是每个页面传了BackURL参数,你就需要给它跳回BackURL指定的页面,如
/user!input.action?BackURL=/user!list.action?page.no=5&page.size=20&searchParam=searchValue
当进入新增用户界面时,也会经过BackURLPreResultListener监听器处理。
而真正要处理的只是save方法执行后要跳转

所以我们还得为这段代码加一个开关,即什么时候执行,什么时候不执行,改造过后的代码如下:

public class BackURLPreResultListener implements PreResultListener {


	@Override
	public void beforeResult(ActionInvocation invocation, String resultCode) {
		// handle backUrl
		HttpServletRequest request = ServletActionContext.getRequest();
		if (Boolean.TRUE.equals(request.getAttribute(Const.ENABLE_BACK_URL))) {
			String backURL = extractBackURL(request);
			if (StringUtils.isNotBlank(backURL)) {
				ServletActionContext.getResponse().sendRedirect(backURL);
				invocation.setResultCode(Action.NONE);
			}
		}
	}
}

即检查request里是否有Attribute,key为Const.ENABLE_BACK_URL,value为True,如果有,则视为当前action方法执行完后需要跳转

而在save方法里我们应该往request里给这个开关变量赋值True
public String save() throws Exception {
	//...
	userService.save(user);
	//...
	ServletActionContext.getRequest().setAttribute(Const.ENABLE_BACK_URL, Boolean.TRUE);
	return RELOAD;
}

save方法里返回RELOAD改不改都无所谓,因为后面BackURLPreResultListener会把这个执行结果改成NONE的

在此基本上整个功能都已实现完毕,但是还有一个小小的缺憾:
一般在save方法里,我们都会加如下代码:来让页面可以展示保存成功的消息:
addActionMessage("保存用户成功!");

因为Struts2里的MessageStoreInterceptor会将这些消息存在session里,等Reload的方法list执行完毕后才会清空,这样在list页面我们依然能取到save方法里放进去的提示消息
而MessageStoreInterceptor判断是否需要将消息存在session里的逻辑是
save方法返回的结果Result是否是redirect类型的,具体可以参考源代码逻辑:

protected void after(ActionInvocation invocation, String result) throws Exception {


	String reqOperationMode = getRequestOperationMode(invocation);
        //result的类型是redirect时才会把actionMessage放入session里
	boolean isRedirect = invocation.getResult() instanceof ServletRedirectResult;
	if (STORE_MODE.equalsIgnoreCase(reqOperationMode) ||
		STORE_MODE.equalsIgnoreCase(operationMode) ||
		(AUTOMATIC_MODE.equalsIgnoreCase(operationMode) && isRedirect)) {


	    Object action = invocation.getAction();
	    if (action instanceof ValidationAware) {
		// store error / messages into session
		Map session = (Map) invocation.getInvocationContext().get(ActionContext.SESSION);


		LOG.debug("store action ["+action+"] error/messages into session ");


		ValidationAware validationAwareAction = (ValidationAware) action;
		session.put(actionErrorsSessionKey, validationAwareAction.getActionErrors());
		session.put(actionMessagesSessionKey, validationAwareAction.getActionMessages());
		session.put(fieldErrorsSessionKey, validationAwareAction.getFieldErrors());
	    }
	    else {
		LOG.debug("Action ["+action+"] is not ValidationAware, no message / error that are storeable");
	    }
	}
}

所以我们知道struts2框架的部分运行逻辑后,可以很容易得出我们的解决方法:
即在struts.xml配置文件中,新增一个全局的result:

<global-results>
	...
	<!-- 在此处定义redirect的backUrl主要是为了利用struts2拦截器MessageStoreInterceptor的便利,即重定向后,actionMessage里的数据都不会丢失 -->
	<result name="backUrl" type="redirect">${#request['backUrl']}</result>
</global-results>

而此处${#request['backUrl']}的 表达式写法可以从request里拿出真正需要跳转的backUrl的地址

我们只要将BackURLPreResultListener修改如下,就可以让我们的跳转经过MessageStoreInterceptor处理

public class BackURLPreResultListener implements PreResultListener {


	@Override
	public void beforeResult(ActionInvocation invocation, String resultCode) {
		// handle backUrl
		HttpServletRequest request = ServletActionContext.getRequest();
		if (Boolean.TRUE.equals(request.getAttribute(Const.ENABLE_BACK_URL))) {
			String backURL = extractBackURL(request);
			if (StringUtils.isNotBlank(backURL)) {
				//此处不使用response.sendRedirect来重定向,主要是为了利用struts2拦截器MessageStoreInterceptor的便利,即重定向后,actionMessage里的数据都不会丢失的功能
				//ServletActionContext.getResponse().sendRedirect(backURL);
				ServletActionContext.getRequest().setAttribute("backUrl", backURL);
				invocation.setResultCode("backUrl");
			}
		}
	}
}


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值