我们可以通过Spring当中提供的注解@Component以及它的三个衍生注解(@Controller、@Service、@Repository)来声明IOC容器中的bean对象,为应用程序注入运行时所需要依赖的bean对象,也就是依赖注入DI。
关于IOC容器中Bean的其他使用细节,主要包括以下三方面:
- 获取IOC中的Bean
- 通过注解声明时的一些细节
- 第三Bean的创建
1. 如何从IOC容器中手动的获取到bean对象
这里就不谈DI的方式了哈
SpringBoot项目在启动的时候会自动的创建IOC容器(也称为Spring容器),并且在启动的过程当中会自动的将bean对象都创建好,存放在IOC容器当中。应用程序在运行时需要依赖什么bean对象,就直接进行依赖注入就可以了。
而在Spring容器中提供了一些方法,可以主动从IOC容器中获取到bean对象,下面介绍3种常用方式:
-
根据name获取bean 容器中默认为当前类的首字母小写
Object getBean(String name)
-
根据类型获取bean
<T> T getBean(Class<T> requiredType)
-
根据name获取bean(带类型转换)
<T> T getBean(String name, Class<T> requiredType)
如果知道Spring注入的方式其实也就对应着在DI时,即依赖注入时的两种方式及按照名称,按照类型,还有当有多个实现类时,如果区分。
下面就通过代码来获取以及查看容器中的bean
定义一个父接口:
public interface FatherInterface {
void hello();
}
定义一个实现类,实现父接口;注意加上@Component注解,表示这个类让Spring管理
@Component
public class SonComponent implements FatherInterface{
@Override
public void hello() {
System.out.println("There is son");
}
}
然后在测试类中注入容器对象,从容器中通过上面的三个方法获取bean
@SpringBootTest(classes = SkyApplication.class) // 这里因为我当前的项目是多模块所以需要指定一下
public class MyTestDemo {
// 这个对象就是Springioc容器对象
@Autowired
private ApplicationContext application;
@Test
public void testGetBean(){
// 方式一 根据name获取bean
FatherInterface bean1 = (FatherInterface)application.getBean("sonComponent");
System.out.println(bean1);
// 方式二 根据类型获取bean
FatherInterface bean2 = application.getBean(FatherInterface.class);
System.out.println(bean2);
// 方式三 根据name获取bean(带类型转换)
FatherInterface bean3 = application.getBean("sonComponent",FatherInterface.class);
System.out.println(bean3);
}
}
控制台可见三种方式都可以获得到当前对象,并且当前地址相同
第三种存在的意义在于当使用第二种方式来获取的时候如果接口有两个实现类时,通过名字来区分。
比如现在:一个接口,两个实现类,如果再通过第二种方式获取就会出现异常,因为当按照类型查找时有两个Spring并不知道是哪个
报错主要信息如下:
available: expected single matching bean but found 2:
此时就需要通过名字来指定。
在Spring中我们也可以通过图形化界面来查看容器中的内容,其中高亮的是Spring所依赖的文件,其他的是第三方或我们自己编写bean。
在Spring中也提供了另一种方式通过在子类中使用**@Primary**,来指定当有通过类型获取的时候当有多个实现类时使用哪一个。
2. bean的作用域配置
通过上面我们可以看到不管通过哪一种方式得到的地址都是相同的,也就是单例。可见Spring默认创建的对象都是单例模式。
单例: 指的是在运行环境中一个类:只存在一个对象
spring 默认就是单例
@Scope 表示 作用范围
singleton 单例: 程序运行时只会创建一个对象
1) spring 启动时 就初始化
2) 想要使用时才初始化 应该加 @Lazy
prototype 多例: 每获取一次对象,都创建一个新的
使用时才初始化
写上的话提示:冗余默认参数值分配
即默认。
在Spring中支持五种作用域,后三种在web环境才生效:主要记忆前两种,一个单例一个多例。
作用域 | 说明 |
---|---|
singleton | 容器内同名称的bean只有一个实例(单例)(默认) |
prototype | 每次使用该bean时会创建新的实例(非单例) |
request | 每个请求范围内会创建新的实例(web环境中,了解) |
session | 每个会话范围内会创建新的实例(web环境中,了解) |
application | 每个应用范围内会创建新的实例(web环境中,了解) |
把当前类的的@Scope注解的value修改为prototype
@Component
@Slf4j
@Scope(value ="prototype")
public class SonComponent implements FatherInterface{
....
}
此时再运行查看控制台当前就是多例,而对于创建时机,单例默认是Spring容器初始化时,可以通过@Lazy来指定
3. 管理第三方的bean对象
之前我们所配置的bean,像controller、service,dao三层体系下编写的类,这些类都是我们在项目当中自己定义的类(自定义类)。当我们要声明这些bean,也非常简单,我们只需要在类上加上@Component以及它的这三个衍生注解(@Controller、@Service、@Repository),就可以来声明这个bean对象了。
但是在我们项目开发当中,还有一种情况就是这个类它不是我们自己编写的,而是我们引入的第三方依赖当中提供的。
比如我们要使用dom4j 对xml文件进行解析的时候。因为dom4j就是第三方组织提供的。 dom4j中的SAXReader类就是第三方编写的。
按照我们之前的做法,需要在SAXReader类上添加一个注解@Component(将当前类交给IOC容器管理)
但是第三方提供的类是只读的。无法在第三方类上添加@Component注解或衍生注解。
那么我们应该怎样使用并定义第三方的bean呢?
- 如果要管理的bean对象来自于第三方(不是自定义的),是无法用@Component 及衍生注解声明bean的,就需要用到**@Bean**注解。
解决方案1:在启动类上添加@Bean标识的方法
@SpringBootApplication
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
//声明第三方bean
@Bean //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean
public SAXReader saxReader(){
return new SAXReader();
}
}
您可能会问,同样是new ,我使用的时候不使用注入使用new不好吗? 不好,如果当多次使用的时候每次都是新的对象,这样不便于管理。我们使用IOC的规则就是将对象,统一交给Spring容器(IOC容器)统一管理。
此时容器中就有了SAXReader对象,但在容器中的对象的名字是方法名
说明:以上在启动类中声明第三方Bean的作法,不建议使用(项目中要保证启动类的纯粹性)
解决方案2:在配置类中定义@Bean标识的方法
既然要保证启动类的纯粹性那么我们就创建一个配置类,专为第三方类服务。
@Configuration //配置类 (在配置类当中对第三方bean进行集中的配置管理)
public class CommonConfig {
//声明第三方bean
@Bean //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean
//通过@Bean注解的name/value属性指定bean名称, 如果未指定, 默认是方法名
public SAXReader reader(DeptService deptService){
System.out.println(deptService);
return new SAXReader();
}
}
如下:
在方法上加上一个@Bean注解,Spring 容器在启动的时候,它会自动的调用这个方法,并将方法的返回值声明为Spring容器当中的Bean对象。
注意事项 :
-
通过@Bean注解的name或value属性可以声明bean的名称,如果不指定,默认bean的名称就是方法名。
-
如果第三方bean需要依赖其它bean对象,直接在bean定义方法中设置形参即可,容器会根据类型自动装配。
关于Bean大家只需要保持一个原则:
- 如果是在项目当中我们自己定义的类,想将这些类交给IOC容器管理,我们直接使用@Component以及它的衍生注解来声明就可以。
- 如果这个类它不是我们自己定义的,而是引入的第三方依赖当中提供的类,而且我们还想将这个类交给IOC容器管理。此时我们就需要在配置类中定义一个方法,在方法上加上一个@Bean注解,通过这种方式来声明第三方的bean对象。
既然说到了这里那么关于声明Bean时的几个条件注解是少不了的,也就是通过注解,来告诉Spring我们在什么条件下才去创建这个Bean。即按照条件装配
。在另一个笔记中:
@Conditional四个较常用的派生注解总结