当前位置: 首页 > 科技观察

SpringIoC是如何进行依赖注入的

时间:2023-03-12 18:17:03 科技观察

一、依赖注入(DI)DI(DependencyInjection),SpringIoC不是一种技术,而是一种思想。通过这种思路,可以指导我们设计松散耦合的代码。SpringIoC思想的作用体现在两个方面,一个是如何将Bean组装到容器中,如何从容器中获取Bean,另一个是如何解决Bean之间的依赖关系。容器管理依赖关系。当一个bean需要依赖另一个bean时,IoC容器如何实现这样的依赖。Spring中解决bean之间依赖关系的方式在Spring的概念中称为依赖注入(DependencyInjection,DI)。一般认为,Spring依赖注入的实现方式有三种:构造函数注入、setter方法注入、注解注入。不过,就我个人而言,我觉得应该分为两种形式——基于XML的注入和基于注解的注入,再细分为以下几种形式:基于XML的注入是我们学习和使用的第一种方式,而且也是大家最熟悉的方式就是简单介绍一下,举例说明。1.通过构造方法注入publicclassUserServiceImplimplementsUserService{privateUserDaouserDao;publicUserServiceImpl(UserDaouserDao){this.userDao=userDao;}/**方法继承自UserService**/}首先定义一个服务层UserServiceImpl,然后在里面添加dao层参考用户道。接下来就是添加一个构造方法publicUserServiceImpl(UserDaouserDao)等待Spring通过这个方法为userDao注入实例。最后在SpringXML配置文件中注入对应的bean实例。通过构造方法的注入,注入类必须有对应的构造方法。如果没有对应的构造方法,就会报错。2、通过setter方法注入修改UserServiceImpl.java为:publicclassUserServiceImplimplementsUserService{privateUserDaouserDao;publicvoidsetUserDao(UserDaouserDao){this.userDao=userDao;}/**MethodinheritedfromUserService**/}然后修改XML文件内容为:之间的区别这两个方法首先,UserServiceImpl.java不需要添加构造方法,但是必须要有一个无参的构造方法(比如publicUserServiceImpl(),示例中没有写,因为java会提供一个无参的构造方法)参数构造方法默认)为Spring容器注册生成Bean(如userService)。2、在XML文件中,使用构造函数注入时,需要用到这对标签;并且在注入setter方法时,使用标签。在XML注入的过程中,除了使用ref=""来引用外,还可以使用value=""来设置具体的值,效果类似于使用注解@Value。2.基于注解的依赖注入3.自动装配源代码:@Target({ElementType.CONSTRUCTOR,ElementType.METHOD,ElementType.PARAMETER,ElementType.FIELD,ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceAutowired{booleanrequired()defaulttrue;}@Autowired是基于注解的依赖注入的关键点。它的源代码非常简单。request()只有一个参数。该参数的作用是识别被注入的Bean是否必须被注入,即在Spring容器中没有找到对应的bean时,如果其值为true,则报异常;如果它的值为false,则不会出现异常,但是在使用过程中,如果容器没有注入bean,那么可能会出现空指针异常。还有一点,源码中@Target中包含的参数正是基于注解依赖注入的注入方式类型。@Target确定@Autowired可以标记在哪些类型上。1.通过构造方法注入:@Service("userService")publicclassUserServiceImplimplementsUserService{privateUserDaouserDao;@AutowiredpublicUserServiceImpl(UserDaouserDao){this.userDao=userDao;}/**继承自UserService的方法**/}根据开发文档,这种在只有一种构造方法的情况下,从Spring4.3开始,不再需要添加@Autowired注解,也是可以的。但是,如果有多个构造方法,需要在其中一个方法上标记@Autowired,否则Spring会报异常。2.通过setter方法注入@Service("userService")publicclassUserServiceImplimplementsUserService{privateUserDaouserDao;@AutowiredpublicvoidsetUserDao(UserDaouserDao){this.userDao=userDao;}/**方法继承自UserService**/}3.注入@Service("userService")publicclassUserServiceImplimplementsUserService{@AutowiredprivateUserDaouserDao;/**继承自UserService的方法**/}4.通过方法入参注入以上三种注入方式,大家比较熟悉,不再赘述。让我们关注参数注入。其实method入参注入方式感觉和构造方法和setter方法注入形式差不多,相当于在入参位置给构造方法和setter方法注解@Autowired。可能有点抽象,直接看例子:@ComponentpublicclassUserDaoImplimplementsUserDao{//简单返回一个User,模拟数据库查找过程@OverridepublicUsergetUser(Longid,Stringname){Useruser=newUser();user.setId(id);user.setName(name);user.setAccount("12345678911");user.setPassword("******");user.setOtherInfo("thisisatestaccount");returnuser;}}//UserService类@Service("userService")publicclassUserServiceImplimplementsUserService{privateUserDaouserDao;publicUserServiceImpl(@AutowiredUserDaouserDao,@AutowiredUseruser){System.out.println("UserServiceImpl:"+user);this.userDao=userDao;}@OverridepublicUsergetUser(Longid,Stringname){returnuserDao.getUser(id),姓名);}}//简单配置类//作用是为标有@Componet(@Service也算)注解的类生成bean//同时为标有@Autowired@Configuration@ComponentScanpublicclassDIConfig的bean(对象)注入实例{//用于将用户注入服务类@BeanpublicUsergetUser(){Useru=newUser();u.setName("userinjectintoservice");returnu;}}//测试类//注意:使用JUnit4测试时,如果需要使用@Autowired注入必须加上//@RunWith注解使用Spring启动(或SpringBootRunner)//@ContextConfiguration扫描配置类@RunWith(SpringRunner.class)@ContextConfiguration(classes=DIConfig.class)publicclassDITest{//如果不在测试类上添加两个注解,它将被注入Fail@AutowiredprivateUserServiceuserService;@TestpublicvoidtestAutowired(){System.out.println(userService.getUser(1L,"name"));}}运行测试方法后,我得到以下结果:publicUserServiceImpl(@AutowiredUserDaouserDao,@AutowiredUseruser)在输出中:publicvoidtestAutowired()测试方法中输出:注意publicUserServiceImpl(@AutowiredUserDaouserDao,@AutowiredUseruser)的入参:userDao是UserServiceImpl的一个字段,但是用户不是。我们可以在构造方法中添加任意参数,只要是我们需要的即可,不一定要求参数是类中的属性字段。另外需要注意的是,这里所说的方法并不是任意的方法,而是构造方法或者setter方法。这种publicvoidinitService(@AutowiredUserDaouserDao)自定义方法是不能注入的。4、@Primary和@Qualifier在上面的例子中,我们注入和使用的bean只是容器中只有一个Bean实例的情况。那么如何处理容器中多个同类型的bean呢?修改配置类代码如下:@Configuration@ComponentScanpublicclassDIConfig{@BeanpublicUsergetUser(){Useru=newUser();u.setName("thisisuser");returnu;}@BeanpublicUsergetUser2(){Useru=newUser();u.setName("thisisuser2");returnu;}}修改测试类:@RunWith(SpringRunner.class)@ContextConfiguration(classes=DIConfig.class)publicclassDITest{@AutowiredprivateUseruser;@TestpublicvoidtestAutowiredPriamry(){System.out.println(user);}}不做其他处理时,结果为:因为有两个UserBeans(getUser,getUser2,如果不指定@Bean,默认方法名是BeanName),因此Spring无法确定使用哪一个进行注入。修改方法:在@Bean中设置name,如@Bean(name="user"),当name可以匹配privateUseruser;时,也可以完成注入。将privateUser用户重写为getUser或者getUser2中的任意一个也可以完成注入。道理同上,Spring会先根据类型进行匹配,如果匹配不到,再根据名称进行匹配,如果匹配不上,自然会抛出异常。另外,Spring为我们提供了两个注解来消除依赖注入时的歧义。@Primary@Target({ElementType.TYPE,//类,接口,枚举类型ElementType.METHOD})//方法@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfacePrimary{}@Primary是一组同类型的Beanpriority级别的注解,也就是说一旦某个类型添加了@Priamry,当注入时没有明确指定Bean时,就会注入@Priamry标识的Bean。@Configuration@ComponentScanpublicclassDIConfig{@Primary@BeanpublicUsergetUser(){Useru=newUser();u.setName("thisisuser");returnu;}@BeanpublicUsergetUser2(){Useru=newUser();u.setName("thisisuser2");return;}}比如如上,给getUser()加上相应的注解,测试方法也可以正常运行。但是这个方法的问题是@Priamry可以在很多类中使用。如果多个相同类型的bean被@Primary标记,那么@Priamry就会失去作用。@Qualifier所以Spring提供了@Qualifier注解,直接标注在@Autowired注入的Bean上,明确指定要注入一个Bean。@Target({ElementType.FIELD,ElementType.METHOD,ElementType.PARAMETER,ElementType.TYPE,ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic@interfaceQualifier{Stringvalue()default";}@Qualifier可以只要@Autowired可以出现,就和它一起使用。比如下面这样:@RunWith(SpringRunner.class)@ContextConfiguration(classes=DIConfig.class)publicclassDITest{//直接指定使用getUser2进行注入@Autowired@Qualifier("getUser2")privateUseruser;@TestpublicvoidtestAutowiredPriamry(){系统输出。println(user);}}这两个注解可以消除歧义,建议结合使用@Bean(name="xxx")和@Qualifier(value="xxx")`。但是如果开发环境没有歧义,自然就没有必要用这些了。当然,以上只是@Autowired的一些常见介绍。如果想了解更多,可以查看基于注解的容器配置。这个参考文档有更详细和丰富的介绍。总结总的来说,Spring是如何实现IoC的呢?首先,Spring提供了一个获取和管理bean的IoC容器。然后提供了一套依赖注入机制,帮助IoC容器更好的管理各个Bean之间的依赖关系,从而更好的实现IoC的思想。一个Bean不可能独立于其他Bean的依赖而存在。当一个Bean需要引入其他Bean进行初始化时,就需要依赖注入的机制。比如有一个类A要调用B接口的方法或者需要B接口的实例。传统的程序流程是用一个C类实现B接口,然后A类创建一个C类的实例来调用它的方法。在Spring的依赖注入过程中,变成A类只需要在自身内部添加一个注入接口(广义上的,不是接口接口)即可。该接口可以是构造方法、setter方法或其他形式;同时添加对B接口的引用(privateBb;)。当确实需要生成类A的实例时,SpringIoC容器根据类A提供的接口注入相应的Bean,这个bean可以是类C(类C实现了B{}),也可以是类D(D类实现B{})等等;具体谁根据Bean的组装策略和IoC容器中的Bean来决定,不再由开发者管理。本文经授权转载自公众号“良墟Linux”。世界500强外企Linux开发工程师梁旭,在公众号分享大量Linux干货,欢迎关注!