整理写给java web一年左右工作经验的人Word格式文档下载.docx
《整理写给java web一年左右工作经验的人Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《整理写给java web一年左右工作经验的人Word格式文档下载.docx(19页珍藏版)》请在冰点文库上搜索。
![整理写给java web一年左右工作经验的人Word格式文档下载.docx](https://file1.bingdoc.com/fileroot1/2023-5/6/e68b464c-0819-4e5a-934d-8a8f0b8b99e2/e68b464c-0819-4e5a-934d-8a8f0b8b99e21.gif)
在login方法中,会有这样的一段代码:
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。
(现已加入豪华午餐)publicUserAction{
Stringname;
Stringpassword;
Stringnewpassword;
//updatePassword这个方法需要
Stringoldpassword;
......//所有的UserAction从前端获取的参数
publiclogin(){
...
}
publiclogout(){
publicupdatePassword(){
......//所有UserAction需要提供的方法
}//UserAction结束眼疾手快的人也许可以注意到一点:
这个类不再需要接受HttpServletRequest及HttpServletResponse作为参数。
每当我们有一个发送到UserAction的请求,AaronDispatcher就帮我们new一个新的UserAction实例,同时将请求中的参数赋给UserServlet中的属性。
具体的底层做法,类似于下面这样:
(实际上会复杂很多,不会直接new对象,而是使用反射来创建对象并赋值属性)UserActionuserAction=newUserAction();
userAction.setName(request.getAttrbute(“name”));
userAction.setPassword(request.getAttrbute(“password”));
userAction.setNewpassword(request.getAttrbute(“newpassword”));
userAction.setOldpassword(request.getAttrbute(“oldpassword”));
......如果我们需要登陆功能,直接调用userAction.login()就可以了(至于name和password,直接可以在方法内部获取当前对象属性)。
所有的方法中,从request中获取参数并进行封装的这么一个过程,全部都被巨牛叉的AaronDispatcher做了,是不是减少了很多重复的代码量?
!
可能会有疑虑,所有的请求,无论什么方法,都进行一次属性全赋值,如果前台没有传入这个属性,不就为空了嘛?
但是要知道,如果我们调用login这个功能,newpassword和oldpassword固然会因为前台没有相应的属性值传入而设为null,但是,在login方法中,我们根本就不会用到这两个参数啊!
所以即使为空也不会有错的!
甚至我们可以做的再牛叉一点,AaronDispatcher会去读取一段配置文件,配置文件中指定了什么样的请求调用什么样的类以及相应的方法,这样我们就可以彻底解耦最前方的Controller了!
但是AaronDispatcher是怎么做到无论什么类,当中有什么属性,我们都不需要事先知道,我们都可以接收前端参数,给他们的属性赋值呢?
(答案是通过反射)现在,我们已经成功的重新发明轮子了!
因为以上这个伟大的想法已经有被别人抢在前面实现了,就是著名的Struts2,毋庸置疑,Struts2的核心功能就是这么简单。
在Struts2中,每一个处理类被称之为Action,而Struts2也正是通过xml配置文件,实现了无需要修改代码,通过修改配置文件,就可以修改Controller。
Struts2发展到今天已然是一个功能齐全的庞然大物了。
正如一开始所说,MVC框架只不过帮助我们封装了请求参数,分发了请求而已。
Controller是非常薄的一层,而我们的业务逻辑都是由BLL层提供的Service对象实现。
首先讲述一下为什么会有所谓BLL(BusinessLogicLayer)和DAL(DataaccessLayer)。
在一个项目中,无论是查询用户的用户名,还是查询库存数量,这些数据终归是要保存到数据库的,而这些对数据库的操作将会无比的频繁,如果我们不将这些对数据库表的操作独立出来,如果在多个方法中存在着对一个用户记录的查询,我们不得不把这段代码copy、paste无数次,既然这样,我们为什么不像上面那样,将这种可能会多次遇到操作抽象出来呢?
于是就有了所谓的DAL了,这样,无论在什么地方,需要用到数据库查询相关的工作的时候,仅仅需要这么做:
Useruser=userDaoImp.getUserById(userId);
......这么做有一个好处:
减少了因为持久化方案的更换而导致的代码修改带来的工作。
持久化是一个非常高端大气的专业术语,说的更专业一点,就是将内存中的数据保存到硬盘中。
在我们的项目中,用户进行了注册,我们需要将用户注册的用户名密码保存起来,以便下次用户登陆的时候我们能够知道,这个用户名的用户是合法注册过的。
通常持久化的方案就是将数据保存到数据库中,但是我相信如果我不愿意使用数据库,而直接将用户名密码明文保存到文本文件中,也没有人会从技术上反对吧(实际上这种事情在中国互联网的发展历史中还真发生过。
),如果我真的选择这么做,我所需要做的工作就是仅仅修改DAL中的实现,将对数据库的操作改为对本地文件的操作,而无须修改调用持久化方法的方法。
业务层负责业务的处理(接收上层传过来的信息进行处理),当处理完之后,将处理的结果利用DAL的对象进行“保存到硬盘”。
而DAL具体是怎么实现的,完全不会影响到已实现的业务。
很明显的,为了做到上面这一点,DAL中的方法要尽量的“单纯”,不包含任何的业务上的逻辑,仅仅是将内存中的数据(一般就是某个对象)保存到硬盘的“实现”,以及从硬盘读取的数据提取到内存的“实现”。
已经很明显了,三层架构不是从来都有的,只不过是在无数次痛苦的经历过后先烈们总结出来的一套证明可以在某一方面减少因变动而带来的额外工作量。
说它经典,也只不过是因为它实现了展示、业务、持久化这三个必不可少却又相对对立的需求的切割(不过确实有的项目中,展示不是必选的)。
所以基本上所有的复杂架构也只不过是在此基础上的进一步分割,曾经做过一个巨复杂SaaS项目,为了减少某些不定因素的变动而带来的代码上的改动,架构师将BLL分成了两层,在原有的BLL之上又增加了一层corebusinesslayer。
这样MVC框架只需要调用corebusiness的业务而无须自己在重复组装比较底层的业务逻辑了。
如果有更复杂些的项目的话,就需要通过分割子项目及更复杂的层级关系来解决了。
这个时候我们或许应该讲述BLL了,不过在此之前,我们可以再多想一步,能不能修改DAL中的东西,让我们使用起来更简单?
一般来说,数据库中的表对应着java中的类,表中的一行记录对应着一个entity对象,表中的字段对应着对象中的属性,我以前一直觉得很神奇,这就是传说中的ORM。
这当中还有很多更复杂的东西,比如多表级联的结果映射为对象,在这里我们先忽略这些复杂的情况。
有了上面的知识,我们可以发现,如果我们选择关系型数据库作为持久化方案,我们的DAL其实也很“单纯”,他们所做的也不过是将对象属性通过sql存储到数据库、将通过sql获取的数据封装为对象。
同样的我们可以写一个巨牛叉框架(好吧,这次不是写一个巨牛叉的类了),它会自动根据我们entity的名字,去数据库寻找相应的表,当我们调用insert,delete,update,select等方法的时候,它会自动帮助我们根据要求及参数拼接sql,然后去数据库查询/修改记录,如果是查询,则把查询出来的记录集封装成对象,保存在list中。
这样,我们就可以在DAL中简单的定义一些entity就可以了。
比如,我们这次在DAL中,仅仅只定义了一个类:
publicUser{
longid;
}是的,剩下的事全部交给这个巨牛叉的框架来做了,当我们需要在UserService中查询一个用户记录的时候,我们只需要这么做:
//我们已经假设了框架巨牛叉,所有的DAL对象都是可以根据entity自动生成
AaronDaouserDao=newAaronDao(User.class);
List&
User&
list=userDao.list(......)//括号里面是一些列条
件,比如一些分页条件啊,属性限制啊之类的。
哇~,生活瞬间变得很美好,这个巨牛叉的框架太棒了,只要定义好entity就可以完成以前需要成千上百行才能完成的功能,我连数据库的刷库脚本都不用写了~这么巨牛叉的框架一定会帮我们做好这些的。
我恨不得马上就使用这个框架来开发我下的一个项目。
只可惜java中还真没有这种框架。
实际上java的JDBC远比想象中的复杂(主要是因为作为强异常处理的java语言,为了完成一次对数据库的操作,需要编写的异常处理代码实在太多了),还有事务的处理,数据库连接的开启和释放(甚至连接池的配置),等等等等。
如果使用SpringJDBC或者HibernateORM框架,可以帮助我们不需要花太多精力放在非核心的处理上,专注于数据的读/写。
即使这样,需要写的代码还是太多了。
因为每一个entity我们都必须手动编写相应的Dao类(注:
操作entity的类),我又开始怀念那个巨牛叉的框架了。
如果大家有兴趣,倒是可以尝试一下实现我上面所形容的据牛叉的DAL框架。
实际上,真的有人在Ruby中实现了类似于上述的数据库操作框架,在RubyOnRails中,我们仅仅只需要定义一些普通的entity,而剩下的操作都是ror自动生成,当年DHH(ror的作者)在网上发出15分钟从零开始完成博客系统的视频,整个Web世界都被惊叹到了。
在正式讲到BLL之前,咱们先怀念一下最原始的servlet,是的,就是它刚刚出生的时候的事。
最早的时候,sun在提出servlet规范的时候,很多人是直接在servlet中完成接收参数,处理业务,从数据库或者本地文件中读取数据,最后打印出html返回。
这个过程,大概会长成这个样子:
response.getWriter().println(“&
html&
”);
response.getWriter().println(“hello”+user.name);
/html&
不用怀疑,JSP出现之前很多大神早期真的都这么做过。
显然的,后来对数据库的操作被剥离出来作为DAL了,那为什么还要剥离BLL呢?
如果我们不剥离service对象(BLL),如果我们需要增加用户,我们需要在UserServlet中这样写:
publicvoiddoPost(HttpServletRequestrequest,HttpServletResponseresponse){
Strringcmd=request.getAttribute(“command”);
if(“addUser”.equals(request.getAttribute(cmd))){
Stringname=request.getAttribute(“name”);
Stringpassword=request.getAttribute(“password”);
Useruser=newUser(name,password);
if(userDaoImp.getUserByName(“name”)!
=null){
userDaoImp.addUser(user);
......//返回添加成功页面
}else{
......//返回添加失败页面
}elseif(其他command){
......
}这当中只有一个简单的逻辑:
如果用户名已经存在,则返回到错误页面,告知用户添加新用户失败。
如果用户名不存在,则返回到正确页面,告知用户添加新用户成功。
如果我们想测试我们所写的代码是否正确,会遇到一个前所未有的困难,需要模拟HttpServletRequest和HttpServletResponse对象以及一个UserServlet对象。
我们很难脱离浏览器环境测试这个类。
(实际上可以通过mock来模拟这个过程,这个后面会说到)简单的办法就是写好前端页面,然后通过在页面上去添加一个用户,看看是不是真以上逻辑正确。
天呐。
这才是一个简单的业务逻辑,我们就不得不等到有了前端页面之后才能去测试它,如果我们的逻辑更复杂点,等到前端都做完了才能发现有后台有bug,而且即使修改完了还需要回头通过浏览器再测试一遍,如果是通过页面不断地手动输入用户信息这种,估计一天也就测试几个case了。
大部分时间都浪费在键盘上了。
要是能把测试的case记录下来,下次由电脑自动的重新模拟这个过程该多好(正所谓重复劳动力全部交给电脑吧!
),不过自动化测试在后面,我们先跳过这个疯狂的想法。
还是修改源代码吧。
Stringcmd=request.getAttribute(“command”);
if(“addUser”.equals(cmd)){
userService.addUSer();
}//结束doPost正如一开始所说,通过将servlet作为很薄的一层controller,真正的业务逻辑抛给service做,所有对业务的测试全部都可以简单的变为对service的测试。
比如我们可以简单的这样测试:
publicstaticvoidmain(String[]args){
UserServiceuserService=newUserService();
System.out.println(userService.addUser(user));
}servlet中唯一要做的,就是根据service的返回结果,返回不同的UI展示。
这样保证了一种“契约”:
servlet负责调用service并提供相应参数,根据service的返回结果,返回不同的UI展示。
只要service没有错误,servlet肯定能正确的执行MVC中对View的跳转。
而对service中业务逻辑的测试,在前期就可以通过简单的单元测试做完,保证最后提供出去的service都是正确的。
相比较于将业务逻辑写入servlet,等到后期通过页面的操作才能发现bug,开发效率可以说是骤增。
实际情况当然会比我所说的例子复杂的多。
首先,现在很少有项目会直接采用servlet的形式来做,那样子的话我们不得不在很多地方重新创造轮子(还记不记得我设计的很牛叉的AaronDispatcher。
),一般会采用MVC框架来封装servlet。
同时,我们会遇到很多很多诸如安全性,权限控制,异常处理等等之类的,需要解决的问题。
(最后我们会发现,我们的业务逻辑的代码量和这些非业务逻辑代码量相比,要少的多的多)对有些人来说,如果仅仅是为了测试而多做一层service的确看起来没有那个必要,三层架构不一定总是正确,有很多缺点,典型的贫血模型,不够面向对象(关于这一点,网上的资料好多,可以尝试一下);
开发过程漫长;
做出来的效果鱼龙混杂;
(同样适用三层架构,大牛们和我设计出来的效果简直就是美食和狗屎的区别)现实生活中,没有这么复杂的php也活得好好的。
但是,但是,但是。
如果我们只是想做一个网站,它只需要记录我们的博客,展示我们曾经拍过的照片,提供一个供大家谈谈人生谈谈理想论坛,那么我建议使用php,的确做网站最好的是php,用java纯属装逼。
java之所以是java(或者换句话,.net之所以是.net),他们与php的差别在于,他们有完整的命名空间机制(java中的package,C#中的namespace),完善的开发标准体系,成熟的第三方框架,语法简单、规则性强。
事实上,java/.net绝对不适合做快速成型的展示型网站。
吐槽结束,回到上面的问题,既然三层架构绝对不是权威,仅仅为了测试还不值得我们抽象出一层BLL,那为什么我们要像CargoCult土著居民那样,跟随别人的脚步呢?
因为我们还不够强。
因为我们还不够强,不能想到更好的分层办法,所以我们不得不沿用传统的三层架构或者在此基础上的多层架构,如果我们足够强大,完全可以利用领域模型的思维来构建系统。
因为我们不够强大,很多功能我们需要利用BLL才能实现。
正如前面所说,java工程师的方向绝对不是做一个简单的网站,如果项目领导突然有一天神经大条,说我们需要做一个iphone客户端,因为领导用的是iphone,如果我们没有把BLL抽象出来,我们就不得不重新写一个和web处理相同业务逻辑的层级,用来和iphone客户端通信。
如果业务复杂,天呐。
正是由于我们已经将不依赖于任何显示逻辑的BLL抽象出来,我们才能淡然的添加一层薄薄的iphone客户端通信层,当需要发生业务逻辑的时候,仅仅只需要调用BLL已实现的方法。
通常三层架构中的UI最终会返回html给浏览器解析,而iphone通信层一般会返回json数据给iphone客户端解析。
好吧,的确不是所有领导都有iphone的,但是,即使我们确定我们仅仅需要支持的就是浏览器,很不幸,很多时候我们也需要利用BLL。
通常BLL中的每一个方法都是一个事务。
事务是一个很复杂的东西,在分布式系统中更是如此,但即使是一个简单的项目,我们也不得不为事务而伤神。
在我们的潜意识中,事务应该是数据库访问层做的事,但是很不幸,我们需要在业务逻辑处理中进行事务。
最烂大街的例子,BLL中有一个AccountServer类,提供一个transfer(Doublemoney,Accountfrom,Accountto)方法。
在这个方法中,首先会进行一次update(from),再进行一次update(to)的数据库操作,但是如果update(from),汇款操作成功了,但是因为某些未知原因,update(to),收款操作失败了,估计所有人都接受不了这件事。
所以我们要保证transfer(Doublemoney,Accountfrom,Accountto)作为一个方法,要么里面的数据库操作都成功,要么都失败。
抽象出业务逻辑层的好处在于我们可以以一种更好的颗粒度来做事务。
说点题外话。
不得不说,如果不利用SpringAOP来配置事务,纯手工编写的话,给BLL中的方法做事务,的确是一个很有技术含量的活。
这当中的确用到了很多设计模式的奇技淫巧、ThrealLocal等较为高深的技术(好吧,的确在一年之前,我真的不知道ThreadLoacl是什么。
),因为我们做事务的一个前提是,事务中所有的数据库操作,使用的是同一个connection。
关于更具体的,如何简化BLL中的事务配置,以及面向接口的编程习惯,spring的使用,将会留到下一个应用场景。
现在我们来做一次总结:
MVC之类的设计模式、三层架构的划分,归根结底的目的在于解耦,减少项目中牵一发而动全身的现象,同时减少重复代码的编写,增加代码的可读性、复用性。
所谓框架,就是帮助我们减少重复的、不必要的、非业务核心的代码的编写的工具而已。
(不然我们用它图什么呢?
)事实上,如果我们真的看完所谓23种设计模式,实际上只是在强调三个原则:
封装变化点、对接口进行编程、多使用组合而不是继承。
(这里主要注意第一点)
好吧,进入下面的应用场景考虑这样一个应用场景:
我们已经深刻的掌握了三层架构的要领(事实上并没有。
),并且使用了业内最牛叉的AaronMVC框架来处理请求,一切都变得很美好。
突然有一天,领导来了。
领导有一天突然发现,我们的User