3.spring依赖注入(DI)

IOC与DI区别

同一件事,站在不同角度上看待问题的不同描述

IOC:Spring立场,如何创建管理这些Bean

DI:应用程序立场,如何获取使用这些Bean

什么是依赖?即java对象的引用
什么是注入?spring容器将Bean资源注入到应用程序中(个人理解)

1.依赖注入
    通过依赖注入,对象的依赖关系将由系统中负责协调各个对象的第三方组件(spring的容器)在创建对象的时候进行设定,无需应用程序自行创建对象和管理它们的依赖关系。或者说应用程序依赖Spring为其提供运行时所需的资源(Bean)
    通俗点的说,依赖的注入就是Bean的声明与装配。或者说即依赖类不由程序员实例化,而是应用程序声明需要哪些Bean,spring容器会帮我们实例化对象并且将实例注入到需要该资源的类中(饭来张嘴)。

所以我的理解依赖注入应分为两步
声明➕装配(注入)

  • 声明:即应用程序告诉spring,哪些Bean对象要交由spring管理,在创建容器都时候就会创建大部分Bean资源。简单说,启动服务,应用程序会告诉spring你需要准备实例化哪些资源(java对象)
  • 装配:即应用程序告诉spring,需要哪些资源(Bean)装配,在初始化容器的时候容器协调管理这些依赖关系。简单说,启动服务,应用程序告诉spring你需要把哪些资源送到我碗里来,赶紧送过来。

所以说,这两步又像是一步,即上面的官话,创建并协调依赖关系。

优点
    依赖注入带来的最大的收益就是松耦合,重点松耦合。

依赖注入的方式:

    1⃣️构造器注入 

    2⃣️setter注入

    3⃣️通过filed变量来注入

Spring容器负责创建应用程序声明的bean并通过DI来协调这些对象之间的关系。但是,作为开发人员,你需要告诉Spring要创建哪些bean并且如何将其装配在一起。但描述如何进行装配时,spring提供了三种主要的装配机制。

Bean装配机制(依赖注入的本质)

Spring的官话,创建应用对象之间协作关系的行为称为装配。
即协调引用之间的行为称为装配。这也是依赖注入的(DI)的本质。
也就是说当一个对象A的成员属性是另一个对象B,实例化对象A的的时候,需要实例化其成员属性对象B。
或者说来指定依赖之间的关系。大白话,把B装配到A,比如组装汽车对象的时候我们要同时把*这个对象装配进去,这样车才能跑起来。

    1⃣️在XML中显示配置、

    2⃣️在Java中进行显示配置(javaconfig)、

    3⃣️隐式的Bean发现机制和自动装配(注解)

下面重点讲解自动化装配和在java中进行显示的配置,在xml中显示配置过于麻烦,大多数开发已不用,有需要自己查资料。不过建议多去了解xml配置,它有利于系统性的了解spring,初学者尤其建议。

隐式的Bean发现机制和自动装配--简称自动化配置(重点)

如果一个对象只通过接口来表明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行切换。但是这样会存在一个问题,在传统的依赖注入配置中,我们必须要明确要给属性装配哪一个bean的引用,一旦bean很多,用xml配置就不好维护了。基于这样的场景,spring使用注解来进行自动装配解决这个问题。自动装配就是开发人员不必知道具体要装配哪个bean的引用,这个识别的工作会由spring来完成。与自动装配配合的还有“自动检测”,这 个动作会自动识别哪些类需要被配置成bean,进而来进行装配。这样我们就明白了,自动装配是为了将依赖注入“自动化”的一个简化配置的操作。

通过前面DI分析,依赖注入分两步,声明+装配

声明,即告诉spring容器哪些Bean将由其管理

装配,即告诉spring容器哪些Bean需要装配进本Bean

1.声明=组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean.

@Componet、@Service 、@Repository 注解(在类上使用),组件扫描注解,可以表明该类会作为组件类,并告知Spring要为这个类创建bean,没有必要显示配置该bean。
 这三个注解@Service多用于service层,@Repository多用于dao层。@Componet是Spring的备用。但是这三者其实是一样的,可以互相替换。

每个组建都用自己的Id,spring默认的是类名首字母小写。可以使用@Component("xxxxx")重命名。
还有另外一种bean的命名方式,使用java依赖植入规范中所提供的@Named注解来为bean设置ID.@Named("xxxxxxx"),其实spring支持@Named作为@Compoent的替代方案,两者之间有一些细微的差异。

