三、Spring Beans
13. 什么是Spring beans?
在 Spring 中,构成应用程序主干并由Spring IOC容器管理的对象称为bean。bean是一个由Spring IoC容器实例
化、组装和管理的对象。
概念简单明了,我们提取处关键的信息:
- bean是对象
- bean由SpringIOC管理,由Spring IoC容器实例化、组装和管理
14. 配置Bean有哪几种方式?
- xml:
- 注解:@Component(@Controller 、@Service、@Repostory、@Configuration这些实际上都是继承的@Component) 前提:需要配置扫描包 反射调用构造方法
- javaConfig: @Bean 通常搭配@Configuration的配置类使用,可以自己控制实例化过程,属性配置,如下图。
- @Import 3种方式
springboot自动装配的核心注解@Import- 参数如果是普通类,将该类实例化交给IOC容器管理。(除开下面两种都是普通类,4.2之前的版本只能加载有@Configuration的配置类,但后面版本没有该注解也可以加载)
- 参数如果是ImportBeanDefinitionRegistrar的实现类,支持手工注册bean。
- 参数如果是ImportSelector的实现类,注册selectImports返回的数组(类全路径)则IOC容器批量注册。
15. 解释Spring支持的几种bean的作用域
Spring框架支持以下五种bean的作用域:
- singleton : bean在每个Spring ioc 容器中只有一个实例。
- prototype:一个bean的定义可以有多个实例。
- request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。
- session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情
形下有效。 - application:全局 Web 应用程序范围的范围标识符。
注意: 缺省的Spring bean 的作用域是Singleton。使用 prototype 作用域需要慎重的思考,因为频繁创建和销毁 bean
会带来很大的性能开销。
指定作用域的方式:
- @Scope注解
- xml方式
16. 单例bean的优势
由于不会每次都创建新对象,所以有一下几个性能上的优势:
- 减少了新生成实例的消耗新包括两方面,第一,spring会通过反射或者cglib来生成bean实例这都是耗性能的操作,其次给
对象分配内存也会涉及复杂算法。 提供服务器内存的利用率 ,减少服务器内存消耗 - 减少jvm垃圾回收,由于不会给每个请求都新生成bean实例,所以自然回收的对象少了。
- 可以快速获取到bean,因为单例的获取bean操作除了第一次生成之外,其余的都是从缓存里获取的所以很快。
17. Spring框架中的单例bean是线程安全的吗?(阿里一面)
不是,Spring框架中的单例bean不是线程安全的。
spring 中的 bean 默认是单例模式,spring 框架并没有对单例 bean 进行多线程的封装处理。
实际上大部分时候 spring bean 无状态的(比如 dao 类),所以某种程度上来说 bean 也是安全的,但如果 bean 有状
态的话(比如 view model 对象),那就要开发者自己去保证线程安全了,最简单的就是改变 bean 的作用域,
把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。
扩展
- 有状态就是有数据存储功能(比如成员变量读写)。
- 无状态就是不会保存数据
18. Spring如何处理线程并发问题?
单例Bean的情况,如果在类中声明成员变量,并且有读写操作(有状态),就是线程不安全。
解决:
- 设置为多例
- 将成员变量放在ThreadLocal
- 同步锁 会影响服务器吞吐量
- 只需要把成员变量声明在方法中(无状态), 单例Bean是线程安全的
19. Spring实例化bean方式的几种方式
- 构造器方式(反射);
通过例如 、@Component的方式,将完整类路径存到BeanDefinition.beanClass中,Spring在创建bean的时候,就会根据beanClass进行反射,通过反射得到对象实例,反射实际上就是调用构造方法而得到的对象实例
- 静态工厂方式; factory-method
<!--使用静态工厂方法实例化Bean-->
<bean class="cn.tulingxueyuan.beans.Person" id="person" factory-method="createPersonFactory" >
方法必须是静态方法
public static Person createPersonFactory(){
Child child=new Child();
child.setName("儿子");
return child;
}
- 实例工厂方式(@Bean); factory-bean+factory-method
<!--使用实例工厂方法实例化Bean-->
<bean class="cn.tulingxueyuan.beans.Person" id="person" factory-bean="personFactory" factory-method="createPersonFactory" >
调用实例化对象的工厂方法,创建实例对象。
@Bean就是这种方式,就是把@Bean所在的配置类读取到factoryBeanName中,factoryMethodName就是@Bean修饰的方法名
- FactoryBean方式
实现FactoryBean接口,实例化重写getObject方法返回的对象。
四种方式比较,只有第一种是Spring容器控制实例生成,其他三种可以由程序员自己控制它的实例化的过程。
20.什么是bean装配?
装配(注入),或bean 装配是指在Spring 容器中把bean组装到一起,前提是容器需要知道bean的依赖关系,如何通过依赖注入来把它们装配到一起。
手动装配
<bean id="userService" class="com.dcy.service.UserService">
<property name="name" value="你是我的日月星河"></property>
<property name="userDao" ref="userDao"></property>
</bean>
@Value方式
自动装配
例如@AutoWired,如果是ByName,就去容器中找到bean对应的对象装配进去。
21.解释不同方式的自动装配,spring 自动装配 bean 有哪些方式?
在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使
用autowire来配置自动装载模式。
在Spring框架xml配置中共有5种自动装配:
- no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。@Autowired 来进行手动指定
需要自动注入的属性 - byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自
动装配。 - byType:通过参数的数据类型进行自动装配。若 IOC 容器中有多个与目标 Bean 类型一致的 Bean. 在这种情况下, Spring 将无法判定哪个 Bean 最合适该属性, 所以不能执行自动装配
- constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。当 Bean 中存在多个构造器时, 此种自动装配方式将会很复杂. 不推荐使用
JavaBean结构:
自动装配
<!-- 自动装配
byType: 根据类型进行自动装配. 但要求 IOC 容器中只有一个类型对应的 bean, 若有多个则无法完成自动装配.
byName: 若属性名和某一个 bean 的 id 名一致, 即可完成自动装配. 若没有 id 一致的, 则无法完成自动装配
在使用 XML 配置时, 自动装用的不多. 但在基于 注解 的配置时, 自动装配使用的较多
-->
<!-- 自动装配carAuto、addressAuto属性 -->
<bean id="personAuto" class="com.shaohe.spring.beans.autowire.PersonAuto"
autowire="byName">
</bean>
- 在 Bean 配置文件里设置 autowire 属性进行自动装配将会装配 Bean 的所有属性. 然而, 若只希望装配个别属性时, autowire 属性就不够灵活了.
- autowire 属性要么根据类型自动装配, 要么根据名称自动装配, 不能两者兼而有之.
- 一般情况下,在实际的项目中很少使用自动装配功能,因为和自动装配功能所带来的好处比起来,明确清晰的配置文档更有说服力一些
22. 自动装配的限制(需要注意)
- 一定要声明set方法
- 覆盖: 你仍可以用 < constructor-arg >和 < property > 配置来定义依赖,这些配置将始终覆盖自动注入。
- 基本数据类型:不能自动装配简单的属性,如基本数据类型、字符串 (手动注入还是可以注入基本数
据类型的 <property value=“” @Value) - 模糊特性:自动装配不如显式装配精确,如果有可能尽量使用显示装配。
所以更推荐使用手动装配(@Autowired(根据类型、再根据名字) ref=“” 这种方式 更加灵活更加清晰 )
22. 有哪些生命周期回调方法?有哪几种实现方式?
- 生命周期回调: 初始化、销毁
初始化Bean()
1、@PostConstruct注解标注的方法
2、执行InitializingBean实现类的afterPropertiesSet方法
3、执行bean的init-method属性指定的初始化方法
销毁Bean(在容器关闭时)
4、@PreDestory注解标注的方法
5、执行DiposibleBean实现类的destory
6、执行bean的destroy-method属性指定的销毁方法
启动类
public class Run {
public static void main(String[] args) {
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(MainConfig.class);
//bean对象销毁是在容器关闭的时候
context.close();
}
}
配置类
@Configuration
@ComponentScan//扫描该类所在目录及其子目录
public class MainConfig {
//initMethod只能使用@Bean和<bean>标签的方式
//如果使用@Component,没有地方指定initMethod
@Bean(initMethod ="init2",destroyMethod = "destory2")
public UserService userService(){
return new UserService();
}
}
public class UserService implements InitializingBean, DisposableBean {
@PostConstruct
public void init(){
System.out.println("注解初始化");
}
@PreDestroy
public void destory(){
System.out.println("注解销毁");
}
@Override
public void destroy() throws Exception {
System.out.println("接口销毁");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("接口初始化");
}
public void init2(){
System.out.println("initMethod初始化");
}
public void destory2(){
System.out.println("destroyMethod销毁");
}
}
执行结果: