Java 依赖注入总结

最近在研读Hive社区版本的源码,发现其中多处用到了Java依赖注入,这里简单总结一下依赖注入的几种实现方法。在谈java依赖注入之前,有必要先回顾一下设计模式中的Proxy模式。

其实每个模式名称就表明了该模式的作用,代理模式就是多一个代理类出来,替原对象进行一些操作,比如我们在租房子的时候回去找中介,为什么呢?因为你对该地区房屋的信息掌握的不够全面,希望找一个更熟悉的人去帮你做,此处的代理就是这个意思。再如我们有的时候打官司,我们需要请律师,因为律师在法律方面有专长,可以替我们进行操作,表达我们的想法。先来看看关系图:

image.png

根据上文的阐述,代理模式就比较容易的理解了,我们看下代码:

1
2
3
public interface Sourceable {
    public void method();
}
1
2
3
4
@Override
    public void method() {
        System.out.println("the original method!");
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 public class Proxy implements Sourceable {
    private Source source;
    public Proxy(){
        super();
        this.source = new Source();
    }
    @Override
    public void method() {
        before();
        source.method();
        atfer();
    }
    private void atfer() {
        System.out.println("after proxy!");
    }
    private void before() {
        System.out.println("before proxy!");
    }
}

测试类

1
2
3
4
5
6
7
8
public class ProxyTest {
    public static void main(String[] args) {
        Sourceable source = new Proxy();
        source.method();
    }
}

输出:

before proxy!
the original method!
after proxy!

代理模式的应用场景:

如果已有的方法在使用的时候需要对原有的方法进行改进,此时有两种办法:

1、修改原有的方法来适应。这样违反了“对扩展开放,对修改关闭”的原则。

2、就是采用一个代理类调用原有的方法,且对产生的结果进行控制。这种方法就是代理模式。

使用代理模式,可以将功能划分的更加清晰,有助于后期维护!

下面我们要说的Java依赖注入可以说就是proxy模式的一种应用,这里总结一下常用的三种依赖注入方法

1. 接口注入

我们常常借助接口来将调用者与实现者分离。如:

1
2
3
4
5
6
7
8
9
public class ClassA {
private InterfaceB clzB;
public init() {
    Ojbect obj =
    Class.forName(Config.BImplementation).newInstance();
    clzB = (InterfaceB)obj;
}
……
}

上面的代码中,ClassA依赖于InterfaceB 的实现,如何获得InterfaceB 实现类的实例?传统的方法是在代码中创建InterfaceB 实现类的实例,并将起赋予clzB。而这样一来,ClassA在编译期即依赖于InterfaceB 的实现。为了将调用者与实现者在编译期分离,于是有了上面的代码,我们根据预先在配置文件中设定的实现类的类名,动态加载实现类,并通过InterfaceB 强制转型后为ClassA所用。
这就是接口注入的一个最原始的雏形。而对于一个接口注入型IOC容器而言,加载接口实现并创建其实例的工作由容器完成,如J2EE开发中常用的Context.lookup(ServletContext.getXXX),都是接口注入型IOC的表现形式。
Apache Avalon是一个典型的Type1型IOC容器。

2. 构造子注入

构造子注入,即通过构造函数完成依赖关系的设定,如:

1
2
3
4
5
6
7
8
9
 public class DIByConstructor {
private final DataSource dataSource;
private final String message;
public DIByConstructor(DataSource ds, String msg) {
    this.dataSource = ds;
    this.message = msg;
}
……
}

可以看到,在Type2类型的依赖注入机制中,依赖关系是通过类构造函数建立,容器通过调用类的构
造方法,将其所需的依赖关系注入其中。
PicoContainer(另一种实现了依赖注入模式的轻量级容器)首先实现了Type2类型的依赖注入模式。

3. 设置注入

这种方式广泛应用在Spring框架的参数配置中,客户代码仅仅面向接口编程,而无需知道实现类的具体名称。同时,我们可以很简单的通过修改配置文件来切换具体的底层实现类。下面我们抛开Spring框架本身,使用一个简单的例子来实现类似Spring的依赖注入
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
public class ActionFactory {
    public static Action getAction(String actionName) {
      Properties pro = new Properties();
      try {
        pro.load(new FileInputStream("config.properties"));
        String actionImplName = (String) pro.get(actionName);
        String actionMessage = (String) pro.get(actionName + "_msg");
        Object obj = Class.forName(actionImplName).newInstance();
        // BeanUtils是Apache Commons BeanUtils提供的辅助类
        BeanUtils.setProperty(obj, "message", actionMessage);
        return (Action) obj;
      } catch (FileNotFoundException e) {
        e.printStackTrace();
      } catch (IOException e) {
        e.printStackTrace();
      } catch (ClassNotFoundException e) {
        e.printStackTrace();
      } catch (InstantiationException e) {
        e.printStackTrace();
      } catch (IllegalAccessException e) {
        e.printStackTrace();
      } catch (InvocationTargetException e) {
        e.printStackTrace();
      }
      return null;
    }
  }

配置文件则采用最简单的properties文件形式:
TheAction=net.xiaxin.spring.qs.UpperAction
TheAction_msg=HeLLo

测试类:

1
2
3
4
public void testFactory(){
    Action action = ActionFactory.getAction("TheAction");
    System.out.println(action.execute("Rod Johnson"));
}

上面的例子中,我们通过设置注入,在运行期动态将字符串“HeLLo” 注入到Action实现类的Message属性中。用同样的方法,我们可以通过修改配置文件让程序调用不同的实现方法,从而实现灵活的依赖注入。在Hive源码中,HiveDriverRunHook类也是使用这种方式实现注入的