package soundsystem;

import org.springframework.stereotype.Compoenet;

@Component
public class SgtPeppers implements CompactDisc{

private String title ="Sgt. pepper‘s Lonely Hearts Club Band":
private String artist ="The Beatles";

public void play(){
System.out.pringln("Playing" + title + "by"+artist);

}

既然组件注解是告诉spring该Bean要交给spring管理。在我们主动亮牌子后,spring会有一个相应的发现机制,即组件扫描器。
但是组件扫描不是默认启用的,还需要显示配置一下Spring,既开启包扫描,从而命令它去寻找带有@Compoent注解类,并为其创建bean。不开启组件扫描是不会创建bean的。
在spring boot项目,组件扫描器默认扫描启动类所在的包下面,其实包扫描注解已经由@SpringBootApplication所配置。也可以使用xml(还是上面说的,xml可以直观让我们了解),也可以使用javaconfig,下面介绍就用javaconfig。

@CompentScan 从定义的扫描路径中找出标识了需要装配的类自动装配到spring的bean容器中。如@Controller @Service @Respository @Comment
如果不配置包路径@ComponentScan默认会扫描与配置类相同的包。如果更倾向于xml配置,可以在xml文件使用Srping context命名空间的<context:component-scan>元素开启注解扫描。

package soundsystem;//配置类,启动包扫描

@Configuration //配置类注解
@ComponentScan//启动包扫描,扫描@component
public class CDPlayerConfig{

}

指定路径,及扫描多个基础包

//在@ComponentScan的value属性中指明包的名称

@ComponentScan("com.guxilong")/@ComponentScan(basePackages="com.guxilong")

@ComponentScan(basePackages={"com.guxilong","com.guxilong2"})

上面的方法不安全,如果你重构代码的话,那么所指定的基础包可能就会出现错误。除了将基础包设置类String类型外,@ComponentScan还提供了另外一种方法。那就是将其设置为包中所包含的类或接口

@ComponentScan(basePackgeClasses={CDPlayer.class,DVDPlayer.class})

数组中包含的类所在的包将会作为组件扫描的基础包。样例中使用了组件类,但是你可以考虑创建一个用来进行扫描的空标记接口。通过空标记接口的方式,你依然能够保持对重构友好的引用,但是可以避免了引用任何实际的应用程序代码。

@CompentScans

存在多个@ComponentScan时,可以使用@ComponentScans将这些@ComponentScan放在里面统一管理,@ComponentScans是一个数组。


@ComponentScan(basePackages = {"com.java.dao"})
@ComponentScan(basePackages = {"com.java.service"})
public class BeanConfig {
...
}
//等价
@Configuration
@ComponentScans({  
       @ComponentScan(basePackages = {"com.java.dao"}),
       @ComponentScan(basePackages = {"com.java.service"})
})
public class BeanConfig {
...
}

2.装配=自动装配(autowiring):Spring自动满足bean之间的依赖

将组件扫描到的bean和他们的依赖装配到一起。根据上面的依赖注入分析,常用的有两种,构造器注入,和set注入

下面先分析装配注解

  • @Autowired 默认是按照类型注入,它要求依赖对象必须存在。如果允许null值,可以设置它required属性为false。如果我们想使用ByName装配,可以结合@Qualifier注解一起使用
//自动注入
public class CDPlayer implements MediaPlayer{

@Autowired
private CompactDisc compactDisc;
}
//set 注入
@Component
public class CDPlayer implements MediaPlayer{

private CompactDisc compactDisc;

@Autowired
public setCompactDisc(CompactDisc compactDisc){
this.compactDisc =compactDisc;
}

//构造注入
public class CDPlayer implements MediaPlayer{

private CompactDisc compactDisc;

@Autowired
public CDPlayer(CompactDisc cd){
this.compactDisc =cd;
}
}

@Autowired是spring特有的注解。可以考虑用@Inject替换(源于java依赖注入规范)

