写给java web一年左右工作经验的人
我把我这些年在java学习中学到的东西,按照项目开发中可能遇见的场景,进行了一次梳理。

这个故事是我{zh1}决定加上来的,我非常喜欢这个故事,软件工程中有一个被戏称为Cargo Cult编程法的编程风格,而下面这个故事讲述了此编程法的来源:
早在40年代,据说,美军曾驻扎在一个偏远的岛屿。岛上的土著居民在此以前从未见过的现代文明,所以,他们对联军和他们带来的东西非常惊奇。他们发现联军修建了机场跑道和控制塔,带着耳机的士兵对天呼叫,然后满载着大量货物的大铁鸟便从天而降。当铁鸟降落后,货物便分发给所有岛上的人们,为人们带来繁荣。
终于,有{yt},xx离开了,大铁鸟也不再回来了。为了再次得到货物,岛上的土著居民用竹子建造了自己的跑道,控制塔,让他们的头领登上平台,并让他戴上用椰子做的耳机。但无论他们如何努力尝试,大铁鸟再也没有回来。
几十年后,研究人员发现了该岛。岛上的土著居民仍旧保留着这一宗教仪式。他们把岛上居民的这一奇怪的宗教仪式命名为“Cargo Cult”

@考虑这样一个应用场景:我们的项目功能日渐强大,代码却日渐臃肿,我们如何将代码变得有条理些?
无论我们是学习还是工作,我们的前辈总是会告诉我们,我们需要把java项目进行架构上的分层,界面层(UI)业务逻辑层(BLL) 数据访问层(DAL),就像Cargo Cult中的土著居民一样,虽然我们并不知道为什么高手们要那样做,但是我们相信这么做可以让程序工作起来,后面我会讲到为什么会有所谓的经典的三层架构,现在我们已经做完的事是,按照三层架构将项目搭建并运行起来了!

我们一般会想到MVC,很多http://cici1949.cn.qiyeku.com都有说到,可惜很多时候我们理解的MVC是错误的。。。甚至我曾经天真的以为,MVC正好对应着DAL,UI,BLL。。。实际上,这两者并没有显式的关系,前者属于设计模式,而后者属于架构设计的范畴,如果一定要扯到一起的话,关系可能会是这样的:

 
http://cici1949.cn.qiyeku.com

实际上,Controller是很薄的一层,它仅仅负责接收参数,封装参数,调用不同的service。所以我们可以考虑先从它入手,简化代码。
我们现有的controller做法是,不同的业务调用不同的servlet,通过参数的不同,调用不同的方法,比如,有下面一个form

http://cici1949.cn.qiyeku.com

1

2

3

4

<formaction=”UserServlet?command=login”>

<inputname=”name”/>

<inputname=”password”/>

</form>

如果我们在这个表单中输入用户名密码,最终后台会将这个请求提交到UserServlet中,然后根据command=login,调用UserServlet中的login方法。在login方法中,会有这样的一段代码:

http://cici1949.cn.qiyeku.com

1

2

3

4

5

6

7

8

Stringname=request.getAttribute(“name”);

Stringpassword=request.getAttribute(“password”);

Booleanresult=userService.hasUser(name,password);

if(result){

....

}else{

....

}

我们可以发现,基本上这个servlet中,所有的方法几乎都有着从request中获取参数的这么一个过程,而且同一个servlet中,需要获取的参数大部分都是重叠的(比如UserServlet中,几乎所有的方法都需要获取name和password的值,才能进行近一步操作),既然每一个方法都有这么一个需求,为什么不考虑将这一过程抽象出来呢?
首先,我们可以设计一个叫AaronDispatcher的类,它负责截取了所有的对项目的访问的http请求。
比如,我们上面的请求叫UserServlet?command=login,同时传递三个参数name和password(以及上面的command) 。AaronDispatcher巨牛叉,它直接把这个请求截取了,并进行分析,首先它的名字叫UserServlet,调用的方法叫login。为了不引发歧义,我们更改前台的请求地址,改为发送到UserAction?command=login。
然后我们可以重新设计UserServlet,创建全新的UserAction。(现已加入豪华午餐)

http://cici1949.cn.qiyeku.com

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

publicUserAction{

Stringname;

Stringpassword;

Stringnewpassword;//updatePassword这个方法需要

Stringoldpassword;

......//所有的UserAction从前端获取的参数

publiclogin(){

...

}

publiclogout(){

...

}

publicupdatePassword(){

...

}

......//所有UserAction需要提供的方法

}//UserAction结束

(眼疾手快的人也许可以注意到一点:这个类不再需要接受HttpServletRequest及HttpServletResponse作为参数了)
每当我们有一个发送到UserAction的请求,AaronDispatcher就帮我们new一个新的UserAction实例,同时将请求中的参数赋给UserServlet中的属性。具体的底层做法,类似于下面这样:(实际上会复杂很多,不会直接new对象,而是使用反射来创建对象并赋值属性)

http://cici1949.cn.qiyeku.com

1

2

3

4

5

6

UserActionuserAction=newUserAction();

userAction.setName(request.getAttrbute(“name”));

userAction.setPassword(request.getAttrbute(“password”));

userAction.setNewpassword(request.getAttrbute(“newpassword”));

userAction.setOldpassword(request.getAttrbute(“oldpassword”));

......

