技术交流笔记——SPRING IOC 原理
前两天组织了一次内部的技术交流,主题是spring ioc框架的原理。时间很短,只有一个小时,所以内容比较简单,主要是几个题目,和围绕题目的一些讨论。
需求变化的场景
首先考虑这样一个场景:
系统中有如下几个类。
Class A {
B b;
C c;
public void service(){
Assert b!=null;
Assert c!= null;
....
}
}
Class B{
D d;
}
Class C{
E e;
F f;
}
Class D{}
Class E{}
Class F{}
在调用A的service时,必须先对它进行初始化,并设置b和c的初始值。初始化B时,必须先初始化d;初始化C时,必须先初始化e和f。
问题1
使用new的方法,写出完整的对A进行初始化、并调用service()服务的步骤。
参考答案:
class A{
public A(){
this.b=new B();
this.c=new C();
}
……
}
Class B{
public B(){
this.d=new D();
}
}
Class C{
public C(){
this.e=new E();
this.f=new F();
}
}
调用代码:
public static void main(String[] args){
A a=new A();
a.service();
}
问题2
由于需求变更,类A被修改为:
Class A {
//B b;
D d;
C c;
public void service(){
Assert d!=null;
Assert c!= null;
....
}
}
请修改问题1中的代码,使A能够正常初始化。
参考答案:
class A{
public A(){
this.d=new D();
this.c=new C();
}
……
}
……
调用代码:
public static void main(String[] args){
A a=new A();
a.service();
}
问题3
由于需求变更,类A的服务service()被拆分成两部分service()和service_2(),并且,系统中先有的调用service()服务的代码,有一些要改用service_2()。类A的定义已修改如下:
Class A {
D d;
C c;
G g;
public void service(){
Assert d!=null;
Assert c!= null;
....
}
public void service_2(){
Assert g!= null;
……
}
}
请修改问题2中的代码,以适应新的需求。
参考答案:
class A{
public A(D _d, C _c){
this.d=_d;
this.c=_c;
}
public A(G _g){
this.g=_g;
}
……
}
……
调用代码:
public static void main(String[] args){
A a=new A(new D(),new C());
a.service();
A a_2=new A(new G());
a.service_2();
}
问题4
公司发现类A可以使用单例实现。请修改上述代码以实现此需求。
参考答案:
class A{
private static A a=new A(new D(),new C());
private static A a_2=new A(new G());
public static A getA(){
return a;
}
public static A getA_2(){
return a_2;
}
private A(D _d, C _c){
this.d=_d;
this.c=_c;
}
private A(G _g){
this.g=_g;
}
……
}
……
调用代码:
public static void main(String[] args){
A a=A.getA();
a.service();
A a_2=A.getA_2();
a.service_2();
}
对象的实例化与耦合
直接使用new关键字,是最基础的实例化对象的方式。
使用这种方式直接进行实例化,最直接的问题是将服务的调用者和服务的实现类耦合在了一起。
如问题1中,main方法中直接new一个A的实例,那么它就和类A耦合起来了。A和B、C,B和D,C和E、F,也是一样。
直接耦合带来的麻烦,在问题3、4中体现得最为明显。
由于需求变更而导致的对类A进行的修改,都引发了对调用代码的修改。
参考答案中只给出了一处调用。但是设想一下,如果A提供的是基础服务,比如数据库操作,或者一些基础的业务逻辑。那么,系统中会有多少对A的引用?一旦数据库从Oracle迁移到MySQL,或者某服务针对江西提供一种逻辑、针对湖南提供另一种逻辑(这种情况在我参与的项目中真实出现过),那么当修改类A时,系统中会有多少处代码要做变更?
希望这部分内容能帮助理解这样直接耦合会带来什么样的问题。
而解决问题的办法,无疑就是解耦合。
问题2和问题4给了我们一种思路。
问题2将类A的创建全部封装在了无参数构造方法A()里面。这样,创建A的过程就和调用服务的过程完全隔离开了:调用A的时候完全不必知道,要创建A需要做些什么工作。可惜这种方式有点脆弱,遇到问题3就不行了,因为A()方法只能定义一个。它无法实现两种创建逻辑。
问题4延续了问题2的思路,把创建A的逻辑封装起来。调用代码中只需要获取实例并调用服务,而不需要考虑A是如何创建的。
这其实就是工厂模式的思路。
问题5
请使用工厂模式,改写问题4中的代码。
参考答案:
Public abstract class Factory {
Public static Factory aFactory(){
Return new FactoryA ();
}
Public static Factory a2Factory(){
Return new FactoryA_2();
}
Create();
}
Public class FactoryA implements Factory {
@Override
Public A Create(){
return new A(new D(),new C());
}
}
Public class FactoryA_2 implements Factory {
@Override
Public A Create(){
return new A_2(new G());
}
}
……
调用代码:
public static void main(String[] args){
A a = Factory.aFactory().Create();
a.service();
A a_2 = Factory.a2Factory().Create();
a_2.service_2();
}
问题6
将service()和service_2()方法提取到接口中,再修改问题5中的代码。
参考答案:略
问题7
用问题6的思路,为其它类(B/C/D/E/F/G)等创建工厂
参考答案:略
问题7的出现带来了一个新的困扰:那么多的类,要写那么多的工厂……尤其是,当我们对现有系统进行改造或者重构时,这种重复工作量会有多少,可想而知。
问题8
为了简化问题7的工作,公司定义了一个通用的工厂类
public Class Factory{
/**
* 根据入参,调用指定类的默认构造方法(无参数构造方法),生成一个指定的类的实例。如果找不到指定的类,或者指定的类没有可用的构造方法,则返回null。
* <p />
* 例如,调用
* String a = Factory.product(“java.lang.String”),将返回一个””。等同于调用了 String a = new String();
*
* @param className 用户指定的类名。必须是全路径名,
* @return 用户指定的类的一个实例,或者是null
*/
public static Object product(String className){
① // 请将这里的代码补充完整
}
}
请将①处的代码补充完整。
这里就需要用到java的反射机制了。
回到SpringIOC
综合上述问题,思考一下:为什么会出现Spring IOC框架?它的基本原理是什么?
最简单的说,IOC出现的原因就是为了解耦合。利用一个通用的工厂,来管理各个实现类的创建过程以及在创建过程中引入的类的依赖关系。 这样,各个类在代码层级上,是可以做到“兵不知将将不知兵”的,从而减少对类进行修改、扩展时的工作量。
它的基本原理,很明了,就是反射+工厂。