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{
}