摘要: 我相信很多在刚刚使用Spring的同事会考虑一个问题:我们为什么要用Spring,Spring虽然给我们带来了一些好处,可是,似乎好处似乎不是那么明显。IOC的作用似乎也很牵强。
1.前言我相信很多在刚刚使用Spring的同事会考虑一个问题: 我们为什么要用Spring,Spring虽然给我们带来了一些好处,可是,似乎好处似乎不是那么明显。IOC的作用似乎也很牵强。 所以,冷凝沙漠在此记录了自己的一些Spring开发经验,与各位分享。 2.一个常见的业务场景 |
publicclass Example1 { publicstaticvoid main(String[] args) { String specialParam = args[0];// 这是一个特定的参数
Object[] param1 = new Object[] {}; Object[] param2 = new Object[] {}; Object[] param3 = new Object[] {}; // ………… 以上是一堆常见、有规则的其他参数
if ("CanShu1".equals(specialParam)) {
// ………… 一堆冗长的程序
} elseif ("CanShu2".equals(specialParam)) { // ………… 又一堆冗长的程序
} elseif ("CanShu3".equals(specialParam)) {
// ………… 再一堆冗长的程序
} else {
// ………… 其他默认的冗长的程序 } } } |
2.3.优缺点分析
从这个示例的角度,代码挺清晰的,但是到了实际应用中,可能就会有数十套逻辑,可能会有长达数千行代码,可能会绕晕很多人。
3.一个常见的不好的修改方式
3.1.场景简介与代码示例
如下是很常见的一个修改方式,将不同的流程提炼出独立的方法,让逻辑判断的位置独立出来。
publicclass Example2 {
publicstaticvoid main(String[] args) { String specialParam = args[0];// 这是一个特定的参数
Object[] param1 = new Object[] {}; Object[] param2 = new Object[] {}; Object[] param3 = new Object[] {}; // ………… 以上是一堆常见、有规则的其他参数
// 提炼独立方法 if ("CanShu1".equals(specialParam)) { handleCanShu1(param1, param2, param3); } elseif ("CanShu2".equals(specialParam)) { handleCanShu2(param1, param2, param3); } elseif ("CanShu3".equals(specialParam)) { handleCanShu3(param1, param2, param3); } else { handleDefault(param1, param2, param3); } }
publicstaticvoid handleCanShu1(Object[] param1, Object[] param2, Object[] param3) { // ………… 一堆冗长的程序 }
publicstaticvoid handleCanShu2(Object[] param1, Object[] param2, Object[] param3) { // ………… 又一堆冗长的程序 }
publicstaticvoid handleCanShu3(Object[] param1, Object[] param2, Object[] param3) { // ………… 再一堆冗长的程序 }
publicstaticvoid handleDefault(Object[] param1, Object[] param2, Object[] param3) { // ………… 其他默认的冗长的程序 } } |
3.2.优缺点分析
相信各位维护的旧项目里面不会缺乏此类代码。
从维护的角度来看:
1.如果要继续更新这个代码,也只能不断的在这个类上下功夫。很可能这个类未来会变成一个“只有上帝知道它是干嘛的”的复杂类。
2.必须要有源码才能更新代码,才能重新编译。相信我,很多维护方没有源代码,这种修改对于程序员而言属于“Mission Impossible”。当然,对于搞IT的,天天玩程序员版的碟中谍,也是家常便饭。
因此,大部分Java培训师、Team Leader、代码走查员都会说:这样不好,这样不好!
4.中规中矩的工厂模式
4.1.场景简介
从模式的角度来看,工厂模式自然是这里最适合的模式:
咱们先提炼一个公共的接口,定义一个调用的工厂,再不断实现这个接口,并将接口注册到工厂。
未来如果要增加一种业务,就单独增加一个实现类,并注册到工厂就行了。
4.2.代码示例
以下是一套简单的实现代码:
publicclass Example3 { publicstaticvoid main(String[] args) { String specialParam = args[0];// 这是一个特定的参数
Object[] param1 = new Object[] {}; Object[] param2 = new Object[] {}; Object[] param3 = new Object[] {}; // ………… 以上是一堆常见、有规则的其他参数 Example3Service service = Example3Factory.getService(specialParam); service.run(param1, param2, param3);
} } |
调用其实是很简单的。
package com.test; /** * 提炼出工厂和接口 */ publicclass Example3Factory { publicstatic Example3Service getService(String specialParam) { if ("CanShu1".equals(specialParam)) { returnnew Example3ServiceCanShu1Impl(); } elseif ("CanShu2".equals(specialParam)) { returnnew Example3ServiceCanShu2Impl(); } elseif ("CanShu3".equals(specialParam)) { returnnew Example3ServiceCanShu3Impl(); } else { returnnew Example3ServiceDefaultImpl(); } } } |
工厂类可以简单的如例子中写死,也可以写成collection形式,然后进行注册。
package com.test; /** * 提炼出工厂和接口 */ publicinterface Example3Service { publicvoid run(Object[] param1, Object[] param2, Object[] param3); }
|
接口神马的最简单了,以下就是接口实现:
package com.test;
publicclass Example3ServiceCanShu1Impl implements Example3Service { publicvoid run(Object[] param1, Object[] param2, Object[] param3) { // ………… 一堆冗长的程序 } } |
package com.test;
publicclass Example3ServiceCanShu2Impl implements Example3Service { publicvoid run(Object[] param1, Object[] param2, Object[] param3) { // ………… 又一堆冗长的程序 } } |
package com.test;
publicclass Example3ServiceCanShu3Impl implements Example3Service { publicvoid run(Object[] param1, Object[] param2, Object[] param3) { // ………… 再一堆冗长的程序 } } |
package com.test;
publicclass Example3ServiceDefaultImpl implements Example3Service { publicvoid run(Object[] param1, Object[] param2, Object[] param3) { // ………… 其他默认的冗长的程序 } } |
4.3.优点分析
形成工厂模式的优点很明显:
l提供创建对象的接口. 为系统结构提供了非常灵活强大的动态扩展机制,只要我们更换一下具体的工厂方法,系统其他地方无需一点变换,就有可能将系统功能进行改头换面的变化。
l除了工厂类以外的程序都不需要去了解具体的实现情况,所以给程序实现带来了很多方便。
l将程序的一部分复杂度集中在接口的实现上,一部分程序员可以专心于如何通过实现接口来实现业务逻辑,另一部分程序员可以专心于通过更新工厂注册方式来将新的实现对接到整个程序中。
4.4.缺点分析
形成工厂模式的缺点也很明显:
l工厂类依旧需要去了解具体的实现类以及其参数,当程序复杂度到一定程度时,工厂类依旧可能很复杂。
l还是必须要有源码才能更新工厂类的代码,才能重新编译。
5.开始引入Spring
5.1.场景简介
既然工厂模式的缺点集中在工厂上,那么就优化工厂好了。我们可以把工厂优化为抽象厂,也可拆掉这个工厂,用Spring来替代这个工厂。
可以这样想,工厂类就是提供了一个Map,根据一个特定的key值,找一个特定的Bean。如果仅仅是这样,Spring自身是不是就能作为这个工厂了?
5.2.代码示例
调用方的代码依旧很简单:
package com.test; import java.util.Map; import javax.annotation.Resource; publicclass Example4 { @Resource private Map<string,> example4ServiceMap; @Resource private Example4Service example4ServiceDefaultImpl;
publicvoid runMain(String specialParam) { Object[] param1 = new Object[] {}; Object[] param2 = new Object[] {}; Object[] param3 = new Object[] {}; // ………… 以上是一堆常见、有规则的其他参数
Example4Service service = example4ServiceMap.get(specialParam); if (service == null) { service = example4ServiceDefaultImpl; } service.run(param1, param2, param3); } } |
工厂的内容直接用Spring配置完成:
<!--?<-->xmlversion="1.0"encoding="UTF-8"?><!--?<--> <<>beansxmlns=http://www.springframework.org/schema/beans<!--<--> ……………… xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd …………">
<<>beanid="example4ServiceMap"<!--<--> class="java.util.HashMap"> <<>constructor-arg><!--<--> <<>map><!--<--> <<>entrykey="CanShu1"value-ref="example4ServiceCanShu1Impl"/><!--<--> <<>entrykey="CanShu2"value-ref="example4ServiceCanShu2Impl"/><!--<--> <<>entrykey="CanShu3"value-ref="example4ServiceCanShu3Impl"/><!--<--> <<!--</span-->map> <<!--</span-->constructor-arg> <<!--</span-->bean>
<<!--</span-->beans> |
接口以及其实现类没有啥变化,只是多了些注解而已。当然,需要好好管理Spring中的Bean:
package com.test; /** * 提炼出工厂和接口 */ publicinterface Example4Service { publicvoid run(Object[] param1, Object[] param2, Object[] param3); } |
实现这里就只多了个注解:
package com.test; import org.springframework.stereotype.Service;
@Service publicclass Example4ServiceCanShu1Impl implements Example4Service { publicvoid run(Object[] param1, Object[] param2, Object[] param3) { // ………… 一堆冗长的程序 } } |
package com.test;
import org.springframework.stereotype.Service;
@Service publicclass Example4ServiceCanShu2Impl implements Example4Service { publicvoid run(Object[] param1, Object[] param2, Object[] param3) { // ………… 又一堆冗长的程序 } } |
package com.test;
import org.springframework.stereotype.Service;
@Service publicclass Example4ServiceCanShu3Impl implements Example4Service { publicvoid run(Object[] param1, Object[] param2, Object[] param3) { // ………… 再一堆冗长的程序 } } |
package com.test;
import org.springframework.stereotype.Service;
@Service publicclass Example4ServiceDefaultImpl implements Example4Service { publicvoid run(Object[] param1, Object[] param2, Object[] param3) { // ………… 其他默认的冗长的程序 } } |
5.3.优缺点分析
这个方法的优点在于,不需要代码书写工厂类了,只是一个简单的XML配置文件,就搞定了,因此,将新类注册到整个程序时,也就不需要源码、编译之类的,如果没有特殊情况,只要编译实现类就行了。
6.进一步简单化
6.1.场景简介
那能不能更简单一些呢?
能!
6.2.代码示例
调用方的稍微调整一下,会更简单:
package com.test;
importjava.util.Map;
import javax.annotation.Resource;
import……………….SpringUtil;
publicclass Example5 {
@Resource private Example5Service example5ServiceDefaultImpl;
publicvoid runMain(String specialParam) { Object[] param1 = new Object[] {}; Object[] param2 = new Object[] {}; Object[] param3 = new Object[] {}; // ………… 以上是一堆常见、有规则的其他参数
Example5Service service = (Example5Service) SpringUtil.getBean("example5Service" + specialParam + "Impl"); if (service == null) { service = example5ServiceDefaultImpl; } service.run(param1, param2, param3); } } |
仅仅是将调用方式微调一下,配置文件也就不需要了。
而接口和实现类则不会受到影响,不再重复粘贴了。
这里的SpringUtil在各个公司应该都有自己的简单实现,就不自己费力写了。
当然,事务之类的周边事宜还是需要各位自己在配置文件中好好管理起来的。
6.3.优缺点分析
到这个时候,程序已经初步从配置变成了流行的“规则”了。运维人员需要增加一个新业务时,只是需要保证类名保持一定的规则,而且Bean name在管理范围内,没有啥冲突,就行了。
7.后语
在此,冷凝沙漠只是记录了一个初步的思路来给大家启发大家思路。真正在程序开发的过程中需要比这更加细腻的设计过程。
希望这篇博文能解决大家在工作中遇到的问题。