江苏新闻:Spring方式注入的使用与实现原理

admin 6个月前 (05-14) 科技 57 0

一、前言

  这几天为了更详细地领会Spring,我最先阅读Spring官方文档。说实话,之前很少阅读官方文档,就算是读,也是读别人翻译好的。然则最近由于准备春招,需要领会许多知识点的细节,网上险些搜索不到,只能硬着头皮去读官方文档。虽然我读的这个Spring文档也是中文版的,然则很明显是机翻,十分不通顺,只能对着英文版本,双方对照着看,这个历程很慢,也很吃力。然则这应该是一个程序员必须要履历的历程吧。

  在读文档的时刻,我读到了一个叫做方式注入的内容,这是我之前学习Spring所没有领会过的。以是,这篇博客就参照文档中的形貌,来讲一讲这个方式注入是什么,在什么情形下使用,以及简朴谈一谈它的实现原理。


二、正文

2.1 问题剖析

  在说方式注入之前,我们先来思量一种现实情形,通过现实案例,来引出我们为什么需要方式注入。在我们的Spring程序中,可以将bean的依赖关系简朴分为四种:

  1. 单例bean依赖单例bean
  2. 多例bean依赖多例bean
  3. 多例bean依赖单例bean
  4. 单例bean依赖多例bean

  前三种依赖关系都很好解决,Spring容器会帮我们正确地处置,唯独第四种——单例bean依赖多例beanSpring容器无法帮我们获得想要的效果。为什么这么说呢?我们可以通过Spring容器事情的方式来剖析。

  我们知道,Springbean的作用域默认是单例的,每一个Spring容器,只会建立这个类型的一个实例工具,并缓存在容器中,以是对这个bean的请求,拿到的都是同一个bean实例。而对于每一个bean来说,容器只会为它举行一次依赖注入,那就是在建立这个bean,为它初始化的时刻。于是我们可以最先思量上面说的第四种依赖情形了。假设一个单例bean A,它依赖于多例bean BSpring容器在建立A的时刻,发现它依赖于B,且B是多例的,于是容器会建立一个新的B,然后将它注入到A中。A建立完成后,由于它是单例的,以是会被缓存在容器中。之后,所有接见A的代码,拿到的都是同一个A工具。而且,由于容器只会为bean执行一次依赖注入,以是我们通过A接见到的B,永远都是同一个,只管B被设置为了多例,然则并没有用。为什么会这样?由于多例的寄义是,我们每次向Spring容器请求多例bean,都市建立一个新的工具返回。而B虽然是多例,然则我们是通过A接见B,并不是通过容器接见,以是拿到的永远是同一个B。这时刻,单例bean依赖多例bean就失败了。

  那要若何解决这个问题呢?解决方案应该不难想到。我们可以放弃让Spring容器为我们注入B,而是编写一个方式,这个方式直接向Spring容器请求B;然后在A中,每次想要获取B时,就挪用这个方式获取,这样每次获取到的B就是不一样的了。而且我们这里可以借助ApplicationContextAware接口,将context工具(也就是容器)存储在A中,这样就可以方便地挪用getBean获取B了。好比,A的代码可以是这样:

class A implements ApplicationContextAware {
    // 纪录容器的引用
    private ApplicationContext context;
    // A依赖的多例工具B
    private B b;

