通过注解自己实现 DI容器

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 大致的工作是:

  1. 通过默认的构造方法创建对象
  2. 检查每个字段是否有被 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.javaServiceB 标识为单例注入

@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 一个新的对象出来,这个时候就需要引入容器来管理类。对于单例模式的注入,就需要引入一个缓存变量,将创建过的类缓存下来,每次创建实例的时候,就去检查是有缓存。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spring是一个流行的Java应用程序框架,其中的DI(依赖注入)是一个重要的概念。 在Spring中,DI是一种设计模式,用于将对象之间的依赖关系从代码中移除,从而实现松耦合并提高可重用性和可测试性。在DI中,对象不会自行实例化其依赖项,而是将这些依赖项传递给它们。 Spring框架中的DI实现主要有两种方式:构造函数注入和属性注入。 在构造函数注入中,依赖项通过对象的构造函数传递。这种方式可以确保依赖项在对象被实例化时就已经存在。 在属性注入中,依赖项通过Setter方法或直接设置公共字段注入到对象中。这种方式更加灵活,因为它允许对象在运行时更改其依赖项。 Spring框架还提供了一些依赖项注入的方式,如接口注入和注解注入,这些方式可以让开发人员更加方便地管理依赖项。 总之,Spring中的DI是一种强大的编程模式,可以让开发人员更加灵活地管理对象之间的依赖关系,从而提高代码的可重用性和可测试性。 ### 回答2: Spring中的DI(依赖注入)是通过IOC(控制反转)实现的。在Spring中,使用IOC容器来管理对象的创建和依赖关系的注入。 首先,我们需要在Spring的配置文件中定义要管理的对象。可以使用XML配置文件或Java注解来完成配置。在配置文件中,我们可以指定对象的类名、属性、构造函数等信息。 然后,在程序运行时,Spring的IOC容器会根据配置信息创建对象,并将对象的依赖注入到对象中。依赖注入可以通过构造函数、setter方法或字段注入来完成。 Spring的IOC容器会自动解析对象之间的依赖关系,并保证依赖关系正确地注入到对象中。这样,我们就可以通过IOC容器来管理对象的创建和依赖关系,而不需要手动去创建对象或者处理对象之间的依赖关系。 通过使用IOC容器,我们可以实现松耦合的对象之间的交互。对象之间的依赖关系不再硬编码在代码中,而是由IOC容器来管理。这样,我们可以很方便地修改对象之间的依赖关系,而不需要修改源代码。 总结来说,Spring中的DI通过IOC容器实现,将对象的依赖关系注入到对象中,实现了对象之间的松耦合。这种设计模式可以提高代码的可扩展性和可维护性,使系统更加灵活和易于测试。 ### 回答3: Spring中的依赖注入(Dependency Injection,DI)是指通过容器自动将一个对象所依赖的其他对象注入到该对象中的过程。 在Spring中,DI实现主要依赖于以下两个核心机制: 1. Bean的定义:通过使用XML配置文件或者注解的方式,我们可以将一个对象(即Bean)标识为一个可被Spring管理的对象。在配置文件中,我们需要指定这个对象所依赖的其他对象或者值。这样Spring就知道了哪些对象需要被注入到该Bean中。 2. 容器的实例化和管理:Spring容器负责创建和管理所有的Bean。它会读取配置文件中的Bean定义,并根据定义的信息来创建相应的Bean实例。当容器创建一个Bean时,它会检查该Bean所依赖的其他Bean,并将它们注入到该Bean中。这样就完成了对象之间的依赖关系的建立。 值得注意的是,Spring的依赖注入有多种方式的实现: 1. 构造函数注入:通过构造函数来注入依赖的对象。在Bean的定义中,我们可以指定构造函数的参数,Spring会根据参数的类型和名称来寻找对应的依赖对象进行注入。 2. Setter方法注入:通过Setter方法来注入依赖的对象。在Bean的定义中,我们可以将依赖的对象定义为Bean的属性,并提供相应的Setter方法。Spring会调用这些Setter方法,将依赖的对象注入到Bean中。 3. 接口注入:通过实现特定的接口来注入依赖的对象。在Bean的定义中,我们可以实现一些特定的接口,如ApplicationContextAware、BeanFactoryAware等。Spring容器会检测到这些接口的存在,并通过调用对应的方法将依赖的对象传递到Bean中。 总的来说,Spring的依赖注入是一种通过容器自动管理对象之间的依赖关系的方式。它使得我们能够更加灵活地组织和管理对象,并实现对象之间的解耦。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值