作者: 江南白衣 像工匠一样进行重构, 让重构成为一门手艺.
Martin Fowler的《Refactoring》其实更适合做一本关于重构的洗脑,宣言式的书,就像Kent Beck的《XP Explain》一样薄薄的就可以了。只可惜他却非常的厚,后面的重构名录都是写给小白看的。所以我更喜欢《Refacoring WorkBook》,以一个工匠的语气(沉默寡言而实要)传授重构的手艺。
1.重构 Between Classes
〈Design pattern〉有半数篇幅教育大家不能只靠继承,要善用组合/委托。重构里面其实也有很多事情靠把继承变成委托来解决。
1.1继承 1.1.1 并行继承体系,组合爆炸 这在以前是个头痛的问题,现在都已习惯用委托。 另外java还有个不是很让人满意的接口机制解决并行继承。 1.1.2 父子类的关系
比如过于亲密,子类会乱修改父类的方法,访问父类的变量,这时候可以定义final的Template方法。
还有拒绝的馈赠,我暂时还没有在这上面遇到问题,作者也建议如果没事就由他,如果有事,就要费劲的move method ;或者子类不继承父类,而只是组合父类。
1.2职责 经过很多次重构之后,我发现,其实哪个方法应该放在哪个类其实很主观的,你每天醒来都能想到一个理由让一个方法搬一下家,所以我现在已经放弃追求一种“对”的职责分配了,看着顺眼就行。
1.3散弹式修改 作一个修改就要改N个类时,也没什么特别好方法,就是找找看,有没有能为这个修改负责的统管全局的类。 但现在的很多散弹式修改是分层做成的。
1.4库类 OpenSource的类库,总有些时候会想要扩展 1.如果只是一两个方法,直接在客户代码里扩展, 2.否则自己多一个类库的子类 3.最费劲就是引入一个新的层
题外话,重构其实很依赖工具,和对全部代码的拥有度,哗一下就来个全项目的rename。当你设计库类时,你并不一定拥有使用这些库类的客户代码了,因此一开始就要认真设计,不能依赖重构,改接口会让人K死的。
2.重构 Within Classes
2.1 大是罪 Long Method、Large Class、Long Parameter List, 一般通过度量工具找出来,还可以自己设定一个触发器,当度量值超过某个限度时就报警。 PMD可以实现这个功能,但度量工具我更喜欢Metrics Reload,一个IDEA的插件,给出的度量信息很全面。 但是也切忌为了度量的数值而重构。 Long Method当然是尝试Extract Method。 Large Class就要把类的职能分开几个域,尝试拆出另一个Class或者SubClass。 Long Parameter List 可以通过在方法内的变量用查询获得而不用由参数传入; 或者把某些参数组成一个对象。
1.2 重复也是罪 重复在30年前就被认为是不好的一样东西,表现在1.代码类似,2.代码、接口不同而作用相近。 去除重复的方法也没什么特别,无非就是 Extract Method(同一个类)。有差异时,通过参数化达到共用。 Pull Up Method到父类(同一个父类)。有差异时,通过模板机制达到共用。 Class A调用Class B 或者 Extract Class C (两个完全无相干的类)
1.3 命名 中国程序员的英文本来就差,要多参考Ofbiz、Comperie的命名, 尽快建立团队的项目字典、领域术语字典。
也幸亏,现在在工具辅助下,代码的rename是最容易的重构。
1.4复杂条件表达式 作者认为,即使现在Program最关注的是对象,以及对象间的关系了,但优质的内部代码依然重要,推荐《编程珠玑》和《Elements of Programing style》。 化简复杂条件的基本有三个方法 1.通过!(A&B)==(!A)||(!B)化简 2.通过有意义的变量,函数代替条件表达式,比如 boolean isMidScore = (X>1000)&&(X<3000); 3.通过把一个if拆分开来执行,把guard clause放在前面 比如if(A||B) do(); ->if(A) do(); if(B) do(); 又可以把2、3灵活组合,比如根据2,Extract出一个isRight()函数,根据3 isRight() { if(A) return true; if(B) return true; return false; } 1.5 其他 没用的死代码,通过IDE工具探知并移除。小心有些框架代码是不能删除的。 Magic Number,当然是改为常量。如果Magic Number很多,可以用Map、枚举类来存放。 除臭剂式的注释,为方法,变量改一个更适合的名字。如果注释是针对一个代码段的,可以Extract Method。当然,代码只能说明how, 不能说明why,更不能说明why not,这才是注释存在的地方。
Ilog JRules 是最有名的商用BRMS,刚拿了JOLT; Drools 是最活跃的开源规则引擎,一路高歌猛进; Jess 是Clips的java实现,就如JRuby之于Ruby,是AI系的代表。
今天对比了一下这三个颇有代表性的规则引擎的规则语言。其中Ilog是商业产品,没有机会实战。
作者:江南白衣
1.一样的If--Then 句式与Rete引擎
三者都会把原来混乱不堪的if---else---elseif----else谜团, 拆成N条带优先级的"If 条件语句 then 执行语句" 的句式。 三者都主要使用foreward-chaining的Rete引擎,按优先级匹配条件语句,执行规则语句。 规则执行后会引发事实的变化,引擎又会重新进行条件匹配,直到不能再匹配为止,Rete的算法保证了效率的最高。
2.开发人员使用的规则语言
2.1 Drools的XML框架+Java/Groovy/Python嵌入语言
Drools的用XML的<Conditons>、<Consequence> 节点表达If--Then句式,而里面可以嵌入上述语言的代码作为判断语句和执行语句。 其中Java代码会使用Antlr进行解释,而Groovy和Python本身就是脚本语言,可以直接调用。 Drools的聪明之处在于,用XML节点来规范If--Then句式和事实的定义,使引擎干起活来很舒服。 而使用Java,Groovy等原生语言来做判断和执行语句,让程序员很容易过渡、移植,学习曲线很低。
<java:condition> hello.equals("Hello") </java:condition>
<java:consequence> helloWorld( hello ); </java:consequence>
2.2 ILog的IRL(ILog Rule Language)
IRL用When{}Then{}表达 If--Then句式
When { ?customer: Customer(totalTime >=1000); } Then { execute {?customer.setAmount(getAmount()-20.00); }
文档称IRL的语法是Java Syntax-like的,但我怎么也看不出两者是相同的。不过他因为是商业产品,有很强大的编辑器和管理工具,编写规则的速度应该不坏。
2.3 Jess的CLIPS jess用 => 表达 If-Then句式。 这CLIPS是真正的程序员专用语言,而且还要是很专业的程序员才习惯的东西。但这种本来就是用来做专家系统的AI语言,对规则的表达能力也应该是最强的。 讲解一下下面这段代码,airplane有一个属性--name,有两个子类--喷气式和螺旋桨飞机,其中螺旋桨飞机可以使用任意跑道,而喷气式飞机不能使用Grass跑道。
; Fact templates (deftemplate airplane (slot name)) (deftemplate jet extends airplane) (deftemplate prop extends airplane) ;
Rules (defrule can-use-grass-runway (prop (name ?n)) => (printout t "Aircraft can use grass - " ?n crlf)) (defrule can-use-asphalt-runway (airplane (name ?n)) => (printout t "Aircraft can use asphalt - " ?n crlf)) |
3.客户使用的规则语言
如果客户可以自己任意编写规则,无疑是产品一个很大的卖点。大部分客户都会喜欢这样一个玩具。而且也只有把规则编写交给客户,才能达到规则引擎的全部意义。
3.1 Drools的 DSL Drools的最新版Drools2.0Rc2里,House和Conways game of Live两个例子有DSL的版本 对比一下Java版本,效果如下:
<house:condition> <house:room name="calvin"> <house:温度> <house:greater-than scale="摄氏">20</house:greater-than> </house:温度> </house:room> </house:condition>
vs
<java:condition> room.getName( ).equals( "calvin" ) <java:condition> <java:condition> convertToCelsius( room.getTemperature() ) > 20 <java:condition>
但这种XML Base的DSL语法其实好不了多少,而且实现的代价一点不少,要自己实现Conditons和Consequence Factory类,自行解释那段XML,基本上没有什么便利的底层支持。 其实,一不做二不休,干脆用Antlr来定义真正的DSL,同样是实现Conditons和Consequence Factory类可能更好。只不过解释XML人人都会,Antlr就比较少人用而已。
3.2 ILog的BAL(Business Action Language)--最完美的王者? 没有实际用过,只能看文档过过瘾。从文档来看,配合Ilog的编辑器,的确就是很完美的规则语言了。
If the call destination number is the preferred number Then apply the preferred number rate
JetBrains的MPS出来了,Martin Fowler也大力捣鼓出一篇《Language Workbenches: The Killer-App for Domain Specific Languages?》,成为有志于LOP、DSL领域的总领性文章。
首先,了解Martin Fowler的立场很重要。但似乎为了保证阅读率,MF把立场摆到了最后。
1. LOP带来的两个最大优点是 a. 通过新的封装及思维模式,提高程序员的生产率。 b. 改变程序员与领域专家的关系,最理想情况是领域专家直接用DSL编程。 MF认为第2点比第1点带来的效果大得多,但也困难得多。COBOL刚出来的时候已经有人提领域专家直接编程了,结果呢? 2.现在大家对DSL应该是什么样子的还知之甚少,文本语言?图形语言?一切都还在假设。 3.现在的LOP工具还在非常初始的阶段。
4.但MF同时认为LOP是目前最有趣的想法,不论它日后成不成功,都会激发出大量有趣的思想与实践,留意一下LOP是绝不会吃亏的事情。
是不是热情骤减?本来MPS的发布使LOP看起来像是明天就可以开始动手的事情,现在又变成了虽然很有趣,但还远没到下山摘果子时候。 从头读一遍文章 1.开头 A Simple example of LOP Martin举的这个例子占了全文1/3的篇幅,又长又不刺激神经,看得大家频频起身吃零食,上厕所.... 2.传统的LOP MDA不是什么新概念,DSL当然也用不着是,DSL其实早就在我们身边,包括 1.Unix下用yacc打造的微型DSL 2.Lisp,fp用自身来构造DSL 3.XML配置文件 4.GUI描述文件(VB, Delphi....) 5.Adaptive Object Models and Active Data Models? (没完全理解) (注:SQL也算吧)
3.External DSL和Internal DSL DSL分内外两种,像yacc这种把DSL parser后translate成base语言的属于External DSL。 而Lisp这种用语言本身来构造新的语言的称为Internal DSL。
External DSL的好处是它可以是任何样子的,不受Base语言的制约。另外它也通常是运行时解释的。 不好的地方: 第一, 它需要花很多时间去设计语言,写Parser,写Generator,写IDE。 第二, 不能直接使用Base语言的IDE,在后IntelliJ时代这让人很不爽. 第三, 需要学太多语言和思维方式,不是指if-else语法的不同,而是在java里我们已经习惯了用Object和Method来表达想法,但在其他DSL里则可能要运用完全不同的概念,比如文章开头的例子。
而Internal DSL和External DSL的优缺点很多地方正好调转。而且Lisp,Smalltalk的语法和我们平常的Java,C#差别很大。还有,最近Ruby们好像也有可能用来写Internal DSL了。
正是因为两种DSL都缺点明确,所以DSL在今天这么不普及。Language workbeanch,正是为了使External DSL变得容易而出现的。
4.今天的Language Workbeanch 有Intentional Software的IP, JetBrains的MPS和微软的软件工厂。
1.一段DSL将有一个Editble reprensentation,一个storage reprentsentation,一个编译后的excuteble reprentsentation,一个在editor中的AST-Astraction reprensentation。其中editble和storage reprensentation可以合一,也可以分开。 2.定义一个新DSL的三个步骤: a.定义语言的schema b.定义编辑器 c.定义Generator 一个DSL可以拥有多种编辑器和代码生成器。 5.Language WorkBench的优缺点 优点: 1.省却了写Parser,直接定义抽象语法。 2.省却了写IDE。 3.IDE的语法提示与语法检查,给领域专家直接编写提供了可能,这是COBOL时代没有的。 4.DSL与项目的良好集成,可以项目与DSL语法一起refactor,可以一边设计语言一边使用语言。
缺点: 1.Vendor专属,用了MPS,就不可能再转到IP或者微软,因为他们之间根本没有标准可言。 2.但Generator并没有比以前简单(要命阿)。 3.现在代码以astraction reprensention为中心,版本管理,AST支持diff/merge的问题。
6.我的立场 试用了一下MPS,因为Generator还没有革命性的突破,MPS还没到真正可用的时候。 不过几个月间,MPS EAP已经从初始的150版本升级到220,让人无法忽略它的进度。
作者:江南白衣
注重实效的TDD的确能加快,而不是拖慢开发的进度(片面的追求覆盖率的全面UnitTest不在此列) 一,可以实现真正分层开发。 二,不需要依赖和频繁重启Web Container。 三,手工测试总不免改动数据库,如何把数据库恢复到测试前的状态是件伤脑筋的事情。而Unit Test可以使用自动Rollback机制,巧妙的解决了这件事情。
Spring 下的Unit Test主要关注三个方面: 1. bean的依赖注入 2. 事务控制,Open Session in Test 及默认回滚 3. 脱离WebContainer对控制层的测试
1.bean的依赖注入 能不依靠WebContainer来完成ApplicationContext的建立与POJO的依赖注入一向是Spring的得意之处。
String[] paths = { "classpath:applicationContext*.xml" }; ApplicationContext ctx =new ClassPathXmlApplicationContext(paths); UserDAO dao = (UserDAO) ctx.getBean("userDAO");
如果你连这也觉得麻烦,那么只要你的testCase继承于Spring-mock.jar里的AbstractDependencyInjectionSpringContextTests,实现public String[] getConfigLocations()函数, 并显式写一些需要注入的变量的setter函数。 注:因为是AutoWire的,变量名必须等于Spring context文件里bean的id。
2.Open Session in Test 及自动Rollback 又是来自Spring这个神奇国度的东西,加入下面几句,就可以做到Open Session in Test ,解决Hibernate的lazy-load问题;而且接管原来的DAO里的事务控制定义,随意定义测试结束时是提交还是回滚,如果默认为回滚,则测试产生数据变动不会影响数据库内数据。 你可以让testCase继承于AbstractTransactionalDataSourceSpringContextTests,通过setDefaultRollback(boolean)方法控制最后回滚还是提交。 如果自己编写,代码是这样的:
protected PlatformTransactionManager transactionManager; protected TransactionStatus transactionStatus; protected boolean defaultRollback = true; public void setUp() { transactionManager = (PlatformTransactionManager) ctx.getBean("transactionManager"); transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition()); } public void tearDown() { if (defaultRollback) transactionManager.rollback(this.transactionStatus); else transactionManager.commit(this.transactionStatus); }
(注,hibernate太奸诈了,如果全部默认回滚,只会在session里干活,一点不写数据库,达不到完全的测试效果。)
3.Controller层的Unit Test
controller层靠Spring提供的MockHttpServletRequest和Response来模拟真实的servlet环境,并且spring 2.0了加了一个AbstractModelAndViewTests,提供一些检测返回值的utils函数。
protected XmlWebApplicationContext ctx; protected MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); protected MockHttpServletResponse response = new MockHttpServletResponse(); protected Controller controller = null; protected ModelAndView mv = null; public void setUp() { String[] paths = {"applicationContext*.xml","myappfuse-servlet.xml"}; ctx = new XmlWebApplicationContext(); ctx.setConfigLocations(paths); ctx.setServletContext(new MockServletContext("")); ctx.refresh(); controller = (CustomerController) ctx.getBean("customerController"); //再加上前文的事务控制的代码 } public void testCustomerList() throws Exception { request.setRequestURI("/customer.do"); request.addParameter("action", "listView"); mv = controller.handleRequest(request, response); assertModelAttributeAvailable(mv, "customers"); }
4.进一步简化 一来这两个基类的名字都太长了。 二来有一些公共的context文件的定义。
所以可以再抽象了几个基类,分别是DAOTestCase,ControllerTestCase。
5. EasyMock MockObject是一样彻底分层开发的好东西,而且使用上没什么难度。而且已不再存在只支持接口不支持Class的限制。
//设定BookManager MockObject bookManagerMockControl = MockClassControl.createControl(BookManager.class); bookManagerMock = (BookManager) bookManagerMockControl.getMock(); controller.setBookManager(bookManagerMock); //录制getAllBook()和getCategorys方法的期望值 bookManagerMock.getAllBook(); bookManagerMockControl.setReturnValue(new ArrayList()); bookManagerMockControl.replay(); //执行操作 mv = controller.handleRequest(request, response); //验证结果 assertModelAttributeAvailable(mv, "books");
Easy Mock VS JMock:
JMock 要求TestCase继承于MockObjectTestCase太霸道了。妨碍了我继承于Spring2.0的ModelAndViewTestCase和使用MockDao,RealDao并行的继承体系。因此采用没那么霸道的easyMock。
另外,easyMock的脚本录制虽不如jmock那么优美,但胜在简短易读。jmock那句太长了 。
6. 显示层测试 还有,显示层至今没有什么好的UnitTest方法,无论是不成才的httpUnit们还是笨重的GUI test工具。Appfuse一直用的那个ThoughtWork那个Selenium和J3Unit的效果不知如何, 其中J3Unit号称支持prototype。
作者: 江南白衣 之前用Groovy的嵌入式xml语法时, 发现遍历DOM只有children()函数而没有parent(),就帮它修改了一下,连UnitTest一起提交到Groovy的JIRA上。 今天看到开发组接纳了我的改动。 > [patch] add parent() implement to XmlSlurper > Key: GROOVY-1010 > The XmlSlurper only have the children() method to explore DOM. > But in our project, it's so offten to use parent(). > i had patch it with unit test . John Wilson resolved GROOVY-1010: ------------------------------ ---
Resolution: Fixed
Patch added - thanks Calvin!
现在很多开源项目都有了Issue Tracker的渠道,方便大家参与测试和修改。 大家在消费别人的开源努力时,也应该习惯把自己改过的东西提交回去。
作者:江南白衣
以Spring为代表的提供依赖注入的IOC Container风头越盛,比起IOC的原本意义,DI逐渐有妹仔大过主人婆的姿势,所以Martin Fowler同学忍不住写了篇blog,提醒一下大家IOC的本原--一种作为"所有Framework与API Library最根本的区别点"的Design Principle。 当年侯捷同志是以VC下的MFC作例子,马同学与时俱进,换了Ruby、Junit、SWT来教育时下的新新人类。 IOC原理是老生常谈了,可以看马同学的blog。当应用复杂时,都应该考虑把封装从线性调用的API级,提升到奉行IOC的Framework级。
我更关心如何把自己的代码交给IOC框架调用。 1.闭包,匿名内部类,函数指针 如果仅仅把一段代码plug给框架,Groovy、Ruby的闭包是最简单快捷的,再看Java中匿名内部类的做法,比如SWT的事件绑定,Spring的JDBC Template,代码之难看令人心酸,很无妄的的要多一个接口名,一个方法名和两层嵌套,见Martin Fowler的<闭包>的中文版.
2.Template模式,策略模式 如果要把一组关联的代码绑定给Framework,则通常会定义出一个接口。这里的接口是广义的。GOF里有两种模式: 一种是模版(Template)模式,几种最简单、最古老的模式之一。在父类里面定义Control flow,留下钩子程序的位置。而在子类里实现这些钩子程序,Junit的setup()和tearDown()就属于这种类型。
另一种是策略模式。Template的一个重要制约为两者必须是父子关系,这对于单根继承的java有点不便。另外,策略模式在灵活性上显然更胜一筹。策略类只需要实现某个接口,容器就可以在适当时进行调度。比如EJB,和Swing都是这种模式。
3.消息绑定 最后,MS家还有个更高明的消息机制,可以实现更灵活的绑定。
4.AOP, cglib和Annotaton 另外,马同学没有讲的,因为AOP和元数据的出现,框架本身又有了新的封装方式。 框架可以用AOP更隐式,无侵入的提供服务,Annotation可以更简洁的告诉框架如何提供服务。
比如Spring 的JDBC Framework ,封装了连接,事务,异常的管理,让你可以专心的写SQL查询。但那些个匿名内部类让你怎么看怎么不爽,而Java又的确没有闭包......这时你可以用Spring的声明式事务管理机制,你只要把业务代码写成普通函数,而Spring会利用AOP隐式的包裹你的代码提供连接、事务的管理。
如果你不喜欢用xml配置文件声明事务,可以自己用cglib+annotation简单实现一下。甚至,如果你连 annotation也不喜欢,还可以学习rails, 用纯命名约定搞定,只在必要时采用annotation辅助。
潮流兴用Ruby写Sample Code. 还有一样事情,就是马同学开始喜欢用Ruby来写sample code。发现Ruby写sample code的确好,读的时候像伪代码一样清晰简单,写的时候也可以一气呵成,没有太多无聊定义与制约,也不用怕别人投诉代码编译不了....
作者: 江南白衣 一个Appfuse式的项目,会通过项目里最典型的几个场景,demo团队目前的体系框架和设计模式。
它的好处有一打,比如为所有项目提供共同的Library Stack,提供最可靠的代码蓝本,保证大家的模式和代码风格一致,加快知识在团队的传播,方便新人的融入,还有为试验代码提供一个稳定简洁的环境。
所以,一个长期合作的团队,需要这样一个MyAppfuse。
但还要有三条铁的纪律,才能保证辛苦做出来的MyAppFuse不是个寂寞的芭比。 一是强制更新,所有团队approval的最新模式都要refactor到MyAppfuse中。 二是规范更新,每次更新都要严格测试并编写更新记录、移植文档。 三是强制Copy Start,所有代码都必须从MyAppFuse里Copy而不是随自己喜欢找任意项目的代码。
现在开始规划一个Appfuse式项目。我觉得包含如下Content: 1.设计典型的应用情景。 我平时的ERP项目,最典型的情景莫过于: *基础资料管理(如产品资料的CRUD) *单据管理(如订单的录入与管理) *典型报表
每个场景应该有简单与复杂两种模式,方便Developer选用。 场景要仔细设计,尽量演示到所有重要的技术要点。 但场景又要尽量的少,尽量简洁,减少每次模式升级的成本。
2.挑选出其他比较重要的特性。 如Quartz、ClickStream,也一并放入MyAppFuse中。
3.把所有用到的框架、类库、瓶瓶罐罐统统打包。 并附上索引和说明作为团队公用的Library Stack,每次library升级都要认真检测。
4.编写文档。 类似Appfuse的Tutorial,编写文档说明各个场景用到的技术要点与模式,说明如何二次开发。 类似Appfuse的Migrate,详细说明如何升级到MyAppfuse新的版本,促进新模式的传播。
5.简单代码生成工具。 类似Appfuse的AppGen,用Groovy Template或FreeMarker编写简单的代码生成模版。
6.核心的测试用例
后记:这个MyAppfuse终于开源成http://www.springside.org.cn
作者: 江南白衣 扩展Spring系列(1)--Spring 的微内核与FactoryBean扩展机制DreamHead在 《思考微内核》十分激赏 Spring的微内核与扩展机制:
“Spring的微内核在哪里呢?便是DI容器。而通过FactoryBean,我们可以定制自己的组件组装过程,对一个普通的JavaBean做手脚,像Spring AOP中常用的ProxyFactoryBean做的那样。如此,我们就不必把所有功能都做到Spring的DI容器中去,而是以一个FactoryBean来对DI容器的功能进行扩展。除了Spring自身之外,现在已经有一些项目开始利用这个特性扩展Spring,比如,Acegi Security和Spring Modules。” 这确是框架容器界应该贯彻的范式,微内核提供最少的功能,而由扩展接口去增强框架的能力。下面看看Spring怎么设计,明白之后就可以开始为Spring捐献精力了:) 1、微内核的功能1.1 DI(依赖注入)与Singleton管理 利用POJO setter的DI机制,估计每位同学随手都能写一个简单版本,不多说了。 Singleton管理说白了就是先到一个map中按id找找看有没有已存在的实例。 1.2 BeanName与BeanFactory注入 除了DI注入的属性,微内核还有什么能卖给POJO呢?就是Bean在xml 定义里的id和BeanFactory自己了。 卖的机制是让POJO 实现 BeanNameAware和BeanFactoryAware接口。BeanFactory用 if(pojo instance of BeanFactoryAware)判断到POJO需要注入BeanFactory,就调用setBeanFactory(this)将自己注入。 这种框架中 基于接口的注入和调用机制在Java下挺标准的,Spring的功能多是基于这种模式提供。遗憾就是Java不支持多重继承,作为替代的接口里不能提供默认的实现,导致每一个Pojo都要很无聊的实现一遍setBeanFactory()。 1.3 DI后的初始化函数调用 比如属性A,B注入之后,需要同时根据A和B来对A,B进行加工或者装配一个内部属性C,这样就需要在所有属性注入后再跑一个init()函数。 Spring提供两种方式,一种是和上面的原理一样,实现InitializingBean接口的afterPropertiesSet()函数供Spring调用。 一种是在xml定义文件里面自行定义init函数名。 懒得每次在xml文件里定义的就采用第1种方式,不想与spring耦合的pojo就采用第2种方式。本来就是为了扩展Spring而存在的FactoryBean多采用第一种。 所谓微内核,就是仅提供以上三种功能的DI容器。 但作为轻量级容器,还需要以下两种方式,向容器内的POJO 附加各种服务。2.FactoryBean扩展机制Spring的AOP、ORM、事务管理、JMX、Quartz、Remoting、Freemarker、Velocity,都靠FacotryBean的扩展,FacotryBean几乎遍布地上:
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"/>
<bean id="baseDAOService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"/> 只不过当年对这类factoryBean比较麻木不仁,不问原理的照搬照用了。 不过这原理说出来也好简单,所有FactoryBean 实现FactoryBean接口的getObject()函数。Spring容器getBean(id)时见到bean的定义是普通class时,就会构造该class的实例来获得bean,而如果发现是FacotryBean接口的实例时,就通过调用它的getObject()函数来获得bean,仅此而以.......可见,很重要的思想,可以用很简单的设计来实现。 考察一个典型的FactoryBean:
一般会有两个变量,三个接口: 一个setter函数注入需要改装的pojo,一个内部变量保持装配后的对象returnOjbect。 implements三个接口 :FactoryBean,InitializingBean和BeanFactoryAware 。 各接口的意义之前都讲过了。factoryBean会在afterPropertiesSet()里把pojo改装成returnObject,需要用到beanfactory进行天马行空的动作时就靠BeanFactoryAware注入。最后在getObject()里把returnObject返回。 Rod说:IoC principles, combined with the factory bean, afford a powerful means to abstract the act of obtaining or accessing services and resources 3. Bean Post-Processor扩展机制 如果说FactoryBean 是一种Factory、Wrapper式的扩展,Bean Post-Processor就是另一种AOP、visitor式的机制,所以也多用于spring的AOP架构。 Post-Processor的原理就是BeanFactory在前文里的调用afterPropertiesSet()/init-method前后,调用在工厂里注册了的post-processor的postProcessBeforeInitialization()和postProcessAfterInitialization()。 那怎么注册登记呢?又分请不请礼仪公司两类。如果是ApplicationContext,你把继承BeanPostProcessor 的bean往xml里一搁就行了,application context自会打理。如果是BeanFacotry,就要显式的注册,代码大概像:
XmlBeanFactory factory = new XmlBeanFactory("C:/beans.xml"); BeanPostLogger logger = new BeanPostLogger(); factory.addBeanPostProcessor(logger); Rod说:"Post-processors add the ability to customize bean and container behavior in a flexible, externalized fashion. " 对比Factory Bean那段,可见两种机制在他心目中的不同作用。 系列文章:Spring 的微内核与FactoryBean扩展机制 扩展Spring(2)--Spring对各种数据访问框架的集成机制
作者:江南白衣
ANTLR(ANother Tool for Language Recognition)风头正盛,经常可以看到用它做语法解释器的项目,比如Hibernate就在3.0换上它来解释HQL,加强了HQL的语法。 因为Antlr是EBNF-AST语法解释系的代表,而自己总是心思思想搞一下DSL(领域语言),所以从Hibernate来学习一下Antlr的应用。
Hibernate HQL translator作者Joshua Davis的两个Blog Hibernate3 Query Translator Design - Part One : The Basics Hibernate3 Query Translator Design - Part Two : Parsing HQL
Antlr最好的介绍文章是那篇,在《程序员》2004年3月有中文的版本。 不过,那个计算器的例子太简单了。深刻一点的有。 另外,SlickEdit 支持Antlr的语法,是一定要用的编辑器,在 ttdown.com上有破解。
一,Antlr引擎的工作过程大概是这样的: 1.Lexer类--词法分析器。 定义语言中的各种Token(单词),如 From 、Where、=、<>....... Lexer负责把读入的普通文本流识别成Token串。
2.Parser类--语法分析器。 使用BNF语法,递归定义句子的Pattern,如whereStatement、FromStatement、SelectStatement。 Parser负责把读入的Token串匹配成句子,翻译出AST(抽象语法树)。 有些简单的应用,也可以在本层现炒现卖,完成所有动作,属于Single Pass Builder。
3.TreeParser类--抽象语法树遍历器。 根据Parser类分析出来的AST(抽象语法树)进行动作。 用Parser把AST抽取出来,再用TreeParser进行动作的Double Pass Builder模式,解耦了Parser和Generation,再配合Template 生成代码,是Antlr推荐的最佳模式。
二,开发人员的实际步骤
1.按照Antlr的简单语法定义前面讲的3个类,文件的后缀名为g。
2.使用java antlr.Tool xxx.g 命令,把grammar文件编译成java文件。
3.编写应用程序,如:
import antlr.*; import antlr.collections.*; public class Main { public static void main(String[] args) throws Exception { ExprLexer lexer = new ExprLexer(System.in); ExprParser parser = new ExprParser(lexer); parser.expr(); AST ast = parser.getAST(); ExprTreeParser treeParser = new ExprTreeParser(); int x = treeParser.expr(ast); } }
三,Hibernate对Antlr的应用 看过Antlr对HQL的解释,觉得EBNF系的方法要解释Java这样的编程语言还好些,如果要解释类自然语言的DSL就比较痛苦,所以情绪不是很高涨,挑一条最容易的"Delete from goods where ....." 匆匆走过场
Joel的一句话对我的影响比较大:"如果为了证明一个微不足道的问题需要花三个小时写下几黑板的证明步骤,那么这种机制不可能用来证明任何有趣的东西" 。对于我这个层次的程序员,antlr在我手中造不出有趣的DSL来。
Hibernate的HQL Grammar文件一共有三个,在/grammar目录下: 1.hql.g 定义Token类和Parser类,将HQL解释成hql的抽象语法树(AST) 2.hql-sql.g 定义Tree Walker ,将HQL AST转化为SQL AST,将生成模块与Hibernate解耦。 3.sql-gen.g 定义Tree Walker,从SQL AST生成sql
下面看 DELETE FROM GOODS的翻译过程
1.HqlBaseLexer extends Lexer 定义EQ: '='; LT: '<'; GT: '>';PLUS: '+';等符号 及IDENT: ( 'a' .. 'z' | '_' ) ( 'a' .. 'z' | '0' .. '9' | '_' | '$' )*
2.HqlBaseParser extends Parser 先定义DELETE="delete"; FROM="from"; MIN="min"; 等字符串 再定义:
statement : ( updateStatement | deleteStatement | selectStatement ) ; 三种Statement之一
deleteStatement : DELETE^ (optionalFromTokenFromClause) (whereClause)? ;DELETE为叶子,(whereClause)可选
optionalFromTokenFromClause! : (FROM!)? f:path { AST #range = #([RANGE, "RANGE"], #f); #optionalFromTokenFromClause = #([FROM, "FROM"], #range); } ;不是很好懂对吧,我也这样觉得,whereClause就更加不要看了。
3. HqlSqlBaseWalker extends TreeParser hql与sql的delete语句基本上是一样的,没什么转换。
4.SqlGeneratorBase extends TreeParser 根据SQL AST, 生成SQL语句
private StringBuffer buf = new StringBuffer(); protected void out(String s) { buf.append(s); }
statement : selectStatement | updateStatement | deleteStatement ; deleteStatement : #(DELETE { out("delete"); } from (whereClause)? ) ;输出"delete" from : #(f:FROM { out(" from "); } (fromTable)* ) fromTable : #( a:FROM_FRAGMENT { out(a); } (tableJoin [ a ])* { fromFragmentSeparator(a); } ) | #( b:JOIN_FRAGMENT { out(b); } (tableJoin [ b ])* { fromFragmentSeparator(b); } ) ; tableJoin [ AST parent ] : #( c:JOIN_FRAGMENT { out(" "); out(c); } (tableJoin [ c ] )* ) | #( d:FROM_FRAGMENT { nestedFromFragment(d,parent); } (tableJoin [ d ] )* ) ;
.晕了吧 ~~~~~ whereClause : #(WHERE { out(" where "); } ( conditionList | booleanExpr[ false ] ) ) ;
扩展Spring(2) ---Spring对各种数据访问框架的集成机制 何为数据框架集成。
数据访问框架原本好好的,Spring都干了什么呢? 一是用template类封装了数据框架那些资源获取和异常事务处理的废话代码,而且按照自己的意见给出一些增强函数。 二是将其纳入了Spring的声明式事务管理中。 对比Spring对Hibernate、JDBC的集成,还有 Spring Modules对 O/R Broker的集成,发现Spring的DAO框架主要有六个类: 1.Template 著名的Template类,用callback机制封装了除业务代码外的所有必要但废话的代码,重新封装了数据框架的API,并再附送一些增强版。 2.TransactionManager
实现PlatformTransactionManager接口,数据访问框架就能与Spring的事务机制(TransactionTemplate或AOP声明式事务)结合。 重要的类仅以上两个,以下的类都只有少量标准代码,完全可以忽略。 3.DAOSupport
实际DAO类的基类,负责保持template变量。如果你觉得它破坏了你的类层次结构,完全可以不用。 4.Accessor
template类的基类,defining common properties like DataSource and exception translator,也没大用。 5.Operations
template所实现的接口,定义template支持的数据访问函数和增强函数,template有多个实现时才有用。 6.Exception Translate的相关类和函数
异常翻译,Spring DAO很重视的一个功能。 Template类的代码
因为Hibernate本身很复杂,所以HibernateTemplate也不适合畏高晕车的人士如我观看。JDBC简单很多,但JDBCTemplate又忙着增强JDBC的功能,多出好多代码。所以我选O/R broker的集成代码来看,代码一共才280行。 注:如果不熟O/R broker,可以简单的认为broker=connection, executable = statement ,其余一切同Jdbc。 1.1主干函数 Execute(BrokerCallback action) step1. 获得Connection-- connecton = datasource.getConn(); step2. 准备Statement -- statement = new Statement(connection); step3. 执行Action的回调函数doInBroker(Statement)。这个doInBroker()方法由客户定义,会拿着传入的statement,执行种种操作。
try { action.doInBroker(statement ); } catch() { //翻译异常 }
1.2 template的API函数 虽然理论上大家可以直接使用execute(),在匿名内部类里调用数据访问框架的任何API。但java的匿名内部类不比闭包,代码难看无比,所以除了Robbin还没见到其他兄弟提倡直接用execute方法的。 因此,template也对数据框架的API进行了wrap,封装了用execute(StatementCallback action)来执行这些API的函数,如下段就是wrap 了O/R Broker的execute(String statementID.....)方法:
public int execute(final String statementID, final String[] paramNames, final Object[] values) throws DataAccessException { return executeWithIntResult(new BrokerCallback() { public Object doInBroker(Executable executable) throws BrokerException { applyNamedParamsToExecutable(executable, paramNames, values); return new Integer(executable.execute(statementID)); } }); } 另外还提供一些增强型、便利型的API(如selectOne() ,selectMany()),在参数、返回值上极尽变化。 TransactionManager的代码
比较复杂,一下说不清。但JDBC的DatasourceTransactionManager和Hibernate的HibernateTransactionManager的代码都很相近,说明这个TransactionManager其实也比较固定埋头狂抄就是了。 有兴趣的同学,可以响应某大老号召,实现ofbiz与spring的集成:) 系列文章: Spring 的微内核与FactoryBean扩展机制 扩展Spring(2)--Spring对各种数据访问框架的集成机制
作者: 江南白衣 人生像个舞台,请良家少女离开。 同样的,Freemarker和Velocity爱好者请跳过本篇。与弃用webwork而单用Spring MVC Controller接口的理由一样, Freemarker本来是一样好东西,还跨界支持jsp 的taglib,而且得到了WebWork的全力支持,但为了它的非标准化,用户数量与IDE的缺乏,在View层我们还是使用了 保守但人人会用,IDE友好的JSP2.0 配合JSTL。
对于B/S结构的企业应用软件来说,基本的页面不外两种,一种是填Form的,一种是DataGrid 数据列表管理的,再配合一些css, js, ajax的效果,就是View层要关注的东西了。 1. JSP 2.0的EL代替<c:out>JSP2.0可以直接把EL写在html部分,而不必动用<c:out>节点后,老实说,JSP2.0+JSTL达到的页面效果,已不比Velocity相差多少了。
<p>{goods.name}</p> 代替 <p><c:out value="{goods.name}"/></p>
(除了EL里面不能调用goods的函数,sun那帮老顽固始终坚持JSTL只能用于数据显示,不能进行数据操作,所以不能调用bean的get/set外的方法)
2. 最懒的form 数据绑定
Spring少得可怜的几个tag基本上是鸡肋,完全可以不要。 而Spring开发中的那些Simple Form tag又还没有发布。Spring的Tag主要用来把VO的值绑到input框上。但是,和Struts一样,需要逐个Input框绑定,而且语法极度冗长,遇到select框还要自己进行处理.....典型的Spring Sample页面让人一阵头晕.
而jodd的form tag给了我们懒人一个懒得多的方法,只要在<form>两头用<jodd:form bean="myVO"></jodd:form>包住,里面的所有input框,select框,checkBox...统统自动被绑定了,这么简单的事情,真不明白struts,spring为什么不用,为了不必要的灵活性么?
<form> <jodd:form bean="human"> <input type="text" name="name"> <input type="radiobox" name="sex" value="man"> <select name="age"> <option value="20">20</option> <option value="30">30</option> </select> </jodd:form> </form>
不过,jodd有个致命弱点是不能绑定内嵌对象的值。比如Order(订单)对象里有个Customer(顾客)对象,jodd就不能像 struts,spring一样用如下语法绑定:
<input name="customer.customerNo">
这是因为它的beanUtils比Jakata Common弱,用了一个错误的思路的缘故。 动用beanUtils修改一下就可以了,修改后的源码可以在这里下载。
3. DataGrid数据列表
DisplayTag和ValueList都属于这种形式的Tag Library。但最近出现的Extreme Table是真正的killer,他本身功能强大不说,而且从一开始就想着如何让别人进行扩展重载,比如Extend Attributes机制就是DisplayTag这样的让千人一面者不会预留。
4.css, javascript, ajax 天下纷扰,没有什么特别想讲想推荐的,爱谁谁吧。Buffalo, DWR, Scriptaculous, Prototype, AjaxTags, AjaxAnywhere, Rico, Dojo, JSON-RPC,看着名字就头痛。
相关文章 简化Spring(1)--配置文件 简化Spring(2)--Model层 简化Spring(3)--Controller层 简化Spring(4)--View层
作者: 江南白衣 Struts与Webwork的扇子请跳过本篇。 MVC不就是把M、V、C分开么?至唯物朴素的做法是两个JSP一个负责View,一个负责Controller,再加一个负责Model的Java Bean,已经可以工作得很好,那时候一切都很简单。 而现在为了一些不是本质的功能,冒出这么多非标准的Web框架,实在让人一阵郁闷。像Ruby On Rails那样简捷开发,可用可不用,而且没有太多的限制需要学习的,比如 Webwork这型还可以考虑。但像Struts那样越用框架越麻烦,或者像Tapestry那样有严重自闭倾向,额上凿着"高手专用玩具"的,用在团队里就是不负责任的行为了。
so,我的MVC方案是使用Spring MVC的Controller接口,写最普通的JavaBean作为Controller,本质就和当年拿JSP作Controller差不多,但拥有了Spring IOC的特性。
之所以用这么消极的选择标准,是因为觉得这一代MVC框架离重回RAD时代的标准还很远,注定了只是一段短暂的,过渡的技术,不值得投资太多精力和团队学习成本。
1. 原理
Spring MVC按植物分类学属于Martin Flower〈企业应用模式〉里的静态配置型Front Controler,使用DispatchServlet截获所有*.do的请求,按照xml文件的配置,调用对应的Command对象的handleRequest(request,response)函数,同时进行依赖对象的注入。 我们的Controller层,就是实现handleRequest(request,response)函数的普通JavaBean。
2. 优势 Spring MVC与struts相比的优势:
一是它的Controller有着从松到紧的类层次结构,用户可以选择实现只有一个HandleRequest()函数的接口,也可以使用它有很多回调函数的SimpleFormController类。
二是不需要Form Bean,也不需要Tapestry那所谓面向对象的页面对象,对于深怕类膨胀,改一个东西要动N个地方的人最适合不过。
三是不需要强XML配置文件,宣告式编程是好的,但如果强制成框架,什么都要在xml里面宣告,写的时候繁琐,看的时候也要代码配置两边看才能明白就比较麻烦了。
那Webwork呢?没有实战过,不过因为对MVC框架所求就不多,单用Spring MVC的Controller已经可以满足需求,就不多搞一套Webwork来给团队设坎,还有给日后维护,spring,ww2之间的版本升级添麻烦了。真有什么需要添加的,Spring MVC源代码量很少,很容易掌控和扩展。
3.化简
3.1. 直接implement Controller,实现handleRequest()函数
首先,simple form controller非我所好,一点都不simple。所以有时我会直接implement Controller接口。这个接口的唯一函数是供Front Controller调用的handleRequest(request,response)。 如果需要application对象,比如想用application.getRealPath()时,就要extends webApplicationObjectSupport。
3.2.每个Controler负责一组相关的action
我是坚决支持一个Controler负责多个action的,一个Controler一个action就像一个function一个类一样无聊。所以我用最传统的方式,用URL参数如msg="insert"把一组相关action交给一个Controler控制。ROR与制作中的Groovy On Rails都是这种模式,Spring也有MultiActionController支持。 以上三者都是把URL参数直接反射为Controller的函数,而 Stripes的设计可用annotation标注url action到响应函数的映射。
我的取舍很简单,反正Spring没有任何强制,我只在可能需要不重新编译而改变某些东西的时候,才把东西放在xml里动态注入。jsp路径之类的就统统收回到controller里面定义.
3.4.Data Binder
Data Binder是Controller的必有环节,对于Spring提供的DataBinder,照理完全可用,唯一不爽是对象如果有内嵌对象,如订单对象里面包含了Customer对象,Spring需要你先自行创建了Customer对象并把它赋给了Order对象,才可能实现order.customer.customer_no这样的绑定。我偷懒,又拿Jakarta BeanUtils出来自己做了一个Binder。
3.5.提取基类
作者: 江南白衣 因为Spring自带的sample离我们的实际项目很远,所以官方一点的model层模式展现就靠Appfuse了。 但Appfuse的model层总共有一个DAO接口、一个DAOImpl类、一个Service接口、一个ServiceImpl类、一个DataObject.....大概只有受惯了虐待的人才会欣然接受吧。 另外,Domain-Driven逢初一、十五也会被拿出来讨论一遍。 其实无论什么模式,都不过是一种人为的划分、抽象和封装。只要在团队里理解一致,自我感觉优雅就行了。 我的建议是,一开始DO和Manager一生一旦包演全场,DO作为纯数据载体,而Manager类放置商业方法,用getHibernateTemplate()直接访问数据库,不强制基于接口编程。当某天系统复杂到你直觉上需要将DAO层和Service层分开时,再分开就好了。 1.DataObject类 好听点也可以叫Domain Object。Domain Driven Development虽然诱人,但因为Java下的ORM框架都是基于Data Mapper模式的,没有Ruby On Rails中那种Active Recorder的模式。所以,还是压下了这个欲望,Data Object纯粹作一个数据载体,而把数据库访问与商业逻辑操作统一放到Manager类中。 2.Manager类 我的Manager类是Appfuse中DAO类与Service类的结合体,因为: 2.1 不想使用纯DAO 以往的DAO是为了透明不同数据库间的差异,而现在Hibernate已经做的很好。所以目前纯DAO的更大作用是为了将来可以切换到别的ORM方案比如iBatis,但一个Pragmaic的程序员显然不会无聊到为了这个机会不大的理由,现在就去做一个纯DAO层,项目又不是Appfuse那样为了demo各种ORM方案而存在。 2.2 也不想使用Service层来为Dao解耦
在JPetStore里有一个很薄的Service层,Fascade了一堆DAO类,把这些DAO类的所有方法都僵硬的重复了一遍。理论上一个Manager类可以管理数个Dao类,可以避免Dao之间直接耦合。但既然有Manager的情况下,商业逻辑都是写在Manager类的,那样子Manager似乎还是调用另一个Manager比较妥当,调用裸Dao可能存在忽略了某些逻辑。所以,耦合又从Dao层升到Service层了。 所以,除非你做的是超薄的不带逻辑的Service层,否则没有解耦的意义。 何况,对一个不是死搬书的Designer来说,组件边界之内的类之间的耦合并不是耦合。 3.去除不必要的基于接口编程
众所周知,Spring是提倡基于接口编程的。 但有些Manager类,比如SaleOrderManager ,只有5%的机会再有另一个Impl实现。95%时间里这两兄弟站一起,就像C++里的.h和.cpp,徒增维护的繁琐(经常要同步两个文件的函数声明),和代码浏览跳转时的不便(比如从Controler类跟踪到Service类时,只能跳转到接口类的相应函数,还要再按一次复杂的热键才跳转到实现类) 连Martin Flower都说,强制每个类都分离接口和实现是过犹不及。只在有多个独立实现,或者需要消除对实现类的依赖时,才需要分离接口。 3.1 DAO被强制用接口的原因 Spring IOC本身是不会强制基于接口的,但DAO类一般要使用Spring的声明式事务机制,而声明式的事务机制是使用Spring AOP来实现的。Spring AOP的实现机制包括动态代理和Cgilib2,其中Spring AOP默认使用的Java动态代理是必须基于接口,所以就要求基于接口了。 3.2 解决方法 那就让Spring AOP改用CGLib2,生成目标类的子类吧,我们只要指定使用声明式事务的FactoryBean使用CGLib的方式来实现AOP,就可以不基于接口编程了。 指定的方式为 设置proxyTargetClass为true。如下:
<bean class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" id="baseService" abstract="true"> <property name="transactionManager" ref="transactionManager"/> <property name="proxyTargetClass" value="true"/>
</bean> 又因为这些Service Bean都是单例,效率应该不受影响。 4.总结
对比Appfuse里面的5个类,我的Model层里只有VO作为纯数据载体,Manager类放商业方法。有人说这样太简单了,但一个应用,要划成几个JSP,一个Controller,一个Manager,一个VO,对我来说已经足够复杂,再要往上架墙叠屋,恕不奉陪,起码在我的项目范围里不需要。(但有很多项目是需要的,神佑世人) 后记:迫于世人的压力, SpringSide暂时还是把DAO和Service层分开了,但依然坚持不搞那么多接口。 另外,尽量利用IDEA的代码生成热键,为Manager类生成delegate的Dao类方法。 相关文章 简化Spring(1)--配置文件 简化Spring(2)--Model层 简化Spring(3)--Controller层 简化Spring(4)--View层
作者:江南白衣 序
人人都爱Spring加Hibernate。 但Spring MVC+hibernate的Sample如Appfuse的代码却算不得最简洁优美好读,如果在自己的项目中继续发挥我们最擅长的依样画葫芦大法,美好愿望未必会实现。 所以,Pramatic精神不灭。这个系列就是探寻最适合自己的Spring+Hibernate模式。 I-配置文件简化
我厌倦一切配置文件繁重的框架。 最好的情况是,框架提供极端灵活复杂的配置方式,但只在你需要的时候。 Spring提供了三种可能来简化XML。随着国内用户水平的提高,这些基本的简化技巧大家都已掌握。 大家可以直接看第3,第4点--Spring 1.2, Spring 2.0的后继改进。
1.1.autowire="byName" /"byType"
假设Controller有一个属性名为customerDAO,Spring就会在配置文件里查找有没有名字为CustomerDAO的bean, 自动为Controller注入。 如果bean有两个属性,一个想默认注入,一个想自定义,只要设定了autowire,然后显式的声明那个想自定义的,就可以达到要求。这就应了需求,在需要特别配置的时候就提供配置,否则给我一个默认注入。
还有一个更懒的地方,在最最根部的<beans>节点写一句default-autovwrie="byName",可以让文件里的所有bean 都默认autowrie。 不过Rod认为开发期可以这样,但Production Server上不应该使用Autowire。而我觉得那些自定义一次的地方比如TranscationManager应该详细定义,而Dao,Controller这种大量重复定义的bean就可以偷点懒了。
1.2.<bean>节点之间抽象公共定义和 Inner Bean
这太方便懒人了,想不到两个独立的XML节点都可以玩继承和派生,子节点拥有父节点的全部属性。 最好用的地方就是那个Transtion Proxy的定义。先定义一个又长又冗的父类,然后用子类去继承它。 另外,还有一个Inner Bean的机制,可以把DAO写成Proxy的内部类。为什么要写成内部类?为了让Proxy冒名顶替它去让Controller Autowire。(详见后面的示例)
1.3. 宽松的配置, To XML or Not to XML 据说Spring比Struts的配置宽松了很多,这就给人把东西从配置文件中撤回原码中的机会。 不赞成什么都往配置文件里晒,造成了Rich Information的配置文件,修改或者查看的时候,要同时打开配置文件和原码才能清楚一切。 而我希望配置文件就集中做一些整体的配置,还有框架必须的、无需管理的冗余代码。而一些细节的变化不大的配置和逻辑,就尽量别往里塞了。因此,Success/Fail View 的配置,不建议放在里面。
2.简化后的配置文件
1.Controller只剩下一句
<bean name="customerController" class="org.springside.bookstore.web.CustomerController" autowire="byName"/>
2.DAO也只剩一句
<bean id="customerDAO" class="org.springside.bookstore.dao.CustomerDao"/>
3.Service类只剩下5行
<bean id="customerManager" parent="baseTxService"> <property name="target"> <bean class="org.springside.bookstore.service.CustomerManager"/> </property> </bean>
3.Spring 1.2后xml语法简化
最主要的简化是把属性值和引用bean从子节点变回了属性值,对不喜欢autowire的兄弟比较有用。 当然,如果value要CDATA的时候还是要用子节点。另外,list的值可以用空格隔开也比较实用。
<property name="myFriendList"> <list> <value>gigix</value> <value>wuyu</value> </list> </property> 简化为 <property name="myFriendList" value="gigix wuyu"/>
4.Spring 2.0来了 如果没什么外力刺激,spring xml 可能就这样不会变了。但现在xml成了过街老鼠,被ror的默认配置和JDK5的annotation逼得不行,当然就要继续求变。 比如有好事者认为,节点名必须以bean打头,附加一个属性id来表示bean名;属性值必须搞一个property子节点,子节点上有个属性name来表示属性名,是给机器看的很不直观的东西。
<bean id="customerDAO" class="org.springside...CustomerDAO"> <property name="maxCount" value="10"> </bean> 给人看的东西应该就写成
<customerDAO class="org.springside....CustomerDAO" maxCount="10"/> Spring 2.0正用schema实现类似的语法,具体请看它的JPetStore sample。
5.使用Spring自带的DTD使编辑器Smart.
如果没有用Eclipse的Spring插件,那至少也要使用spring自带的dtd使XML编辑器smart一些,能够自动为你生成属性,判断节点/属性名称有没有拼错等。
6.还有更变态的简化配置方法 比如autoproxy,不过我觉得更简化就不可控了,所以没有采用。
相关文章 简化Spring(1)--配置文件 简化Spring(2)--Model层 简化Spring(3)--Controller层 简化Spring(4)--View层
作者:江南白衣
1.Groovy的最新八卦之处 1.1 Wiki: http://docs.codehaus.org/pages/listpages.action?key=GROOVY 1.2 Mail list的在线浏览和rss定阅 Developer List http://dir.gmane.org/gmane.comp.lang.groovy.devel User List: http://dir.gmane.org/gmane.comp.lang.groovy.user
2.Groovy的开发现状和未来 编译期健壮性大大增强的1.0 JSR-2,全力除Bug的1.0 JSR-3,JSR-4已经发布。 第二次全球Groovy 开发人员FB大会也在巴黎开完,有决议若干。 理论上 1.0正式版很快就要发布。 而计划中的1.1版本将支持 ruby的maxin、continuations 和JDK1.5的Annotatin。
3.Groovy and Java的暧昧关系 作为Java的私生子,Groovy的最终可执行状态是Java class bytecode,所以Java代码里可以把它当普通Java Bean一样调用。还有,Groovy的基础类库,都是用Java代码来写的。
3.1 编译Java class bytecode 就像JSP的最终面目是servlet class,GroovyC也会把groovy文件编译成Java class bytecode以在JVM上运行。 其中Groovy Class会编译成GroovyObject的子类,Groovy Script代码段会编译成Script的子类。 可以用GroovyC来静态编译,也可以在Java程序里用GroovyShell动态parse Groovy文件。
3.2 在Java代码中调用Groovy 1.Groovy类: GroovyObject类默认有get/setProperty()和invokeMethod()的反射接口,在Java代码里,可以直接调用Groovy类的方法,也可以通过反射接口来调用。
2.Script代码段: Script类有Script(Binding)构造函数和run()接口,在java里先通过Script(Binding)构造一个Script类,绑定变量,然后通过run接口进行调用。
3.3 在Java中直接使用Groovy的类库 Groovy和Groovy的框架类库都是用Java写出来的的。所以有些类库如SimpleTemplateEngine,也可以在Java里直接使用,不用绕Groovy一圈。
4.groovyJ插件 groovyJ是IDEA插件,有语法变色和Run()功能,更有用的功能是编译Java文件目录时,会把其中的groovy文件也一同编译。而日后将支持重构、类间跳转等功能,值得期待。 而Eclipse插件只有Run()和语法变色,而且随着Eclipse的升级时灵时不灵,正在花时间实用化中。 NetBeans开了个Coyote的项目来支持脚本语言。
5.一些重要的开发人员 一个PM: Guillaume Laforge
两个Founder: Bob Mcwhirter (同时是Drools,Dom4J的founder) , James Strachan (Core Developers Network,同时work on Geronimo,Drools,ServiceMix 和很多Jarkarta 项目,似乎拥有无穷的精力)
三个来自ThoughtWorks的开发人员: Jame Walnes,Chris Stevenson,Matt Foemmel
四个star of JSR-2: Jeremy Rayner, Jochen Theodorou,和两位老大一起改进JSR-2编译的强壮性和出错信息显示 Franck Rasolo:IDEA插件GroovyJ的开发人员 Christian Stein :Groovlet,Template的开发人员
Steven Devijver : Grails--Groovy on Rails的主持,同时是Spring Modules的leader。
6.有哪些使用Groovy的项目
Grails(Groovy on rails,大老们非常期待的项目,把rails在MVC和ORM的优点抄到java下,而且是基于Spring Framework的)
Drools(规则引擎, 用户可以用groovy写规则)
eXo platform(porlet/Service container,本身核心是groovy,用户可以用groovy 来写Porlet)
XWiki (第2代的wiki引擎、Application引擎。用户可以用groovy写Plug-in,Marco和Application)
RIFE(一个MVC/Web framework,用户可以用groovy写action和配置layout,workflow。还含有一个CRUD框架,用户用groovy 定义domain class,滴嗒一下,就能获得一个CRUD模型)
7.Groovy-all-1.0-jsr3.jar groovy需要asm和antlr包的支持,使用groovy-all-10-jsr3.jar,将预带这两个包的正确版本,非常省心。
8.Migrating to JSR JSR版本语法的的最大改动有两处。网上很多Groovy文章都还是基于旧语法的,需要自己改正过来。 8.1 为了加强代码健壮性,Class里的变量需要用def 定义。而在script里的变量因为是动态binding的,仍然不需要def定义。
8.2 多行的String需要用 """ """而不是" "来括住。
9.SimpleTemplateEngine-总共200行就实现了JSP engine的功能 动态语言开发框架很方便,所以Ruby on Rails没有IDEA级的IDE都能这么就把MVC、ORM都实现了一遍。 请看src/groovy/text/SimpleTemplateEngine.java,总共219行。 原理就是把模版中的文本部分替换成println """ 文本""" ,Groovy部分照搬,生成一个新的Groovy script,然后拿GroovyShell执行一遍就完成了。
10.如何用Java实现动态语言
把GroovyC编译出来的class文件再用jad反编译,可以看到如何用Java去实现一门动态语言。 主要是多了一个MetaClass, 不断的反射反射,运行时还非常依赖Asm
最简单的例子: Groovy文件:
public class Customer { private String id; }
编译出来的Java文件
public class Customer implements GroovyObject { private String id; transient MetaClass metaClass; public Customer() { Object obj = ScriptBytecodeAdapter.invokeStaticMethod ("org.codehaus.groovy.runtime.ScriptBytecodeAdapter", "getMetaClass", this); Metaclass metaclass = (MetaClass)ScriptBytecodeAdapter.asType(obj, groovy.lang.MetaClass.class); } public Object invokeMethod(String s, Object obj) {} public Object getProperty(String s) {.} 11.ant 的编译脚本
<path id="groovy.classpath"> <pathelement path="${basedir}/ROOT/WEB-INF/classes/"/> <fileset dir="${basedir}/ROOT/WEB-INF/lib"> <include name="*.jar"/> </fileset> </path> <taskdef name="groovyc" classname="org.codehaus.groovy.ant.Groovyc"> <classpath refid="groovy.classpath"/> </taskdef> <target name="groovy"> <groovyc destdir="${project.basedir}/ROOT/WEB-INF/classes" srcdir="${project.basedir}/src" listfiles="true"> <classpath refid="groovy.classpath"/> </groovyc> </target>
作者:江南白衣
前篇:〈在Spring+Hibernate框架下,用动态语言写业务类〉讲述在Spring+Hibernate的架构下,因为动态语言所带来的利益,把一部分业务类改用Groovy编写并编译成Java Class文件。 而且,因为Groovy的强大与简便,加上与Java亲密无间的关系,一些框架类也可以逐渐考虑用Groovy编写。
1.虽然多是星零的好处,但忽然间一整吨好处摆在面前还是很让人感动的。
除了动态语言和闭包、MOP,Groovy其他的特性多是对J2SE中设计不合理的地方逐一进行修正,集合、IO、字符串操作......虽然多是星零的好处,但忽然间以整吨好处摆在面前还是挺让人感动的。
同时,Groovy完全兼容Java语法,但又提供糖糖选择的方式感觉很贴心。(Groovy唯一不支持的java语法就是Inner Class的定义和函数定义里的"throws Exception" , 私生子的好处啊)
隐约觉得因为动态语言的无类型,还有闭包这样带着Lisp式FP的印记,加上MOP这样的机制,可能会激发更大的变革发生。
1.动态类型 动态类型在Framework型项目中非常重要,多少设计模式呕心沥血,就是为了和这个类型搏斗。 而且,如果你是把代码编译成java Class,健壮性不会减低太多。 2.闭包 fp的基础,没有闭包的C++用函数指针,java用匿名内部类,都比他差远了。 详看Matin Flower <闭包>文章的中文版 ,在一段文件操作的Script中试演了一下,果然使代码简洁了好些。
3. MOP Groovy的Team Leader-- Guillaume Laforge最喜欢的一样特性,groovy 嵌入式XML语法的基础,对属性和方法访问的intercept 机制。详看另一篇blog。 又比如,在MOP下,DAO那一堆findByName,findByTitle不用再逐一实现了,实现一个findBy即可拦截其他子虚乌有的findByXXX。
4.强大的String类 a.可以直接在String中嵌入EL, "orderBy ${sortColumn}",省了好多"和+号。 b.hql 可以多行,不用写N多"和+ , 写sql时特别方便。 c.简单集成了正则表达式 If ("name"==~ "na.*") {println "match!"}
5. 集合与循环的语法 for (car in cars) { println car } for ( e in map ) { x += e.value}
或者 car.each{print it} 集合也可以直接定义了,如 def myList = ["Rod", 3, Date()] def myMap = ["Neeta":31, "Eric":34]
6.为JDK的基础类扩展了一系列Helper方法 //原来StringTokenizer类的功能被简单的集成到String类中了 names.tokenize(",").each{......} 其他基础类的扩展见Groovy-JDK
7.简化的Bean定义与赋值 //自动生成Getter和Setter class Customer { Integer id; String name; } //简便的对象赋值 customer = new Customer(id:1, name:"calvin"); customer2 = new CUstomer(id:2); 重新使对象的属性public,对java滥用getter,setter是一种修正。
8. Object内建的反射语法 customer.getAt("name") //得到属性name, customer.invokeMethod("someFunction") //调用方法someFunction 从此不再需要Apache BeanUtils。
9.GPath--内置的XML语法,和Fremarker类似。 传说中JDK7.0的功能 jdom和Dom4j可以安息了 book = new XmlSlurper().parseText("<book writer='calvin'><title>D</title></book>") println book.title; println book[@writer]; println book.children().size();
10.运算符重载 //向数组添加对象 params << customer.getAt(it); 还有如C++的向String,InputStream添加对象. 还有集合类相加,如list1+list2
11.简化了IO操作
12.省略了每行末尾的分号 既然每行都要的,何必多此一举呢? 另外return语句也可以省略,不过我还是习惯写:)
2.Groovy版CustomerDAO的示例:
package com.itorgan.myappfuse.dao; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; public class CustomerDAOGroovy extends HibernateDaoSupport { public insert(customer) { getHibernateTemplate().save(customer) }
public List getAllValid(sortColumn) { def hql = """from Customer customer where customer.status='valid' order by ${sortColumn}""" def query = getSession().createQuery(hql) return query.list() }
public boolean isUnique(customer, uniqueColumnNames) { def params = [] def hql = "select count(*) from Customer customer where " def first = true uniqueColumnNames.tokenize(",").each { if (!first) hql += " or " else first = false hql+="customer.${it}=?"
params << customer.getAt(it) } def result = getHibernateTemplate().find(hql,params.toArray()) return ( result.get(0) == 0) } }
如果羡慕Ruby On Rails可以用动态语言来编码,但又舍不得Spring、Hibernate这些Javaer深以为傲的框架,那么有一种折中的方案: 仍然使用Spring+Hibernate框架,而用Groovy/Jython/JRuby来编写Controller类、Service类、DAO类,并把它们编译成普通的Java Class文件来使用。 BuggyBean的blog里用Jython示范了这种方案。
1.why 动态语言? 现在的动态语言都已经很面向对象,和传统的用来写写外围脚本(测试,安装)的script语言已经不同,而且Groovy,Jython,JRuby写成的类除了动态载入外,都可以编译成静态的Java Class文件,所以已能很好的承担J2EE应用里业务类的角色了。
动态语言有什么好处呢,请参看<Groovy写业务类、框架类的那一吨好处>。
2. 八卦:各种动态语言的Java实现 Groovy ,BeanShell,Jython(Python),JRuby(Ruby),Rhino(JavaScript), Jacl(TCL),Bistro(SmallTalk),Kawa(Lisp/Schema)
3.哪种动态语言? Jython总是若断若续,气若游丝的样子,而且现在都才移植到Jython2.1,比Python2.4 慢了几拍,很信不过。
JRuby呢? Dion Almaer在JRuby0.8.2发布时说:"The day JRuby gets up to Jython levels, and then C Ruby levels, will be a great day.",字面上JRuby比Jython还要不靠谱。 Ruby还有一个不好是暂时没有好的IDE(连那个收费的ruby-ide都好弱),如果没有好的IDE,可以抵消掉大部分语言本身的优势,真不能想像Ruby On Rails是用怎么个UltraEdit级的编辑器写出来的。
Groovy的弱势是1.0正式版还没有发行,用户社区还不大。
因为现在选的是Java框架下嵌入哪种动态语言作为业务类。所以Python和Ruby的用户社群和大量的已有项目作用不是很大。而Groovy比起两位舶来品, 1.作为私生子,嵌入性理所当然是最好的,两者的关系暧昧得不得了。 2.另一个天然优势是能兼容Java的语法,把Java代码copy到Groovy几乎不作什么修改(only不支持throws定义语句和Inner Class),团队的学习曲线平滑。 3.因为不是移植项目,语言升级时没有时间差,不用看移植人的脸色。
so,我会选Groovy,等它的正式版出来之后。
作者: 江南白衣 Groovy的Team Leader-- Guillaume Laforge说,MOP(Meta Object Protocol)是他最喜欢的Groovy特性。 MOP是对属性、方法进行拦截解释的简单机制,intercept 已经因为AOP而被大家熟悉。 Groovy的类都继承于GroovyObject,GroovyObject有get/setProperty()和invokeMethod()两个函数,当客户调用不存在的属性和方法时,就会交由这两个函数来处理,在Ruby里,这个方法被更贴切的命名为method_missing()。Groovy类可以通过重载这两个函数,加入自己的hook和behavior,比Java简单的多的实现 Proxy和 Delegator。
而更重要的是,MOP函数可以充当领域语言解释者的角色,拦截一些存在于领域语言的而在Class里根本没有定义的属性、方法来进行解释,这就是Groovy里XML嵌入式语法的魔法根源。 IBM DW有一篇专门的文章 :《PRACTICALLY mini-languages and MOPs Of Groovy:》 比如如下的xml
<shop> <book name="foo"> <writer>庄表伟< SPAN>writer> < SPAN>book> < SPAN>shop>
可以用groovy这样访问
println node.book.writer.text()
node类当然没有book,writer这样属于领域语言的属性,但通过重载getPropety(String name)函数,node类可以把对book,writer属性的访问,转换成相应DOM节点的访问。 实现请参看org.codehaus.groovy.sandbox.util.XMLList类的public Object getProperty(final String elementName)函数。
Guillaume Laforge说,It's an aspect of Groovy which makes the language very powerful, but rare are those who really know and use it.
|
|
| 日 | 一 | 二 | 三 | 四 | 五 | 六 |
---|
27 | 28 | 29 | 30 | 31 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|
公告
常用链接
随笔分类
随笔档案
朋友
积分与排名
最新评论
阅读排行榜
|
|