`
javatar
  • 浏览: 1710477 次
  • 性别: Icon_minigender_1
  • 来自: 杭州699号
社区版块
存档分类
最新评论

C/S请求与B/S请求统一MVC控制器

阅读更多
J2EE中基于B/S的MVC框架不少,设计思想也在不停的进步,从Struts1.x开始,每个人都站在巨人的肩膀上,一步一个脚印。
MVC框架主要以控制器为中心,简化模型与视图的交互过程,
控制器要做哪些事?
1. 页面流控制,也就是一级级forward的处理。
2. 接收数据,从表单或URL上传来的数据,需要透明化接收(即:不能让业务逻辑看到接收过程)。
3. 呈现数据,将数据传到页面,或下一forward控制器。
其它的拦截器,前端校验,模板回调,标签库封装等都是附属功能,围绕控制器转。
以业务实现者的角度看,
控制器接口需要有一个传入参数表示接收到的数据,
有两个传出参数,一个表示呈现数据,一个表示页面跳转索引。
这刚好和Java函数的设计相反,函数不允许返回多个参数,怎么办呢?
因为这样,使得控制器接口的设计,变得百家争鸣,各不相同。
Struts(1.x)给出的方案:
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
	// ......
}

它将返回值作为页面流控制,数据的传入传出都用ActionForm,当然用户也可以直接往request里set。
这个设计是比较差的:
1. ActionForward的包装过程应该由框架做,而不是由用户去ActionMapping中查找,用户应该只需返回最简单的String索引号。
2. ActionMapping不应在接口暴露,它不是业务逻辑所关注的,可以将这些附属环境信息使用ThreadLocal锁定在上下文中,如:ActionContext.getContext().getActionMapping();
3. HttpServletRequest, HttpServletResponse不应对用户公开,因为接口已经拥有ActionForm作为数据模型接口,公开Request和Response,只会误导用户绕过框架,也致使框架严重依赖Servlet容器,不便于单元测试,Mock容器是非常痛苦的事情。
4. ActionForm应该为任意POJO,最好用Serializable作为接口声明,使用户可以将实体模型作为表单模型使用。
综上四点可改成:
public String execute(Serializable form) throws Exception {
	// ......
}

再加上Action上下文:
public class ActionContext {
	public static ActionContext getContext() {...}
	public ActionMapping getActionMapping(){...}
	public HttpServletRequest getRequest(){...}
	public HttpServletResponse getResponse(){...}
}

struts1.x出身早,也怪不得。
我们再来看SpringMVC的改进:
Rod Johnson估计是对Struts1.x早就看不惯了,在IoC/DI容器里,搞起了重复发明轮子的事。
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
	// ......
}

SpringMVC最直接了当了,你不是需要返回两个值吗?给你包装一下就是了,ModelAndView,呈现数据模型与页面控制的封装体。
但这里同样存在与Servlet容器高度耦合,测试极不方便,也没做到数据模型透明化传递。
当然,SpringMVC提供模板方法适配,以做到透明化数据接收,和隔离Servlet容器的作用,如SimpleFormController:
public ModelAndView onSubmit(Object command, BindException errors) throws Exception {
	// ......
}

而WebWork(包括Struts2.0)的设计稍微合理一些:

public String execute() throws Exception {
	// ......
}

public void setXXX() {
	// 接收数据
}

public void getYYY() {
	// 呈现数据
}

接口函数的返回值用于页面跳转,传入数据使用setter属性,传出数据使用getter属性,
这个设计是出乎意料的,在其它框架的Action都是单实例线程安全的情况下,它却选择了非单实例线程安全的Action控制器接口,
也就是每一次请求都需要创建新的实例,每个实例只为一次请求服务,
这一选择,使得它不再为函数签名发愁,因为它可以动用整个控制器实例的所有函数,
也使得控制器演变成了命令模式,Action封装了整个执行体,而不是一个服务。

前些天写了一个适用于RCP/RIA应用的MVC框架:
http://javatar.iteye.com/blog/264509
虽然没发布,但也有个雏形了。
这两天在思考与C/S请求与B/S请求统一控制器的问题,也就想了上面所写的一通,
如果struts4rcp也接收传统B/S页面请求,应该怎么设计控制器接口?
不太想采用WebWork的向控制器注入数据的方式,希望将模型与服务域分离。
晚上,终于想到一个比较好的接口设计方案。
接口设计为什么不能动用异常?不就有两个返回值了?如:
public Serializable execute(Serializable model) throws ForwardException {
	// ......
}

跳转异常信息类如下:
/**
 * 请求跳转异常
 * @author <a href="mailto:liangfei0201@gmail.com">liangfei</a>
 */
public class ForwardException extends RuntimeException {

	private static final long serialVersionUID = -2512411385294660606L;

	private final String actionName;

	private final Serializable model;

	/**
	 * @param actionName 跳转Action名称
	 * @param model 跳转传递数据模型
	 */
	public ForwardException(String actionName, Serializable model) {
		this.actionName = actionName;
		this.model = model;
	}

	public String getActionName() {
		return actionName;
	}

	public Serializable getModel() {
		return model;
	}

	@Override
	public String toString() {
		return "forward to:" + actionName;
	}

}

用返回值作数据模型,用异常作页面跳转,当该问search.action,却被跳传到了login.action,这算不算一种异常情况?
按照REST的说法,每个URL代表一个资源,一个URL得到了两种结果算不算异常?
实际项目中,真正在控制器中跳转的非常少,并且通常是success和failure,
而failure,完全可以作为一个异常处理。
另外,可以加入泛型,使接口契约更强,
优化后的接口如下:
/**
 * 数据传输Action接口
 * @author <a href="mailto:liangfei0201@gmail.com">liangfei</a>
 * @param <M> 传入模型类型
 * @param <R> 返回值类型
 */
public interface Action<M extends Serializable, R extends Serializable> {

	/**
	 * 执行Action
	 * @param model 传入参数
	 * @return 传回返回值
	 * @throws Exception 异常均向上抛出,由框架统一处理,包括: ForwardException
	 */
	R execute(M model) throws Exception;

}

请求类型的三种情况:
1. B/S页面请求: 传入参数表示表单(或URL)数据,由框架自动注入到POJO属性中,返回值作为呈现模型,可以在JSP中直接使用。
2. B/S AJAX请求: 通过AJAX请求传入一个JSON串,由框架格式化为一个POJO,返回值同样由框架反格式化为JSON串,客户端JavaScript可以将JSON看成对象的对等形式。
3. C/S请求:客户端直接拿到接口代理进行执行,如:Action<User, Account> action = Actions.getAction("loginAction"); User user = action.execute(account);
以登录为例:
/**
 * 登录控制器
 * @author <a href="mailto:liangfei0201@gmail.com">liangfei</a>
 */
public class LoginAction implements Action<Account, User> {

	private LoginService loginService;

	// IoC注入
	public void setLoginService(LoginService loginService) {
		this.loginService = loginService;
	}

	public User execute(Account account) throws Exception {
		// 如果需要跳转,可以用:throw new ForwardException("xxxAction", account);
		return loginService.login(account.getUsername(), account.getPassword());
	}

}

/**
 * 帐号信息
 * @author <a href="mailto:liangfei0201@gmail.com">liangfei</a>
 */
public class Account implements Serializable {

	private static final long serialVersionUID = 1L;

	private String username;

	private String password;

	public Account() {}

	public Account(String username, String password) {
		this.username = username;
		this.password = password;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

}

/**
 * 用户实体信息
 * @author <a href="mailto:liangfei0201@gmail.com">liangfei</a>
 */
public class User implements Serializable {

	private static final long serialVersionUID = 4544208653256289206L;

	private Long id;

	private String username;

	private String password;

	private String email;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

}

Spring配置:
<bean id="loginAction" class="com.xxx.LoginAction">
    <property name="loginService" ref="loginService" />
</bean>

下面我们分别来看三种请求的调用方式:
1. B/S页面请求:
<form action="loginAction" method="post">
	<input type="text" name="username" />
	<input type="password" name="password" />
	<!--类元信息,若没有class属性,服务器端需用Map接收-->
	<input type="hidden" name="class" value="com.xxx.Account" />
</form>

响应页面:(可直接使用User对象的属性)
您好,${username},您的邮箱是:${email}

2. B/S AJAX请求:
var account = {username: "james", password: "123456", class: "com.xxx.Account"}; // 传入JSON数据模型,若没有class属性,服务器端需用Map接收
var loginAction = Actions.getAction("loginAction");
var user = loginAction.execute(account); // 执行,并得到JSON结果
alert(user.email);

3. C/S请求:
Account account = new Account("james", "123456"); // 传入数据模型
Action<Account, User> loginAction = Actions.getAction("loginAction"); // 看起像拿到了服务器端Action的引用(即透明化)
User user = loginAction.execute(account); // 执行,并得到User对象
System.out.println(user.getEmail());

在泛型的支持下,客户端与服务器端都不需要做任何强制转型,语义更明确。
分享到:
评论

相关推荐

    Spring MVC请求参数与响应结果全局加密和解密详解

    在控制器方法中,我们可以使用@RequestBody注解来处理请求参数,并使用EncryptUtils工具类来进行加密和解密。例如: ```java @PostMapping("/order/save") public String saveOrder(@RequestBody String orderId, @...

    基于java的MVC模式的B/S电子商务-当当网

    在IT行业中,构建基于Java的B/S(Browser/Server)电子商务系统是一项常见的任务,而"基于java的MVC模式的B/S电子商务-当当网"项目则是这一领域的实例。在这个项目中,开发者利用了MVC(Model-View-Controller)设计...

    经典MVC登录实例代码

    控制器接收用户的请求,处理这些请求,并与模型和视图进行交互。 2. **Servlet的角色** 在MVC架构中,Servlet通常作为控制器来使用。当用户发送HTTP请求时,Servlet接收这些请求,处理后端逻辑,并将结果转发给...

    C#MVC控制器前后端传值

    本篇将深入探讨C# MVC控制器如何实现前后端的数据交互。 一、基础知识 1. MVC模式:这是一种设计模式,将应用程序分为三个主要部分:模型(Model)、视图(View)和控制器(Controller)。模型处理业务逻辑和数据,...

    ASP.NET在MVC控制器中获取Form表单值的方法

    本文实例讲述了ASP.NET在MVC控制器中获取Form表单值的方法。分享给大家供大家参考,具体如下: 在MVC控制器中,如果我们想直接获取表单中某个标签元素的值,可以使用MVC中提供的FormCollection类,具体用法如下所示...

    MVC多层架构

    在VS2012中,视图通常由ASP.NET MVC框架的Razor视图引擎生成,用于显示来自控制器的数据。在这个仓库管理系统中,视图可能包含多个页面,如商品列表、订单详情、库存报告等,它们提供用户友好的界面,用于输入和展示...

    拦截器与冲突解决

    它是Spring MVC配置中的一个便捷元素,用于自动配置基于注解的控制器(@Controller)和数据绑定、异常处理等特性。它会开启对HTTP请求方法(如GET、POST)的自动处理,并且启用像@RequestParam、@PathVariable等注解...

    一个基于J2EE的B/S结构的简单学生管理系统

    1. **控制器**:接收HTTP请求,调用业务逻辑,然后将结果传递给视图进行展示。 2. **Action类**:实现了具体的操作逻辑,如添加、修改、删除学生信息等。 3. **配置文件**:定义了URL映射,关联请求与Action,以及...

    理解PHP中的MVC编程之控制器

    总结来说,控制器是MVC架构中连接模型和视图的桥梁,它处理用户请求,协调模型和视图的工作,确保应用程序的逻辑流畅且有序。在PHP中,通过精心设计的控制器,开发者可以构建出高效且结构清晰的Web应用程序。

    jsp/mvc/mysql外文文献

    模型对象负责处理数据,与数据库交互,并在数据改变时通知控制器。 - View(视图):显示数据给用户。视图通常是从模型获取数据并渲染成用户界面的部分。 - Controller(控制器):接收用户的输入,处理请求,更新...

    小型员工管理开发 C/S

    在这个项目中,模型(Model)负责处理数据和业务逻辑,视图(View)显示信息,而控制器(Controller)处理用户输入并协调模型和视图。通过这种方式,MVC使得代码更易于维护和扩展。 2. ADO.NET: ADO.NET是.NET...

    java基于 B/S 学生成绩管理系统

    3. **MVC模式**:MVC模式是软件工程中一种常见的设计模式,它将应用分为模型(Model)、视图(View)和控制器(Controller)三个部分。在学生成绩管理系统中,模型负责管理数据和业务逻辑,视图负责展示数据,控制器...

    基于C/S的在线考试系统

    在MVC模式下,模型负责存储和更新考试数据,视图负责呈现这些数据给用户,而控制器接收用户操作并协调模型和视图的更新。例如,当用户点击“提交”按钮时,控制器会将答案发送到服务器,服务器验证答案后更新模型,...

    基于B/S的英语学习网站

    在MVC模式中,模型(Model)负责处理业务逻辑和数据管理,视图(View)负责展示用户界面,控制器(Controller)则协调模型和视图,接收用户的请求并转发到相应的处理方法。Struts框架通过配置文件和Action类来实现这...

    基于B/S的客户管理系统

    3. Controller(控制器)作为模型和视图之间的桥梁,接收用户的请求,处理业务逻辑,并更新模型或指示视图进行相应的显示。 本系统采用C#作为后端编程语言,这是一种面向对象的、类型安全的编程语言,广泛应用于...

    spring mvc 拦截器获取请求数据信息.rar

    在Spring MVC框架中,拦截器(Interceptor)是一个强大的工具,用于在请求被控制器处理之前或之后执行特定的逻辑。它们可以用来实现权限检查、日志记录、性能统计等多种功能。本压缩包“spring mvc 拦截器获取请求...

    s:mvcURL如何使用

    在Spring MVC 4.1.x框架中,`mvcURL`是一个关键的概念,它涉及到控制器(Controller)与视图(View)之间的交互。本篇将详细解释`mvcURL`的使用方法,以及它如何帮助我们构建高效、灵活的Web应用程序。 首先,`mvc...

    SpringMVC源码总结(三)mvc:annotation-driven和mvc:message-converters简单介绍

    在Spring MVC框架中,`mvc:annotation-driven`和`mvc:message-converters`是两个非常重要的元素,它们在处理基于注解的控制器和数据转换方面起着关键作用。本篇文章将深入探讨这两个组件的工作原理以及如何在实际...

    Spring MVC请求映射常见的三种方式

    **注解驱动的映射**是Spring MVC最常用的方式,它通过在控制器类的方法上使用`@RequestMapping`注解来定义请求路径。例如: ```java @Controller public class MyController { @RequestMapping("/hello") public...

    用 Filter 作为控制器的 MVC

    本文将深入探讨如何在Struts2中使用Filter作为控制器的MVC实现。 首先,理解Filter在Java Web中的作用至关重要。Filter是Servlet规范的一部分,它允许开发者在请求到达目标Servlet或JSP之前以及响应离开之后对其...

Global site tag (gtag.js) - Google Analytics