Web 程序构架(web application architecture)
一、MVC
MVC构架最早由Trygve Reenskaug在smalltalk工作时提出,最早应用于桌面程序。他将表现层从组件中分离出来。
组件 | 描述 |
---|---|
模型层(Model) | view渲染所需要的数据。如订单数据,产品列表之类的。 |
视图层(View) | 使用模型层对自身进行渲染的部分。可以是jsp或jsf页面,也可以是一个pdf或xml文件。 |
控制层(Controller) | 对用户的行为进行相应的组件。这些行为包括点击提交按钮、点击超链接等。这一层的组件更新model层,以及响应其他的需要行为,例如调用service,放置一个订单。 |
但Model1的MVC直接应用于Web程序却存在一定困难,因为http协议是一种无状态的协议,保持一个model存在困难。
*因为无状态,所以跨请求同一个model存在困难。php等程序使用mvc存在性能瓶颈问题就是因为没有有效解决这一问题,java中是使用容器的概念解决这一问题的。
于是有了Model2模型,这个模型里改进了controll层,引入了前端controller,其中的一个controller可以将一个请求发送到另外一个controller上。control 层中的一个controller响应一个request,返回一个model,并选择一个view。
model2模式的构架图:
这里面的前端控制器(front controller)就是接受请求的controller。它将把请求委托给适当的controller,当被委托的controller完成请求后,front controller根据返回的数据选择合适的view。在许多情况下,这个前端控制器由 javax.servlet.Servlet servlet来实现(例如 structs 里面的 ActionServlet 和 JSF 里面的 FacesServlet )。在spring mvc 里面,这个front controller 就是 org.springframework.web.servlet.DispatcherServlet;
*其中的一个重要划分原则:View层不负担任何的业务逻辑和数据访问逻辑,就是把model在View中渲染出来,以 html , xml , pdf , json 等形式返回到浏览器。
mvc 结构的 Web 程序的构架图:
mvc 结构的Web程序构架概览:
表现层 | 表现层应该尽可能的薄。它作为一个表现变化的层次。应提供一个可变化的web-前端,甚至仅仅是一个web server 的包装。一个设计良好的server层通过表现层来进行操作。 |
服务层 | 实际的包含业务逻辑的调用点。service层:提供调用系统的一个粗糙接口;提供事务边界。持久层以及表现层对于这个层来说,是透明的。 |
数据访问层 | 一个提供数据访问支持的接口,不对上层暴露(透明)。在这一层李,对实际的持久层进行抽象(如:jdbc、JDO、JPA)。注意访问层不提供业务逻辑。 |
各层之间的通信是通过自上而下的单向调用。也就是说表现层可以调用服务层,但服务层不可以调用表现层,等等。在设计中各层要避免循环依赖,因为循环依赖会造成维护困难。
实际应用中,一个典型的系统在概念上可以切出5个层面,如表现层可以切成web层和用户层两个接口。同时程序应包含领域层(domain layer)。领域层横贯系统各个层次,因为从数据访问层到用户层都需要被访问。
典型的系统分层:
实际系统中举例:
com.apress.bookstore.domain :领域层
com.apress.bookstore.service :服务层
com.apress.bookstore.repository :数据访问层
一个实际系统的结构图
分离关注点:
好处:有助于设计、变更以及测试。
在设计中添加新层的原则:如果一个层对于其他的层依赖太多,那么就引入一个层来和其他的各层交互。如果一个层看起来与其它层始终是一个独立的层,那么我们就要重新考虑将其作为程序的一个切面,在spring中,专门用AOP来处理这个问题。
注意:spring中AOP可以是一个基于类的代理,在这种情况下,spring使用了cglib库。
好了言归正传,说说在spring中的层次划分:
1、领域层
程序中用到的实体类。
2、用户界面层
将service层计算结果渲染传给客户端。例如,对于web程序,传回html文档,对于另外一个web程序,传回xml数据,对于一个客户端,传回pdf文件等。
spring支持如下表现层类型:
1、pdf
2、jsp
3、excel
4、freemarker
5、velocity
6、jasperReports
7、Tiles
8、xml
9、json
用户界面层一般依赖于领域层。在实际使用中,我们常常暴露领域层,并直接渲染领域层的数据,这在大量使用form的情况下尤其常见。对于这一点,有广泛争议,当地应不应该呢,直接访问可以降低系统的复杂度,但不直接访问又可以提供程序的弹性。不过在spring-mvc中,并不提倡我们直接暴露领域层的情况。
Web层:
web层有两个职责:其一是引导用户访问整个应用。其二整合service层http协议(从这点看,web层更像是一个facade,一家之言)。
典型的,web层提供浏览导航的功能,相关的逻辑不能出现在service层和领域层(如web分页中,常常出现一个page对象,这类对象只能放在web层整,不能发在领域层,在设计中常常发现这类错误)。web层的浏览导航功能是通过url来实现的(现在流行restful设计,对良好的url设计很有帮助)。
在设计中,web层应该尽可能的薄,他将用户的请求转化为service层所需要的数据,将处理的结果转化并返回给用户界面。web层中不能包含任何业务逻辑,这是service层干的事儿。
web层中也应该保持cookies,会话 (http session),http头(http head)。管理这类对象的事儿也应该web层来做,不能让他们蔓延到service层中去。例如在service层中管理一个session,肯定设计上就有错误。在设计中注意这一点,才能设计出测试性良好且易于复用的service层。例如,我们通过java消息驱动(jms-driven)去调用另外一个系统,如果另外一个系统包含了session代码,那么几乎就不可用。
对于java开发者来说,servlet、jsp为编写web层提供了很大的方便。
几大框架:spring mvc、struct、tapestry,jsp都对对于mvc模式都提供了支持,但实现上有很大不同。我们可以将其划分为两类:
一是 请求\响应(request\response)类的,如spring mvc 和 struct。二是基于组件(component-base)的,例如tapestry和jsf。请求响应类的框架通过操作javax.servlet.ServletResponse类来实现,所有的servlet API对于这类框架是暴露的。而基于组件的框架则试图隐藏相关的API,给用户提供一个类似于swing的编程界面。
spring mvc 在请求/响应类框架以及基于组件的框架之间保持了一个平衡。他将相关的servlet api隐藏起来,但如果需要的话,也可以很方便的访问到他们。
web层依赖于domain层和service层,有时候,我们需要将一个请求映射为一个domain对象,然后调用service层处理domain对象。在spring mvc 中,可以很方便的讲request映射为一个domain对象。
在spring mvc中,web层通过org.springframework.web.servlet.mvc.Controller接口或者org.srpingframework.stereotype.Controller注解来实现。
在执行Controller开始执行之后,需要有一个org.springframework.web.servlet.ModelAndView类的实例。这个实例组装model(通过org.springframework.ui.ModelMap),向视图返回渲染的结果.这个视图是org.springframework.web.servlet.View接口的实现或者一个view的名称。
注意:不要把@Controller注解用于实现了Controller接口的类,会带来难以预料的后果。
服务层层(service layer):
服务层在程序中是非常重要的层,可以看做是整个应用程序的心脏,为用户提供了系统的各项功能。为系统提供了各项原始功能。
一个service的例子:
package com.apress.prospringmvc.bookstore.service;
import com.apress.prospringmvc.bookstore.domain.Account;
public interface AccountService {
Account save(Account account);
Account login(String username, String password) throws AuthenticationException;
Account getAccount(String username);
}
这个service提供为实现一个用例提供了基本的操作。此外,还可以加入void updateLastLogin(Account account)方法对用户进行更新。
一个service层可以对多种客户端提供服务,为一个系统提供唯一的入口。
数据访问层 (data access layer):
存取数据的层,这个层的实现目的是避免service层与具体的持久化方法相关。
分离出数据访问层的目的:1、服务器层不需要了解持久化的知识(最少知识原则),使数据存储对服务层透明化。2、便于测试。数据访问的速度一般是比较慢的,将这层抽离出来单独测试后,测试服务层就不必使用数据访问层的代码,提高了测试速度。
spring框架为整合data access layer 的实现提供了方便(jdbc,jpa,hibernate,ibatis,jdo)。对于这些数据访问层,spring的相关扩展提供了三个功能:
1、事物管理。
2、资源处理。
3、异常转换。
事物管理甚至支持JTA(分布式或全局式事物管理)。资源管理使我们不必担心连接关闭的问题,由框架自动处理。
相关的实现在org.springframework.jdbc 以及org.springframework.orm包中。
异常转换将数据访问的相关异常转化为org.springframework.dao.DataAccessException,这个异常继承自RuntimeException,是UnChecked的异常。
一个Repository例子:
package com.apress.prospringmvc.bookstore.repository;
import com.apress.prospringmvc.bookstore.domain.Account;
public interface AccountRepository {
Account findByUsername(String username);
Account findById(long id);
Account save(Account account);
}