Spring 容器中最经典的概念就有 控制反转 IOC
与 依赖注入 DI
,其实这个两个概念呢,又是相辅相成的。太多的道理就不说了,相信大家都懂,下面呢,我们通过注解实现一个低配版的 DI 容器。
源码部分
首先,我们引入两个注解:一个是 @SimpleInject
(普通注入);另外一个是 @SimpleSingleton
(单列注入)。
普通注入
注解 SimpleInject
来表达出依赖关系,类似于 @Autowired
@Retention(RUNTIME)
@Target(FIELD)
public @interface SimpleInject {
}
创建两个服务 ServiceA 与 ServiceB,使用上面的注解,让 ServiceA 依赖 ServiceB
// ServiceA.java
public class ServiceA {
@SimpleInject
ServiceB b;
public void callB(){
b.action();
}
}
// ServiceB.java
public class ServiceB {
public void action(){
System.out.println("I'm B");
}
}
上面就是表达出了 依赖注入DI
这一层的关系,下面我们需要引入一个类的管理容器,让创建对象的工作交给管理容器,实现 控制反转
,容器的源码如下:
// SimpleContainer.java
public class SimpleContainer {
public static <T> T getInstance(Class<T> cls) {
try {
// 这个地方是假定我们写的例子上面都存在有一个 public 的默认构造方法
T obj = cls.newInstance();
Field[] fields = cls.getDeclaredFields();
for (Field f : fields) {
// 判断是否有被 SimpleInject 注解标识
if (f.isAnnotationPresent(SimpleInject.class)) {
if (!f.isAccessible()) {
f.setAccessible(true);
}
Class<?> fieldCls = f.getType();
f.set(obj, getInstance(fieldCls));
}
}
return obj;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
上面 SimpleContainer
大致的工作是:
- 通过默认的构造方法创建对象
- 检查每个字段是否有被
SimpleInject
标识,如果有就根据 该字段类型 获取到 类型的实例,并设置字段的值
测试
// ContainerDemo.java
public class ContainerDemo {
public static void usingSimpleContainer(){
// 通过容器来获取实例对象
ServiceA a = SimpleContainer.getInstance(ServiceA.class);
a.callB();
ServiceB b = SimpleContainer.getInstance(ServiceB.class);
// 判断获取到的容器实例地址是否为同一个
if(b != a.getServiceB()){
System.out.println("SimpleContainer: different instances");
}
}
public static void main(String[] args) {
ContainerDemo.usingSimpleContainer();
}
}
输出结果为
this is the message form service B
SimpleContainer: different instances
单例注入
从上面的代码可以看到,我们每次获取一个类型的对象,都会创建一个新的对象,实际开发中,这可能不是期望的结果,我们期望的可能是单例的。即:每个类型只创建一个对象,该对象被所有访问的代码共享。
这里我们引入新的注解 @SimpleSingleton
,来表达出单例注入
@Retention(RUNTIME)
@Target(TYPE)
public @interface SimpleSingleton {
}
修改 ServiceB.java
让 ServiceB
标识为单例注入
@SimpleSingleton
public class ServiceB {
public void action() {
System.out.println("this is the message form service B");
}
}
同时也需要修改容器类,为了区分,我新建一个容器类出来 SimpleContainer2.java
。
public class SimpleContainer2 {
// 增加一个静态变量,缓存创建过的单例对象
private static Map<Class<?>, Object> instances = new ConcurrentHashMap<>();
private static <T> T createInstance(Class<T> cls) throws Exception {
T obj = cls.newInstance();
Field[] fields = cls.getDeclaredFields();
for (Field f : fields) {
if (f.isAnnotationPresent(SimpleInject.class)) {
if (!f.isAccessible()) {
f.setAccessible(true);
}
Class<?> fieldCls = f.getType();
f.set(obj, getInstance(fieldCls));
}
}
return obj;
}
public static <T> T getInstance(Class<T> cls) {
try {
boolean singleton = cls.isAnnotationPresent(SimpleSingleton.class);
if (!singleton) {
return createInstance(cls);
}
Object obj = instances.get(cls);
if (obj != null) {
return (T) obj;
}
synchronized (cls) {
obj = instances.get(cls);
if (obj == null) {
obj = createInstance(cls);
instances.put(cls, obj);
}
}
return (T) obj;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
上面 getInstance
检查类型实例是否被 SimpleSingleton
标识,如果被标识,就检查缓存是否存在,不存在就调用 createInstance
创建实例,并且缓存下来。如果没有被标识,直接创建对象返回。
测试
public class ContainerDemo {
public static void usingSimpleContainer2(){
ServiceA a = SimpleContainer2.getInstance(ServiceA.class);
a.callB();
ServiceB b = SimpleContainer2.getInstance(ServiceB.class);
if(b == a.getServiceB()){
System.out.println("SimpleContainer2: same instances");
}
}
public static void main(String[] args) {
ContainerDemo.usingSimpleContainer2();
}
}
输出结果
this is the message form service B
SimpleContainer2: same instances
总结
从上面就有可以看到,如果是需要实现依赖注入,那么就不能通过正常的 new 一个新的对象出来,这个时候就需要引入容器来管理类。对于单例模式的注入,就需要引入一个缓存变量,将创建过的类缓存下来,每次创建实例的时候,就去检查是有缓存。