    /**
     * 这是一个回调方式,会在bean建立时被挪用
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        this.context = applicationContext;
    }

    public B getB() {
        // 每次获取B时,都向容器申请一个新的B
        b = context.getBean(B.class);
        return b;
    }
}

  然则,上面的做法真的好吗?谜底显然是欠好。Spring的一个很大的优点就是,它侵入性很低,我们在自己编写的代码中,险些看不到Spring的组件,一样平常只会有一些注解。然则上面的代码中,却直接耦合了Spring容器,将容器存储在类中,并显式地挪用了容器的方式,这不仅增添了Spring的侵入性,也让我们的代码变得不那么容易治理,也变得不再优雅。而Spring提供方式注入机制,就是用了实现和上面类似的功效,然则加倍地优雅,侵入性更低。下面我们就来看一看。


2.2 方式注入的功效

  什么是方式注入?实在方式注入和AOP异常类似,AOP用来对我们界说的方式举行增强,而方式注入,则是用来笼罩我们界说的方式。通过Spring提供的方式注入机制,我们可以对类中界说的方式举行替换,好比说上面的getB方式,正常情形下,它的实现应该是这样的:

public B getB() {
    return b;
}

  然则,为了实现每次获取B时,能够让Spring容器建立一个新的B,我们在上面的代码中将它修改成了下面这个样子:

public B getB() {
    // 每次获取B时,都向容器申请一个新的B
    b = context.getBean(B.class);
    return b;
}

  然则,我们之前也说过,这种方式并欠好,由于这直接依赖于Spring容器,增添了耦合性。而方式注入可以辅助我们解决这一点。方式注入能帮我们完成上面的替换,而且这种替换是隐式地,由Spring容器自动帮我们替换。我们并不需要修改编写代码的方式,仍然可以将getB方式写成第一种形式,而Spring容器会自动帮我们替换成第二种形式。这样就可以在不增添耦合的情形下,实现我们的目的。


2.3 方式注入的实现原理

  那方式注入的实现原理是什么呢?我之前说过,方式注入和AOP类似,不仅仅是功效类似,现实上它们的实现方式也是一样的。方式注入的实现原理,就是通过CGLib的动态署理。关于AOP的实现原理,可以参考我的这篇博客:浅析Spring中AOP的实现原理——动态署理。

  若是我们为一个类的方式,设置了方式注入,那么在Spring容器建立这个类的工具时,现实上建立的是一个署理工具。Spring会使用CGLib操作这个类的字节码,天生类的一个子类,然后笼罩需要修改的谁人方式,而在建立工具时,建立的就是这个子类(署理类)的工具。而详细笼罩成什么样子,取决于我们的设置。好比说Spring提供了一个详细的方式注入机制——查找方式注入,这种方式注入,可以将方式替换为一个查找方式,它的功效就是去Spring容器中获取一个特定的Bean,而获取哪一个bean,取决于方式的返回值以及我们指定的bean名称。

  好比说,上面的getB方式,若是我们对它使用了查找方式注入,那么Spring容器会使用CGLib天生A类的一个子类(署理类),笼罩A类的getB方式,由于getB方式的返回值是B类型,于是这个方式的功效就变成了去Spring容器中获取一个B,固然,我们也可以通过bean的名称,指定这个方式查找的bean。下面我就通过现实代码,来演示查找方式注入。


2.4 查找方式注入的使用

(一)通过xml设置

  为了演示查找方式注入,我们需要几个详细的类,假设我们有两个类UserCar,而User依赖于Car,它们的界说如下:

public class User {

    private String name;
    private int age;
    // 依赖于car
    private Car car;

    // 为这个方式举行注入
   	public Car getCar() {
        return car;
    }
    
	// 省略其他setter和getter,以及toString方式
}

public class Car {
    private int speed;
    private double price;

    // 省略setter和getter,以及toString方式
}

  好,现在有了这两个类,我们可以最先举行方式注入了。我们模拟之前说过的依赖关系——单例bean依赖于多例bean,将User设置为单例,而将User依赖的Car设置为多例。则设置文件如下:

<!-- 将user的作用域界说为singleton -->
<bean id="user" class="cn.tewuyiang.pojo.User" scope="singleton">
    <property name="name" value="aaa" />
    <property name="age" value="28" />
    <!--
        设置查找方式注入,替换getCar方式,让他成为从spring容器中查找car的一个工厂方式
        name指定了需要举行方式注入的方式,而bean则指定了这个方式被笼罩后,是用来查找哪个bean的
    -->
    <lookup-method name="getCar" bean="car" />
</bean>

<!-- 将car的作用域界说为prototype -->
<bean id="car" class="cn.tewuyiang.pojo.Car" scope="prototype">
    <property name="price" value="9999.35" />
    <property name="speed" value="100" />
</bean>

  好,到此为止,我们就设置完成了,下面就该测试一下通过usergetCar方式拿到的多个car,是不是不相同。若是方式注入没有生效,那么按理来讲,我们挪用getCar方式返回的应该是null,由于我们并没有设置将car的值注入user中。然则若是方式注入生效,那么我们通过getCar,就可以拿到car工具,由于它将去Spring容器中获取,而且每次获取到的都不是同一个。测试方式如下:

@Test
public void testXML() throws InterruptedException {
    // 建立Spring容器
    ClassPathXmlApplicationContext context =
        new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    // 获取User工具
    User user = context.getBean(User.class);
    // 多次挪用getCar方式,获取多个car
    Car c1 = user.getCar();
    Car c2 = user.getCar();
    Car c3 = user.getCar();
    // 划分输出car的hash值,看是否相等,以此判断是否是同一个工具
    System.out.println(c1.hashCode());
    System.out.println(c2.hashCode());
    System.out.println(c3.hashCode());
    // 输出user这个bean所属类型的父类
    System.out.println(user.getClass().getSuperclass());
}

  上面的测试逻辑应该很好明白,除了最后一句,为什么需要输出user这个bean所属类型的父类。由于我前面说过,方式注入通过CGLib动态署理实现,而CGLib动态署理的原理就是天生类的一个子类。我们为User类使用了方式注入,以是我们拿到的user这个bean,应该是一个署理bean,而且它的类型是User的子类。以是我们输出这个bean的父类,来判断是否和我们之前说的一样。输出效果如下:

1392906938
708890004
255944888
class cn.tewuyiang.pojo.User	// 父类果真是User

  可以看到,我们果真能够通过getCar方式,获取到bean,而且每一次获取到的都不是同一个,由于hashcode不相等。同时,user这个bean的父类型果真是User,说明user这个bean确实是CGLib天生的一个署理bean。到此,也就证明了我们之前的叙述。


(二)通过注解设置

  上面通过xml的设置方式,大致领会了查找方式注入的使用,下面我们再来看看使用注解,若何实现。实在使用注解的方式加倍简朴,我们只需要在方式上使用@Lookup注解即可,UserCar的设置如下:

@Component
public class User {
    private String name;
    private int age;
    private Car car;

    // 使用Lookup注解,告诉Spring这个方式需要使用查找方式注入
    // 这里直接使用@Lookup,则Spring将会依据方式返回值
    // 将它笼罩为一个在Spring容器中获取Car这个类型的bean的方式
    // 然则也可以指定需要获取的bean的名字,如:@Lookup("car")
    // 此时,名字为car的bean,类型必须与方式的返回值类型一致
    @Lookup
    public Car getCar() {
        return car;
    }
    
    // 省略其他setter和getter,以及toString方式
    
}

@Component
@Scope("prototype")	// 声明为多例
public class Car {
    private int speed;
    private double price;

    // 省略setter和getter,以及toString方式
}

  可以看到,通过注解设置方式注入要简朴的多,只需要通过一个@Lookup注解即可实现。测试方式与之前类似,效果也一样,我就不贴出来了。


(三)为抽象方式使用方式注入

  现实上,方式注入还可以应用于抽象方式。既然方式注入的目的是替换原来的方式,那么原来的方式是否有实现,也就不重要了。以是方式注入也能用在抽象方式上面。然则有人可能会想一个问题:抽象方式只能在抽象类中,那这个类被界说为抽象类了,Spring容器若作甚它建立工具呢?我们之前说过,使用了方式注入的类,Spring会使用CGLib天生它的一个署理类(子类),Spring建立的是这个署理类的工具,而不会去建立源类的工具,以是它是不是抽象的并不影响事情。若是设置了方式注入的类是一个抽象类,则方式注入机制的实现,就是去实现它的抽象方式。我们将User类改为抽象,如下所示:

// 就算为抽象类使用了@Component,Spring容器在建立bean时也会跳过它
@Component
public abstract class User {
    private String name;
    private int age;
    private Car car;

    // 将getCar声明为抽象方式,它将会被署理类实现
    @Lookup
    public abstract Car getCar();
    
    // 省略其他setter和getter,以及toString方式
    
}

  以上方式,方式注入仍然可以事情。


(四)final方式和private方式无法使用方式注入

  CGLib实现动态署理的方式是建立一个子类,然后重写父类的方式,从而实现署理。然则我们知道,final方式和private方式是无法被子类重写的。这也就意味着,若是我们为一个final方式或者一个private方式设置了方式注入,那天生的署理工具中,这个方式照样原来谁人,并没有被重写,好比像下面这样:

@Component
public class User {
    private String name;
    private int age;
    private Car car;
    
    // 方式声明为final,无法被笼罩,署理类中的getCar照样和下面一样
    @Lookup
    public final Car getCar() {
        return car;
    }
    
    // 省略其他setter和getter,以及toString方式
    
}

  我们依旧使用下面的测试方式,然则,在挪用c1.hashCode方式时,抛出了空指针异常。说明getCar方式并没有被笼罩,照样直接返回了car这个成员变量。然则由于我们并没有为user注入car,以是car == null

@Test
public void testConfIG() throws InterruptedException {
    AnnotationConfigApplicationContext context =
        new AnnotationConfigApplicationContext(AutoConfig.class);

    User user = context.getBean(User.class);
    Car c1 = user.getCar();
    Car c2 = user.getCar();
    Car c3 = user.getCar();
    // 运行到这里,抛出空指针异常
    System.out.println(c1.hashCode());
    System.out.println(c2.hashCode());
    System.out.println(c3.hashCode());
    user.sPCar();
    user.spCar();
    user.spCar();
    System.out.println(user.getClass().getSuperclass());
}


三、总结

  以上大致先容了一下方式注入的作用,实现原理,以及重点先容了一下查找方式注入的使用。查找方式注入可以将我们的一个方式,笼罩成为一个去Spring容器中查找特定bean的方式,从而解决单例bean无法依赖多例bean的问题。实在,方式注入能够注入任何方式,而不仅仅是查找方式,然则由于任何方式注入使用的不多,以是这篇博客就不提了,感兴趣的可以自己去Spring文档中领会。最后,若以上形貌存在错误或不足,迎接指正,共同进步。


四、参考

  • Spring-4.3.21官方文档——方式注入
,

诚信在线5858会员查账

诚信在线5858会员查账(原诚信在线官网)现已开放阳光在线手机版、阳光在线电脑客户端下载。阳光在线娱乐戏公平、公开、公正,用实力赢取信誉。

AllBetGaming声明:该文看法仅代表作者自己,与本平台无关。转载请注明:江苏新闻:Spring方式注入的使用与实现原理

网友评论

  • (*)

最新评论

站点信息

  • 文章总数:658
  • 页面总数:0
  • 分类总数:8
  • 标签总数:1144
  • 评论总数:222
  • 浏览总数:10179