知乎上Intopass写的关于框架的介绍,不错!转一下:
框架到底是什么?要不要学框架?我想这是很多人的疑问。
下面我想针对框架谈一谈自己的理解(以Java Web框架为例)。
① 首先从DRY原则开始
Don't Repeat Yourself,不要重复你的代码。
在编程中不要重复是我们进步(偷懒)的最高原则,一则省时省力,二则便于修改。
当我们在编程中发现了一些重复性代码时,一般来说我们总是要把他们抽取出来成为一个方法。如果不愿意陷入过早优化/过度设计陷阱的话,可以容忍重复两次,但是第三次就绝对不能再忍。
渐渐的我们有了一批方法,我们将其中相关的方法提取到一个工具类中,我相信不少人都写过。
public class DBUtils { public static Connection getConnection() {...} public static void safeClose(Closeable closeable) {...}}
相应的工具类还可以整合成为类库,供自己或大家使用。
网络上也有一些已经整合好的类库,如Commons-Lang工具包。
这些类库一般以JAR的形式进行部署,引入即可使用。
如果每个人都要从方法,工具类,类库一点一点的写起来,似乎有点太过浪费时间。
毕竟重复自己固然不行,重复别人已经做过千百万遍的工作似乎也不是很高明。
不要自己造“轮子”,总有专业造“轮子”的,我们只需要直接使用他们已经写好的类库即可。
往往这些类库,经过无数开发者的迭代开发,不管是性能还是稳定性都要比我们自己写的要好。
② 框架存在的意义,什么是框架?
框架,是为了解决某个特定领域的问题而诞生的。
框架,是为了我们不必总是写相同代码而诞生的。
框架,是为了让我们专注于业务逻辑而诞生的。
下面以JavaWeb开发为例进行说明。
1. 静态网页时代
本来网站都是一个个静态HTML组成的,或许这些网页还是用Dreamweaver写的。
但是这样的静态代码显然不能满足我们,很快我们就迎来了动态网页的时代。
2. Servlet时代
如果熟悉HTTP协议的话,我们就知道其实访问网页的过程不过是一次TCP连接罢了。
浏览器发起TCP连接到服务器,服务器接受请求,然后返回HTML代码作为响应。
那么我们完全可以等到接受到请求之后,再动态生成HTML代码返回给客户端。
Servlet就是这么做的,其主要代码不过是利用out.write()一点一点的输出HTML代码罢了。
当然我们可以在其中掺杂一点动态的东西,如返回当前的时间。
out.write("<!DOCTYPE html>\r\n");out.write("<html>\r\n");out.write("<head>\r\n");out.write("<title>Index Page</title>\r\n");out.write("</head>\r\n");out.write("<body>\r\n");out.write("Hello, " + new Date() + "\r\n");out.write("</body>\r\n");out.write("</html>\r\n");
3. JSP包打天下的时代
纯粹的Servlet很是丑陋,给前端程序员理解和修改这样的代码带来了很多困难。因此JSP技术被发明了出来,原理也不复杂,就是不直接写Servlet,而是先写好JSP文件,再由服务器将JSP文件编译成Servlet。而JSP中是以常见的HTML标签为主,这样前端程序员就能方便的修改这些代码了。
<!DOCTYPE html><html><head><title>Index Page</title></head><body>Hello, <%=new Date()%></body></html>
由只使用 Servlet到使用JSP,虽然是一个简单的变化,但这迎合了前后端专业分工的大趋势,让前段人员只需要懂得HTML/CSS/JavaScript代码就可以开始工作,而不需要学习Servlet那枯燥无味的用法,因此借着JSP技术的东风,JavaWeb技术迅速的扩展开来了。
4. Servlet + JSP 时代
随着JSP技术的发展,用它写成的网站也越来越大,业务逻辑也越来越复杂。开发人员渐渐发现整个网站渐渐的再次变成了一团乱麻,不仅仅是JSP中夹杂了大量的Java代码,页面之间的耦合关系也越来越紧密。
即便是要修改一个简单的按钮文本,或者是引入一段静态的内容,也需要打开越来越庞大的JSP页面,艰难到找到需要修改的部分,有时还不仅仅是一处,这种修改是有很大的风险的,完全有可能引入新的错误。
这时候开发者渐渐意识到,仅仅使用JSP是不行的,JSP承担了太多的责任。这时人们又想起了Servlet,Servlet中主要使用Java代码,处理业务逻辑非常轻松。如果JSP只使用HTML代码,而将业务逻辑的代码转移到Servlet中,就可以大大的减轻JSP的负担,并且让前后端分工更加明确。
因此流程变成了这样,客户端发起请求,Servlet负责接收请求,处理或查询数据库后将相应数据转发给页面,页面负责将这些数据展示出来。这里面有一些技术问题,需要探讨。
1) 后端如何将HTTP地址和Serlvet匹配起来?
答案是利用 Web.xml 进行配置。
2) 后端如何获取客户端提交的参数?
答案是利用 request.getParameter("name");
3) 如后端如何将数据传递到前端?
答案是利用 request.setAttribute("data", data);
4) 后端如何指定要转发到的页面?
答案是利用 request.getRequestDispatcher("/show.jsp").forward(request, response);
5) ........
5. Servlet + JSP 存在的问题
渐渐的开发者发现 web.xml 越来越庞大,里面配置了无数的Servlet-Mapping,越大就越容易混乱,就越容易引入错误,而 web.xml 又是一个核心文件,一旦出错,整个服务器就挂了。还有几乎每一个 Servlet 都充斥着这样的代码,获取参数,校验参数,校验通不过返回错误信息,校验通过进行业务处理,转发数据到相应页面,中间还要注意处理各种异常。有时还需要注意字符编码问题,写一个Servlet真正处理业务逻辑的代码也许就那么一两行,每次都要写的相似代码却有几十上百行。
显然这时就需要DRY原则来发威了,既然相似的代码这么多,为什么不将他们提取出来呢?不过以什么样的形式提取出来呢,其中有些部分可以提取成为类库,有些东西涉及到流程似乎不太好抽取。
6. 框架给出的解决方案――将程序流程控制权转移给框架
就以获取参数为例进行说明:
request.setCharacterEncoding("UTF-8");String dateString = request.getParameter("date");SimpleDataFormat sdf = new SimpleDataFormat("yyyy-MM-dd HH:mm:ss");Date date = null;try { date = sdf.parse(dateString)} catch (Exception e) { logger.info(e);}
仅仅是获取一个参数就要写这么多代码,还没有处理错误输入时如何返回提示信息的问题。而且我们还想让客户不输入第二遍的话,就需要将客户现在已经输入的数据放到响应中返回到客户端去。而客户端也要写相应代码,来设置这些初始值。
为什么这么难以抽取这些代码,因为这些是涉及到流程的,而流程是由程序员的代码控制的。根本的问题是控制权,控制权在程序员,那么就需要程序员写代码主导这些流程。如果将控制权交出去呢?
就好像你是一家公司的老板,你事无巨细都要亲自处理,有人来访,你先接待;需要买东西,你要签字;报表出来,你要过目;你怎么可能不累。如果找一个总经理,有事他先处理,有问题或异常时再来找你是不是轻松很多?
让我们看一下使用 Struts2 框架后的代码是什么样子的。
public class DemoAction { private Date date; public void setDate(Date date) { this.date = date; }}
是不是清爽很多?像是参数获取,编码设置,类型转换,流程控制之类的工作框架就帮你做了,是不是有些神奇?那么框架是如何介入的呢?奥秘在 web.xml 中,当我们使用 struts2 框架,我们需要在 web.xml 中配置 struts2 的核心拦截器,并让这个拦截器拦截所有请求,控制权就是在这个时候转移到了 struts2 框架中。
因为控制权转移到了框架中,所以框架就可以控制整个流程,而框架只需要设置好一些扩展点让我们程序员去写就好了,你比如说HTTP请求地址匹配Action的规则写在struts.xml中。类型转换可以设置统一的处理类,以后遇到就可以自动调用。编码设置只需要在配置文件中添加一行。参数自动帮你获取并设置到成员变量中,直接可用。如果遇到异常就去struts.xml找到响应的处理页面,如果校验通不过,就去struts.xml找input的页面。
总之你不需要再写很多重复性的代码,只需要专注于业务逻辑即可。
7. 框架与类库的区别
① 首先是控制反转
这是框架与类库的根本性不同,你写代码调用类库,框架调用你写的代码。
仍然举老板的例子的话,类库就是老板亲自主持公司业务,将业务分配给各个经理,各个经理就是类库。而使用框架就是请一个总经理,总经理主持公司业务,一般不需要老板出面,只有遇到重大问题时才会请示老板,而且会把材料整理成最舒服的形式交给老板。
② 其次是框架规范了开发者写哪些代码,不写哪些代码
框架给开发者规定了那些代码必须写,如通过 struts.xml 进行配置,写 Action 来处理业务逻辑,写 JSP 页面展示数据。哪些代码不用写,参数获取,制定编码,常用的类型转换等等。
③ 正面清单 VS 负面清单
框架就好像是正面清单,框架告诉你只需要写好这些代码,整个程序就能运行了。
类库就好像是负面清单,类库中已经写的代码你不用写,但是整个程序你需要自己组织,类库没法告诉你还需要写哪些代码,程序才能正确运行。
③ 我需要使用框架吗?
1. 只有在规模以上的程序中,使用框架才能带来开发效率的提升。
如果你只是开发一个小网站,一共也没有几个类,也许 Servlet + JSP 就可以满足你了。
更简单些或许只使用 JSP 也够了,不需要去使用框架。
2. 框架虽然已经为灵活性做了很多准备,但是对于一些特殊需求,仍然不可能面面俱到。
这时你就需要对框架进行定制,可是如果你对于框架的原理不清楚,对于框架的源码没有头绪,使用框架很有可能会给你造成麻烦,所以有人坚持使用 Servlet + JSP 开发其实并非没有道理。
3. 只要程序的规模大了,归根究底你总是要使用框架的,不是使用现成的,就是自己写一个。一般不推荐自己写,除非是以了解框架的原理为学习目的而写。这里我们推荐大家使用框架时,要明白框架帮我们工作的原理,最好能阅读一些关键源代码。
④ 新手何时开始学习框架?
我不建议新手一开始就直接使用框架。
就好像一开始学习编程语言,大家都不推荐直接使用IDE,一定要用命令行自己编译运行几个文件之后,了解清楚了之后才可以使用IDE,要不然对于底层原理不了解,遇到问题没法自己手动排查。
使用框架也是一样,如果不是自己写多了重复性的代码,就很难理解框架为什么要这么设计。如果不尝试几种不同的实现,就很难理解框架为了灵活性而做出的设计和扩展点。如果不写几十个权限检查语句,就很难理解AOP到底有什么好处。
⑤ 要知其然知其所以然
以Spring+Hibernate的声明式事务为例,其经历过好多次改变,一次比一次简单易用,但也将实现细节隐藏的更深,如果不自己从头捋一遍很难理解后面的一些问题。
1. 首先是要学习使用SQL在数据库里进行事务处理。
2. 然后是要学习使用JDBC进行事务处理。
3. 学习JDBC事务之后,要明白事务是基于Connection来进行控制的。
4. 学习Hibernate事务,要明白事务是基于Session来进行控制的。
5. 要理解多线程下,如何基于Session进行事务,要理解不能多个线程同时使用一个Session进行事务的道理。
6. 要理解Spring管理的单例的Dao如何在多线程环境下获取到线程绑定的Session。
7. 要理解每一个Http请求都会由服务器新建一个Thread来进行专门处理的现实。
8. 要理解多线程下单例的对象如何使用ThreadLocal获取到线程绑定的数据的原理。
9. 要理解SessionFactory中openSession() 和 getCurrentSession()的不同,已经为什么我们应该使用后者的道理。
10. 要理解为什么我们要保证事务管理器获取的SessionFactory和我们使用的是同一个。
11. 要理解Spring实际上是使用AOP来进行事务处理的,要理解事务边界存在的意义。
12. 要理解Spring AOP的原理,以及为什么必须是Spring容器管理的类才能进行进行代理。
13. 要理解为什么必须使用Spring注入的Dao和Service,而不能是自己new的Dao或Service。
14. 要学习一下TransactionProxyFactoryBean的原理和配置,要学习一下BeanNameAutoProxyCreator,要学习一下使用AspectJ的原理,要学习一下基于注解的事务配置,以及注解的工作原理。
15. 真正的要会使用Hibernate + Spring声明式事务进行持久化,你还必须了解Hibernate PO的瞬时态,持久态,托管态三种状态的原理以及如何与Spring事务边界进行配合的用法。
16. OpenSessionInViewFilter 的道理是什么,什么是懒加载,以及各种层次的懒加载。
17. 好吧,闲扯到这里我总是听到很多人反映Hibernate太复杂,容易误用,现在想想也确实有些难为那些不喜欢追根究底的程序员了。
框架里面有无穷的知识可以学习,但是想要学到手必须你深入进去,甚至看看框架的演进历程才能研究透彻。
⑥ 继续闲扯一下 Java Web 的MVC模式和多层架构的故事吧。
1) Java Web 的 MVC模式
纯JSP开发Web页面为什么在大型网站不可行,主要是因为后端程序员不懂美工,而前端程序员不懂代码,两者都懂的成本太高,人员也难招聘。所以JSP页面中混杂各种代码,将会导致前端后端都不敢乱动这个文件,所谓越改越乱,越乱越没法改。
早在桌面软件开发时代,这个问题就已经被发现并提出了一个解决方案,那就是MVC模式,Model-View-Controller,Model代表着数据和状态,View负责显示,控制器负责处理和转发请求。
其中View要向Model注册更新事件以便模型发生变化时,视图可以自动更新。同时多个View可以同时注册到一个Model。
2) Model 2.0
但是Web开发中略有不同的是,因为HTTP是基于请求-响应模型的,客户端不发起请求,服务器是没法主动更新客户端的。(虽然现在这么说已经不准确了,但是当时还是这样的)
这张图被称之为Model 2.0,是Java Web MVC模式的一种实现模式。
可以看到浏览器发起请求,Servlet充当控制器负责接收请求,然后判断是直接转发到JSP还是先调用JavaBean进行业务逻辑的处理,再将数据转发给JSP。JSP充当视图层,负责最终返回HTML代码给浏览器。
在Model 2.0中,主要是明确了JSP只能担任视图这一个职责,其中不能有与试图无关的代码。
也就是说最好整个JSP中只有HTML代码,而没有任何<%%>包裹的Java代码。
那么没有Java代码的情况下,如何输出数据以及进行一些简单的逻辑判断或循环呢?
其实Java提供「自定义标签」和「EL表达式」来帮助前端进行这样工作。
<table border="1"> <tr> <th>Name</th> <th>Age</th> </tr> <c:forEach var="item" items="${data.persons}"> <tr> <th>${item.name}</th> <th>${item.age}</th> </tr> </c:forEach></table>
3) MVC框架
Model 2.0 虽然也很不错了,但是其实也有很多不足。如依赖web.xml分发请求,导致web.xml会越来越大。Servlet与容器API耦合很深,难以进行单元测试,而单元测试是程序健壮的根本保障。
只支持JSP一种视图技术,其实PDF,Word,Excel,或者其他模板引擎也可以充当视图。
因此就出现了一些框架,这里以Struts2为例进行说明。struts2 以 struts.xml 作为请求分发的配置文件,还可以分模块导入功能,能有效减小单文件大小,降低错误引入概率。以Action作为后端控制器,不与Servlet-API耦合,单元测试较为方便。在视图层提供了struts标签,以ognl表达式和ValueStack串联前后端,支持多种视图技术。难怪当时 struts2 可以说是如日中天,虽然现在已经输给了 spring mvc 但是仍然不失为是一个优秀的框架,其中很多技巧是可以学习的。
这些MVC框架都是主要实现了控制层,然后提供了视图层所需的一些工具,而对于模型层都是涉及不多。确实,模型层与业务逻辑关系密切,框架能帮助的不多。但是渐渐的也总结出了模型层内再分层的模式。那就是Action > Service > Dao > 数据库,其中Service主要提供粗粒度的带有事务的API,Dao层提供细粒度的一般没有事务的API,层与层之间不能跨层访问。
4) 关于分层
有些人学了J2EE分层设计模式之后,凡是写Java Web必分层,中间要是再加上面向接口编程的模式的话,那么写一个小功能都需要写N个类,其实是有点走火入魔。如果你的项目已经确定规模非常大,那么严格遵守这些模式确实能给项目带来很多帮助。但是如果你的项目其实并没有多大,那么这些层次其实是可以伸缩的。从完整的分层,到去除Service或Dao,甚至直接在Action中操作数据库其实都是可以的。
5) 虽然还有些意犹未尽,有些东西没有讲出来,不过今天先写到这里有时间再更新吧。