为什么要使用Spring?(Spring的设计目标是什么?Spring解决了什么样的问题,让我们如此推崇?)很多书籍对于Spring的普遍解释是用来替代更加重量级的企业级Java技术,尤其是EJB,Spring为开发者提供了一个一站式的轻量级应用开发框架(平台)。Spring是技术发展的产物,我们年轻的一代学者没有经历过三层模型,没有经历过EJB,或许真的很难对Spring这样的技术有切身的体会,充其量只能停留在一些表面的认识之上。在写这篇文章的时候,我也只是对为何使用Spring有一个模糊的认识而已。知史可以明鉴,建议大家可以阅读这篇文章:,描述了Web发展的简史。
由于我对EJB并没有特别的了解,也不敢妄加评判,下面只谈Spring带给我们的好处。总所周知,IOC和AOP是Spring所依赖的根本。
IOC容器
控制反转(Inverse of Control,IOC)有时也被称为依赖注入,是一种降低对象之间耦合关系的设计思想。
耦合会有什么问题呢?耦合具有两面性。一方面,紧密耦合的代码难以测试,难以复用,难以理解。如果对象之间通过显式调用进行交互会导致调用者与被调用者存在着非常紧密的联系,其中一方的改动还将会导致程序出现很大的改动。并且典型的表现出“打地鼠”式的bug特性(修复一个bug,将会出现一个或者更多的bug)。另一方面,一定程度的耦合有时必须的,完全没有耦合的代码什么也做不了。为了完成有实际意义的功能,不同的类必须以适当的方式进行交互。总而言之,耦合是必须的,但是应该被谨慎管理。
如果使用Spring作为应用开发平台,通过使用Spring的IOC容器,可以对这些耦合关系(对Java代码而言)实现一个文本化,外部化的工作,也就是说,通过一个或几个XML文件,我们就可以方便的对应用对象的耦合关系进行浏览,修改和维护,这样,可以在很大程度上简化应用开发。同时,通过IOC容器实现的依赖反转,把依赖关系的管理从Java对象中解放出来,交给了IOC容器来完成,从而完成了对象之间的关系解耦;原来对象—对象的关系,转化为对象—IOC容器—对象的关系,通过这种关系,更体现出IOC容器对应用的平台作用。
如图所示,假设为一家卖茶的商店设计一套管理系统,这家商店开业时,只卖绿茶。随着规模的扩大,未来可能需要销售例如红茶,等其他的茶类品种。传统的实现方式会针对茶抽象化一个基类,绿茶类只需要继承自该基类即可。
当需要使用绿茶时,只需要执行以下代码:AbstractTea t = new GreenTea();这种方法是可以满足当前的设计要求的。如果店家准备更改为销售红茶,那么需要实现一个BlackTea的类,所有用到AbstractTea t = new GreenTea();的地方修改为AbstractTea t = new BlackTea();这种创建对象实例的方法,往往导致程序的改动量非常大。
有什么办法可以增强系统的可扩展性呢?——设计模式,此时可以使用设计模式中的工厂模式来把创建对象的行为包装起来。实现方法如下所示。
通过以上的方法,可以把创建对象的过程委托给TeaFactory来完成,在需要使用Tea对象时,只需要调用Factory类的getTea方法即可,具体创建对象的逻辑在TeaFactory中来实现,那么当商家需要把绿茶替换成红茶的时候,系统中只需要改动TeaFactory中创建对象的逻辑即可,采用了工厂模式后,只需要在一个地方做改动就可以满足要求。
虽然说采用工厂设计模式之后增强了系统的可扩展性,但是从本质上来讲,工厂模式不过是把程序中会变动的逻辑移动到工厂类中,当系统中的类较多的时候,在系统扩展时需要经常改动工厂类中的代码。采用IOC设计思想后,程序会有更好的可扩展性。如下图所示:
Spring容器将会根据配置文件来创建调用者对象(Sale),同时把被调用的对象(AbstractTea的子类)的实例化对象通过构造函数或者Set()方法的形式注入到调用者对象中。
所以到底有什么好处呢?还是要修改配置文件,修改配置文件和修改代码有什么区别呢?代码的可读性也会因为配置文件变的比较差?看了这么多,真的是道理我都懂?实际意义的好处,我还是没有明白。希望有理解的同学,可以留言告诉我。
另外还有一点,IOC容器中默认创建的对象是单例的,就像是一个对象的池子一样,类似于线程池,数据库的连接池,可以复用的对象。
在面向接口编程,因此我们在每次使用一些工具类的时候,或者一些需要实例化才可以调用的对象,这就会存在大量的实例化对象,并且他们的生命周期可能就是从方法的调用开始到方法的调用结束为止,无形之中加大了GC回收的压力。
了解设计模式的可能会想到使用单例模式的方式来解决这个问题,以此来避免大量重复的创建对象,但是我们还要考虑到众多的这种对象的创建都需要改成单例模式的话,是一个耗时耗力的操作。对于这个系统来说,如果都把这种面向接口的对象实现类转换为单例模式的方式的话,大概也要写十几个或者上百个这种单例模式代码,而对于一个单例模式的写法来说,往往是模板式的代码。
可以看出,这种方式有两个问题:
(1)业务代码与单例模式的模板代码放在一个类里,耦合性较高;
(2)大量重复的单例模式的模板代码;
从上述可以看出,使用的单例模式虽然从性能上有所提高,但是却加重了我们的开发成本。因此只会小规模的使用,例如我们操作JDBC的Utils对象等
我们即需要一个单例的对象来避免系统中大量重复对象的创建和销毁,又不想因为使用单利模式造成大量重复无用的模板代码和代码的耦合!Spring的IOC容器便是做到这一点。
具体而言,IOC主要有以下两个方面的优点:
- 通过IOC容器,开发人员不需要关注对象如何被创建的,同时增加新类也非常的方便,只需要修改配置文件即可实现对象的热插拔。
- IOC容器可以通过配置文件来确定需要注入的实例化对象,因此非常便于进行单元测试。
尽管如此,IOC容器也有自身的缺点,具体表现如下:
- 对象时通过反射机制实例化出来的,因此会对系统性能有一定的影响。
- 创建对象的流程变得比较复杂。
AOP
面向切面编程(Aspect-Oriented Programming,AOP)是面对对象开发的一种补充,它允许开发人员在不改变原来模型的基础上动态地修改模型以满足新的需求。
为什么要使用AOP?
这篇博客的内容,我认为是一个很好的表述。
很有人会有一个疑问,AOP面向切面的编程和,把这些重复性的代码抽取出来,调用公共方法有什么区别?调用这些重复性代码会导致,我们的业务代码已经被这些非核心的代码所混淆,并且占据了大量的空间!使得代码的可读性变差。
设计模式中的动态代理,可以实现一个简单的AOP功能,那么Spring的AOP和JDK的动态代理设计模式又有什么区别呢?我们的代理目标对象必须实现一个接口,要是一个接口的实现类,这是因为再生成Proxy对象的时候这个方法需要一个目标对象的接口:
显然,有些特殊的场景使用JDK动态代理技术的话,已经不能够满足我们的场景了。Spring在这里使用CGLib动态代理的方式实现了我们的这种诉求。CGLib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势的织入横切逻辑。