如果我们需要http://cici1949.cn.qiyeku.com登陆功能,直接调用userAction.login()就可以了(至于name和password,直接可以在方法内部获取当前对象属性)。所有的方法中,从request中获取参数并进行封装的这么一个过程,全部都被巨牛叉的AaronDispatcher做了,是不是减少了很多重复的代码量?!
可能会有疑虑,所有的请求,无论什么方法,都进行一次属性全赋值,如果前台没有传入这个属性,不就为空了嘛?但是要知道,如果我们调用login这个功能,newpassword和oldpassword固然会因为前台没有相应的属性值传入而设为null,但是,但是,在login方法中,我们根本就不会用到这两个参数啊!所以即使为空也不会有错的!
甚至我们可以做的再牛叉一点,AaronDispatcher会去读取一段配置文件,配置文件中指定了什么样的请求调用什么养的类以及相应的方法,这样我们就可以彻底解耦最前方的Controller了!
但是AaronDispatcher是怎么做到无论什么类,当中有什么属性,我们都不需要事先知道,我们都可以接收前端参数,给他们的属性赋值呢?(答案是通过反射)
现在,我们已经成功的重新发明轮子了!
因为以上这个伟大的想法已经有被别人抢在前面实现了,就是xx的Struts2,毋庸置疑,Struts2的核心功能就是这么简单。
在Struts2中,每一个处理类被称之为Action,而Struts2也正是通过xml配置文件,实现了无需要修改代码,通过修改配置文件,就可以修改Controller。
Struts2发展到今天已然是一个功能齐全的庞然大物了。
正如一开始所说,MVC框架只不过帮助我们封装了请求参数,分发了请求而已。Controller是非常薄的一层,而我们的业务逻辑都是由BLL层提供的Service对象实现。
首先讲述一下为什么会有所谓BLL(Business Logic Layer)和DAL(Dataaccess Layer)了。在一个项目中,无论是查询用户的用户名,还是查询库存数量,这些数据终归是要保存到数据库的,而这些对数据库的操作将会xx的频繁,如果我们不将这些对数据库表的操作独立出来,如果在多个方法中存在着对一个用户记录的查询,我们不得不把这段代码copy、paste无数次,既然这样,我们为什么不像上面那样,将这种可能会多次遇到操作抽象出来呢?于是就有了所谓的DAL了,这样,无论在什么地方,需要用到数据库查询相关的工作的时候,仅仅需要这么做:

http://cici1949.cn.qiyeku.com

1

2

Useruser=userDaoImp.getUserById(userId);

......

这么做有一个好处:减少了因为持久化方案的更换而导致的代码修改带来的工作。
持久化是一个非常xx大气的专业术语,说的更专业一点,就是将内存中的数据保存到硬盘中。在我们的项目中,用户进行了注册,我们需要将用户注册的用户名密码保存起来,以便下次用户登陆的时候我们能够知道,这个用户名的用户是合法注册过的。
通常持久化的方案就是将数据保存到数据库中,但是我相信如果我不愿意使用数据库,而直接将用户名密码明文保存到文本文件中,也没有人会从技术上反对吧(实际上这种事情在中国互联网的发展历史中还真发生过。。。),如果我真的选择这么做,我所需要做的工作就是仅仅修改DAL中的实现,将对数据库的操作改为对本地文件的操作,而无须修改调用持久化方法的方法。
业务层负责业务的处理(接收上层传过来的信息进行处理),当处理完之后,将处理的结果利用DAL的对象进行“保存到硬盘”。而DAL具体是怎么实现的,xx不会影响到已实现的业务。
很明显的,为了做到上面这一点,DAL中的方法要尽量的“单纯”,不包含任何的业务上的逻辑,仅仅是将内存中的数据(一般就是某个对象)保存到硬盘的“实现”,以及从硬盘读取的数据提取到内存的“实现”。
已经很明显了,三层架构不是从来都有的,只不过是在无数次痛苦的经历过后先烈们总结出来的一套证明可以在某一方面减少因变动而带来的额外工作量。说它经典,也只不过是因为它实现了展示、业务、持久化这三个必不可少却又相对对立的需求的切割(不过确实有的项目中,展示不是必选的)。
所以基本上所有的复杂架构也只不过是在此基础上的进一步分割,曾经做过一个巨复杂SaaS项目,为了减少某些不定因素的变动而带来的代码上的改动,架构师将BLL分成了两层,在原有的BLL之上又增加了一层core business layer。这样MVC框架只需要调用core business的业务而无须自己在重复组装比较底层的业务逻辑了。
如果有更复杂些的项目的话,就需要通过分割子项目及更复杂的层级关系来解决了。
这个时候我们或许应该讲述BLL了,不过在此之前,我们可以再多想一步,能不能修改DAL中的东西,让我们使用起来更简单?
一般来说,数据库中的表对应着java中的类,表中的一行记录对应着一个entity对象,表中的字段对应着对象中的属性,我以前一直觉得很神奇,这就是传说中的ORM。这当中还有很多更复杂的东西,比如多表级联的结果映射为对象,在这里我们先忽略这些复杂的情况。
有了上面的知识,我们可以发现, 如果我们选择关系型数据库作为持久化方案,我们的DAL其实也很“单纯”,他们所做的也不过是将对象属性通过sql存储到数据库、将通过sql获取的数据封装为对象。
同样的我们可以写一个巨牛叉框架(好吧,这次不是写一个巨牛叉的类了),它会自动根据我们entity的名字,去数据库寻找相应的表,当我们调用insert,delete,update,select等方法的时候,它会自动帮助我们根据要求及参数拼接sql,然后去数据库查询/修改记录,如果是查询,则把查询出来的记录集封装成对象,保存在list中。这样,我们就可以在DAL中简单的定义一些entity就可以了。


郑重声明:资讯 【写给java web一年左右工作经验的人】由 发布,版权归原作者及其所在单位,其原创性以及文中陈述文字和内容未经(企业库qiyeku.com)证实,请读者仅作参考,并请自行核实相关内容。若本文有侵犯到您的版权, 请你提供相关证明及申请并与我们联系(qiyeku # qq.com)或【在线投诉】,我们审核后将会尽快处理。
—— 相关资讯 ——