  • @Resource 默认按照名字注入,有两个重要的属性name和type。name属性指定byName,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。需要注意的是,@Resource如果没有指定name属性,并且按照默认的名称仍然找不到依赖对象时, @Resource注解会回退到按类型装配。但一旦指定了name属性,就只能按名称装配了。

常用@Autowired注解,@Resource注解就不详细演示。

 

上面主要讲了自动化装配。下面讲解一下显示装配。

显示装配。

比如有时候比如要将第三方库中的组件装配到你应用中,是没有办法在它的类上添加@Component和@Autowired注解的,因此不能使用自动化装配

在这种情况下,你必须采用显示装配的方式。使用java配置或者xml

1.创建配置类

@Configuration
public class CDPlayerConfig{

}

创建javaConfig类的关键在于为其添加@Configuration注解。表明这是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。但是必须开组件扫描,不管是注解还是xml

2.声明bean

在JavaConfig中声明bean。我们需要编写一个方法,如下,这个方法会创建所需类型的实例,然后给这个方法添加@Bean注解。然后就声明了名字为sgtPeppers的bean,类型为CompactDisc .
@Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring应用上下文中的bean。默认情况下Bean的ID和带有@Bean注解的方法的名字是一样的。如果想重命名。使用@Bean(name="bean的名字")

@Configuration
public class CDPlayerConfig{

@Bean
public CompactDisc sgtPeppers(){
 return new SgtPeppers();
}

}

借助javaconfig实现注入

上面声明的bean是非常简单的,自身没有其他依赖。现在要声明CDPlayer bean,它依赖于CompactDisc,如何装配?

在JavaConfig中装配bean的最简单方式就是引用创建bean的方法。如下装配

@Bean
public CDPlayer cdPlayer(){
return new CDPlayer(sgtPeppers());
}

cdPlayer()方法像sgtPeppers()方法一样,同样使用了@Bean注解,这表明这个方法会创建一个bean实例并将其注册到Spring上下文中。

cdPlayer()的方法体与sgtPeppers()稍微有些区别。在这里并没有使用默认的构造器构建实例。而是调用了需要传入CompactDisc对象的构造器来创建CDplayer实例。看来,CompactDisc 是通过sgtPeppers()得到的。但情况并非完全如此,因为sgtPeppers()方法上添加了@Bean注解。spring将会拦截所有对它的调用,并确保直接返回该方法所创建的bean,而不是每次都对其进行实际调用。

或者这样写,下面的写法一般是最佳的选择,因为他不会要求将ComPactDisc声明到同一个配置类中。在这里甚至没有要求ComPactDisc必须要在JavaConfig中声明,实际上它可以通过组件扫描功能自动发现或者通过xml配置

@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc ){
return new CDPlayer(compactDisc );
}

cdPlayer()方法请求一个CompactDisc作为参数。当spring调用cdPlayer()创建CDPlayer bean的时候,它会自动装配一个CompactDisc到配置方法中。通过这种方式引用其他的bean通常是最佳的选择,因为它不会要求将CompactDisc声明到同一个配置类中。在这里甚至没有要求CompactDisc必须要在JavaConfig中声明。实际上它可以通过组件扫描功能自动发现或者通过xml来进行配置。

导入和混合配置

自动化装配和显示配置(javaconfig或者xml)同时使用

多个配置类组合@import注解

假设单个配置类CDPlayerConfig已经显得有些笨重。我们所能实现的一种方案就是将BlankDisc从CDPlayerConfig拆出来,定义到它自己的CDConfig类中,如下显示

@Configuration
public class CDConfig{

@Bean
public CompactDisc compactDisc(){
return new SgtPeppers();
}}

我们需要有一种方式将这两个类组合到一起。

一种方法就是在CDPlayerConfig中使用@Import注解导入CDConfig

@Configuration
@import(CDConfig.class)
public class CDPlayerConfig{

@Bean
public CDPlayer cdPlayer(CompactDisc  compactDisc ){
return new CDPlayer(compactDisc);
}
}

或者采用另一个更好的办法,创建一个更高级别的SoundSystemConfig,在这个类中使用@Import将两个配置组合在一起

@Configuration
@Import({CDPlayerConfig.class,CDConfig.class})
public class SoundSystemConfig{

}

在javaconfig中引入xml配置@ImportResource注解

我们假设通过xml来配置BlankDisc.

<bean id="compactDisc" class="...BlankDisc"></bean>

那么如何让spring加载它和其它基于java的配置呢?

@ImportResource注解,假设BlankDisc定义在名为cd-config.xml的文件中。该文件位于跟类路径下,那么可以修改SoundSystemConfig,让它使用@ImportResource注解

@Configuration
@Import({CDPlayerConfig.class})
@ImportResource("classpath:cd-config.xml")
public class SoundSystemConfig{

}

 

上一篇:Python下载图片小程序


下一篇:1164【毕设课设】基于8086的16x64点阵汉字显示