DependencyInjection(DI)DI(DependencyInjection),SpringIoC不是一种技术,而是一种思想。通过这种思路,可以指导我们设计松散耦合的程序代码。SpringIoC思想的作用体现在两个方面,一个是如何将Bean组装到容器中,如何从容器中获取Bean,另一个是如何解决Bean之间的依赖关系。容器管理依赖关系。当一个bean需要依赖另一个bean时,IoC容器如何实现这样的依赖。Spring中解决bean之间依赖关系的方式在Spring的概念中称为依赖注入(DependencyInjection,DI)。一般认为,Spring依赖注入的实现方式有三种:构造函数注入、setter方法注入、注解注入。不过,就我个人而言,我觉得应该分为两种形式——基于XML的注入和基于注解的注入,再细分为以下几种形式:基于XML的注入是我们学习和使用的第一种方式,而且也是大家最熟悉的方式就是简单介绍一下,举例说明。通过构造函数UserService注入公共类UserServiceImpl实现{privateUserDaouserDao;publicUserServiceImpl(UserDaouserDao){this.userDao=userDao;}/**继承自UserService的方法**/}首先定义一个服务层UserServiceImpl,然后在其内部添加对dao层的引用userDao。接下来就是添加一个构造方法publicUserServiceImpl(UserDaouserDao)等待Spring通过这个方法为userDao注入实例。最后在SpringXML配置文件中注入对应的bean实例。通过构造方法的注入,注入类必须有对应的构造方法。如果没有对应的构造方法,就会报错。通过setter方法注入修改UserServiceImpl.java:publicclassUserServiceImplimplementsUserService{privateUserDaouserDao;publicvoidsetUserDao(UserDaouserDao){this.userDao=userDao;}/**继承UserService方法**/}然后修改XML文件内容为:这两种方法的区别在于,首先UserServiceImpl.java不需要添加构造方法,但是必须有是一个无参构造方法(比如publicUserServiceImpl(),示例中没有写,因为java会提供一个默认的No-argument构造方法)供Spring容器注册生成bean(比如userService)。2、在XML文件中,使用构造函数注入时,需要使用标签;当注入setter方法时,使用标签。在XML注入的过程中,除了使用ref=""来引用外,还可以使用value=""来设置具体的值,效果类似于使用注解@Value。基于注解的依赖注入@Autowired源码@Target({ElementType.CONSTRUCTOR,ElementType.METHOD,ElementType.PARAMETER,ElementType.FIELD,ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceAutowired{booleanrequired()defaulttrue;}@Autowired是基于注解的依赖注入的关键点。它的源代码非常简单。request()只有一个参数。该参数的作用是识别被注入的Bean是否必须注入。也就是说,没有找到对应的bean时,如果它的值为true,就会报异常;如果它的值为false,则不会出现异常,但在使用过程中,如果容器没有注入bean,则可能会出现空指针异常。还有一点,源码中@Target中包含的参数正是基于注解依赖注入的注入方式类型。@Target确定@Autowired可以标记在哪些类型上。Inject@Service("userService")publicclassUserServiceImplimplementsUserService{privateUserDaouserDao;@AutowiredpublicUserServiceImpl(UserDaouserDao){this.userDao=userDao;}/**根据UserService继承的方法**/}通过构造函数根据开发文档,只有一种构造方法。从Spring4.3开始,不再需要添加@Autowired注解,也可以。但是,如果有多个构造方法,其中一个方法必须标记@Autowired,否则Spring会报异常。通过setter方法注入@Service("userService")publicclassUserServiceImplimplementsUserService{privateUserDaouserDao;@AutowiredpublicvoidsetUserDao(UserDaouserDao){this.userDao=userDao;}/**继承自UserService的方法**/}Inject@Service("userService")publicclassUserServiceImplimplementsUserService{@AutowiredprivateUserDaouserDao;/**一个继承自UserService的方法**/}通过方法入参来注入以上三种注入方式,都是比较熟悉的,不再赘述。让我们关注参数注入。其实method入参注入方式感觉和构造方法和setter方法注入形式差不多,相当于在入参位置给构造方法和setter方法注解@Autowired。说起来可能有点抽象,直接看例子:@ComponentpublicclassUserDaoImplimplementsUserDao{//简单的返回一个User来模拟数据库查找过程@OverridepublicUsergetUser(Longid,Stringname){Useruser=新用户();用户.setId(id);user.setName(名称);user.setAccount("12345678911");user.setPassword("******");user.setOtherInfo("这是一个测试账户");返回用户;}}//UserServiceclass@Service("userService")publicclassUserServiceImplimplementsUserService{privateUserDaouserDao;publicUserServiceImpl(@AutowiredUserDaouserDao,@AutowiredUseruser){System.out.println("UserServiceImpl:"+user);this.userDao=userDao;}@OverridepublicUsergetUser(Longid,Stringname){returnuserDao.getUser(id,name);}}//简单配置类//作用是标记@Componet(@Service也算在内)注解类生成bean//同时为@Autowired标记的Beans(对象)注入实例@Configuration@ComponentScanpublic类DIConfig{//用于Service类输入参数user的注入@BeanpublicUsergetUser(){Useru=newUser();u.setName("用户注入服务");返回你;}}//测试类//注意:使用JUnit4测试需要使用@Autowired注入时,必须添加//@RunWith注解以Spring(或SpringBootRunner)启动//@ContextConfiguration扫描配置类@RunWith(SpringRunner.class)@ContextConfiguration(classes=DIConfig.class)publicclassDITest{//如果不添加测试类上的两个注解,注入就会失败@AutowiredprivateUserServiceuserService;@TestpublicvoidtestAutowired(){System.out.println(userService.getUser(1L,"name"));}}运行测试方法后得到如下结果:publicUserServiceImpl(@AutowiredUserDaouserDao,@AutowiredUseruser)中的输出:publicvoidtestAutowired()测试方法中的输出:注意publicUserServiceImpl(@AutowiredUserDaouserDao,@AutowiredUserDaouserDao,@AutowiredUserDaoAutowiredUseruser)输入参数:userDao是UserServiceImpl的一个字段,而user不是。也就是说,我们可以在构造方法中添加任意参数,只要是我们需要的即可,不一定要求参数是类中的属性字段。另外需要注意的是,这里所说的方法并不是任意的方法,而是构造方法或者setter方法。这种publicvoidinitService(@AutowiredUserDaouserDao)自定义方法是不能注入的。@Primary和@Qualifier在上面的例子中,我们注入和使用的bean只是容器中只有一个Bean实例的情况。那么当容器中存在多个同类型的bean时,如何处理呢?修改配置类代码如下:@Configuration@ComponentScanpublicclassDIConfig{@BeanpublicUsergetUser(){Useru=newUser();u.setName("这是用户");返回你;}@BeanpublicUsergetUser2(){Useru=newUser();u.setName("这是用户2");返回你;}}修改测试类:@RunWith(SpringRunner.class)@ContextConfiguration(classes=DIConfig.class)publicclassDITest{@AutowiredprivateUseruser;@TestpublicvoidtestAutowiredPriamry(){System.out.println(user);}}不做其他处理时,结果是:因为有两个UserBean(getUser,getUser2,@Bean没有注解默认存在名为BeanName的默认方法,所以Spring无法确定使用哪一个进行注入修改方法:在@Bean中设置name,如@Bean(name="user"),当name可以匹配privateUser用户;时,也可以完成注入。将privateUser用户重写为getUser中的任意一个或者getUser2也可以完成注入,道理同上,Spring会先根据类型匹配,如果匹配不到,再根据名字匹配,如果匹配不上,自然会抛出异常另外,Spring为我们提供了两个注解来消除依赖注入时的歧义。@Primary@Target({ElementType.TYPE,//class,interface,enumerationtypeElementType.METHOD})//method@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfacePrimary{}@Primary是设置相同类型的bean优先级注解,即一旦某个类型添加了@Priamry,当注入时没有明确指定Bean时,就会注入@Priamry标识的Bean。@Configuration@ComponentScanpublicclassDIConfig{@Primary@BeanpublicUsergetUser(){Useru=newUser();u.setName("这是用户");返回你;}@BeanpublicUsergetUser2(){Useru=newUser();u.setName("这是用户2");返回你;}}如上,如果给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"";}@限定符可以出现在@Autowired可以出现的任何地方,并与它一起使用。比如下面这样:@RunWith(SpringRunner.class)@ContextConfiguration(classes=DIConfig.class)publicclassDITest{//直接指定使用getUser2进行注入@Autowired@Qualifier("getUser2")privateUseruser;@TestpublicvoidtestAutowiredPriamry(){System.out.println(user);}}两种注解都可以消除歧义。建议结合使用@Bean(name="xxx")和@Qualifier(value="xxx")`。但是如果开发环境没有歧义,自然就没有必要用这些了。当然,以上只是@Autowired的一些常见介绍。如果想了解更多,可以查看基于注解的容器配置。这个参考文档有更详细和丰富的介绍。总结总的来说,Spring是如何实现IoC的呢?首先,Spring提供了一个IoC容器来获取和管理bean。然后提供了一套依赖注入机制,帮助IoC容器更好的管理各个Bean之间的依赖关系,从而更好的实现IoC的思想。一个Bean不可能独立于其他Bean的依赖而存在。当一个Bean需要引入其他Bean进行初始化时,就需要依赖注入的机制。比如有一个类A要调用B接口的方法或者需要B接口的实例。传统的程序流程是用一个C类实现B接口,然后A类创建一个C类的实例来调用它的方法。在Spring的依赖注入过程中,就变成了A类只需要在自身内部添加一个注入接口(广义上的,不是interface接口)即可。这个接口可以是构造方法、setter方法或者其他形式;同时添加对B接口的引用(privateBb;)。当确实需要生成类A的实例时,SpringIoC容器根据类A提供的接口注入相应的Bean,这个bean可以是类C(类C实现了B{})或者类D(类D实现B{})等等;具体谁是根据Bean组装策略和IoC容器中的Bean来决定的,不再由开发者管理。最后,最近有很多朋友找我要一份Linux学习路线图,所以我结合自己的经验,利用业余时间熬夜一个月,整理了一本电子书。无论你是面试还是自我提升,相信都会对你有所帮助!免费送给大家,只求大家给我点个赞!电子书|LinuxDevelopmentLearningRoadmap也希望有小伙伴可以和我一起把这本电子书做得更完美!获得?希望老铁们来个三连击,让更多人看到这篇文章。推荐阅读:干货|程序员和高级架构师免费发送工件的必备资源|支持搜索的资源网站