基础
1.Spring是什么?特性?有哪些模块?
Spring Logo
一句话概括:Spring 是一个轻量级、非入侵式的控制反转 (IoC) 和面向切面 (AOP) 的框架。
2003年,一个音乐家Rod Johnson决定发展一个轻量级的Java开发框架,Spring作为Java战场的龙骑兵渐渐崛起,并淘汰了EJB这个传统的重装骑兵。
Spring重要版本
到了现在,企业级开发的标配基本就是 Spring5 + Spring Boot 2 + JDK 8
Spring有哪些特性呢?
Spring有很多优点:
Spring特性
-
IOC 和 DI 的支持
Spring 的核心就是一个大的工厂容器,可以维护所有对象的创建和依赖关系,Spring 工厂用于生成 Bean,并且管理 Bean 的生命周期,实现高内聚低耦合的设计理念。
-
AOP 编程的支持
Spring 提供了面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等切面功能。
-
声明式事务的支持
支持通过配置就来完成对事务的管理,而不需要通过硬编码的方式,以前重复的一些事务提交、回滚的JDBC代码,都可以不用自己写了。
-
快捷测试的支持
Spring 对 Junit 提供支持,可以通过注解快捷地测试 Spring 程序。
-
快速集成功能
方便集成各种优秀框架,Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis、Quartz 等)的直接支持。
-
复杂API模板封装
Spring 对 JavaEE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等)都提供了模板化的封装,这些封装 API 的提供使得应用难度大大降低。
2.Spring有哪些模块呢?
Spring 框架是分模块存在,除了最核心的Spring Core Container是必要模块之外,其他模块都是可选,大约有 20 多个模块。
Spring模块划分
最主要的七大模块:
-
Spring Core:Spring 核心,它是框架最基础的部分,提供 IOC 和依赖注入 DI 特性。
-
Spring Context:Spring 上下文容器,它是 BeanFactory 功能加强的一个子接口。
-
Spring Web:它提供 Web 应用开发的支持。
-
Spring MVC:它针对 Web 应用中 MVC 思想的实现。
-
Spring DAO:提供对 JDBC 抽象层,简化了 JDBC 编码,同时,编码更具有健壮性。
-
Spring ORM:它支持用于流行的 ORM 框架的整合,比如:Spring + Hibernate、Spring + iBatis、Spring + JDO 的整合等。
-
Spring AOP:即面向切面编程,它提供了与 AOP 联盟兼容的编程实现。
3.Spring有哪些常用注解呢?
Spring有很多模块,甚至广义的SpringBoot、SpringCloud也算是Spring的一部分,我们来分模块,按功能来看一下一些常用的注解:
Spring常用注解
Web:
-
@Controller:组合注解(组合了@Component注解),应用在MVC层(控制层)。
-
@RestController:该注解为一个组合注解,相当于@Controller和@ResponseBody的组合,注解在类上,意味着,该Controller的所有方法都默认加上了@ResponseBody。
-
@RequestMapping:用于映射Web请求,包括访问路径和参数。如果是Restful风格接口,还可以根据请求类型使用不同的注解:
-
@GetMapping
-
@PostMapping
-
@PutMapping
-
@DeleteMapping
-
-
@ResponseBody:支持将返回值放在response内,而不是一个页面,通常用户返回json数据。
-
@RequestBody:允许request的参数在request体中,而不是在直接连接在地址后面。
-
@PathVariable:用于接收路径参数,比如@RequestMapping(“/hello/{name}”)申明的路径,将注解放在参数中前,即可获取该值,通常作为Restful的接口实现方法。
-
@RestController:该注解为一个组合注解,相当于@Controller和@ResponseBody的组合,注解在类上,意味着,该Controller的所有方法都默认加上了@ResponseBody。
容器:
-
@Component:表示一个带注释的类是一个“组件”,成为Spring管理的Bean。当使用基于注解的配置和类路径扫描时,这些类被视为自动检测的候选对象。同时@Component还是一个元注解。
-
@Service:组合注解(组合了@Component注解),应用在service层(业务逻辑层)。
-
@Repository:组合注解(组合了@Component注解),应用在dao层(数据访问层)。
-
@Autowired:Spring提供的工具(由Spring的依赖注入工具(BeanPostProcessor、BeanFactoryPostProcessor)自动注入)。
-
@Qualifier:该注解通常跟 @Autowired 一起使用,当想对注入的过程做更多的控制,@Qualifier 可帮助配置,比如两个以上相同类型的 Bean 时 Spring 无法抉择,用到此注解
-
@Configuration:声明当前类是一个配置类(相当于一个Spring配置的xml文件)
-
@Value:可用在字段,构造器参数跟方法参数,指定一个默认值,支持 #{} 跟 ${} 两个方式。一般将 SpringbBoot 中的 application.properties 配置的属性值赋值给变量。
-
@Bean:注解在方法上,声明当前方法的返回值为一个Bean。返回的Bean对应的类中可以定义init()方法和destroy()方法,然后在@Bean(initMethod=”init”,destroyMethod=”destroy”)定义,在构造之后执行init,在销毁之前执行destroy。
-
@Scope:定义我们采用什么模式去创建Bean(方法上,得有@Bean) 其设置类型包括:Singleton 、Prototype、Request 、 Session、GlobalSession。
AOP:
-
@Aspect:声明一个切面(类上) 使用@After、@Before、@Around定义建言(advice),可直接将拦截规则(切点)作为参数。
-
@After:在方法执行之后执行(方法上)。 -
@Before:在方法执行之前执行(方法上)。 -
@Around:在方法执行之前与之后执行(方法上)。 -
@PointCut:声明切点 在java配置类中使用@EnableAspectJAutoProxy注解开启Spring对AspectJ代理的支持(类上)。
-
事务:
-
@Transactional:在要开启事务的方法上使用@Transactional注解,即可声明式开启事务。
4.Spring 中应用了哪些设计模式呢?
Spring 框架中广泛使用了不同类型的设计模式,下面我们来看看到底有哪些设计模式?
Spring中用到的设计模式
-
工厂模式 : Spring 容器本质是一个大工厂,使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
-
代理模式 : Spring AOP 功能功能就是通过代理模式来实现的,分为动态代理和静态代理。
-
单例模式 : Spring 中的 Bean 默认都是单例的,这样有利于容器对Bean的管理。
-
模板模式 : Spring 中 JdbcTemplate、RestTemplate 等以 Template结尾的对数据库、网络等等进行操作的模板类,就使用到了模板模式。
-
观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
-
适配器模式 :Spring AOP 的增强或通知 (Advice) 使用到了适配器模式、Spring MVC 中也是用到了适配器模式适配 Controller。
-
策略模式:Spring中有一个Resource接口,它的不同实现类,会根据不同的策略去访问资源。
IOC
5.说一说什么是IOC?什么是DI?
Java 是面向对象的编程语言,一个个实例对象相互合作组成了业务逻辑,原来,我们都是在代码里创建对象和对象的依赖。
所谓的IOC(控制反转):就是由容器来负责控制对象的生命周期和对象间的关系。以前是我们想要什么,就自己创建什么,现在是我们需要什么,容器就给我们送来什么。
引入IOC之前和引入IOC之后
也就是说,控制对象生命周期的不再是引用它的对象,而是容器。对具体对象,以前是它控制其它对象,现在所有对象都被容器控制,所以这就叫控制反转。
控制反转示意图
DI(依赖注入):指的是容器在实例化对象的时候把它依赖的类注入给它。有的说法IOC和DI是一回事,有的说法是IOC是思想,DI是IOC的实现。
为什么要使用IOC呢?
最主要的是两个字解耦,硬编码会造成对象间的过度耦合,使用IOC之后,我们可以不用关心对象间的依赖,专心开发应用就行。
6.能简单说一下Spring IOC的实现机制吗?
PS:这道题老三在面试中被问到过,问法是“你有自己实现过简单的Spring吗?”
Spring的IOC本质就是一个大工厂,我们想想一个工厂是怎么运行的呢?
工厂运行
-
生产产品:一个工厂最核心的功能就是生产产品。在Spring里,不用Bean自己来实例化,而是交给Spring,应该怎么实现呢?——答案毫无疑问,反射。
那么这个厂子的生产管理是怎么做的?你应该也知道——工厂模式。
-
库存产品:工厂一般都是有库房的,用来库存产品,毕竟生产的产品不能立马就拉走。Spring我们都知道是一个容器,这个容器里存的就是对象,不能每次来取对象,都得现场来反射创建对象,得把创建出的对象存起来。
-
订单处理:还有最重要的一点,工厂根据什么来提供产品呢?订单。这些订单可能五花八门,有线上签签的、有到工厂签的、还有工厂销售上门签的……最后经过处理,指导工厂的出货。
在Spring里,也有这样的订单,它就是我们bean的定义和依赖关系,可以是xml形式,也可以是我们最熟悉的注解形式。
我们简单地实现一个mini版的Spring IOC:
mini版本Spring IOC
Bean定义:
Bean通过一个配置文件定义,把它解析成一个类型。
-
beans.properties
偷懒,这里直接用了最方便解析的properties,这里直接用一个类型的配置来代表Bean的定义,其中key是beanName,value是class
userDao:cn.fighter3.bean.UserDao -
BeanDefinition.java
bean定义类,配置文件中bean定义对应的实体
public class BeanDefinition { private String beanName; private Class beanClass; //省略getter、setter } -
ResourceLoader.java
资源加载器,用来完成配置文件中配置的加载
public class ResourceLoader { public static Map getResource() { Map beanDefinitionMap = new HashMap(16); Properties properties = new Properties(); try { InputStream inputStream = ResourceLoader.class.getResourceAsStream("/beans.properties"); properties.load(inputStream); Iterator it = properties.stringPropertyNames().iterator(); while (it.hasNext()) { String key = it.next(); String className = properties.getProperty(key); BeanDefinition beanDefinition = new BeanDefinition(); beanDefinition.setBeanName(key); Class clazz = Class.forName(className); beanDefinition.setBeanClass(clazz); beanDefinitionMap.put(key, beanDefinition); } inputStream.close(); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } return beanDefinitionMap; } } -
BeanRegister.java
对象注册器,这里用于单例bean的缓存,我们大幅简化,默认所有bean都是单例的。可以看到所谓单例注册,也很简单,不过是往HashMap里存对象。
public class BeanRegister { //单例Bean缓存 private Map singletonMap = new HashMap(32); /** * 获取单例Bean * * @param beanName bean名称 * @return */ public Object getSingletonBean(String beanName) { return singletonMap.get(beanName); } /** * 注册单例bean * * @param beanName * @param bean */ public void registerSingletonBean(String beanName, Object bean) { if (singletonMap.containsKey(beanName)) { return; } singletonMap.put(beanName, bean); } } -
BeanFactory.java
BeanFactory
-
对象工厂,我们最核心的一个类,在它初始化的时候,创建了bean注册器,完成了资源的加载。
-
获取bean的时候,先从单例缓存中取,如果没有取到,就创建并注册一个bean
public class BeanFactory { private Map beanDefinitionMap = new HashMap(); private BeanRegister beanRegister; public BeanFactory() { //创建bean注册器 beanRegister = new BeanRegister(); //加载资源 this.beanDefinitionMap = new ResourceLoader().getResource(); } /** * 获取bean * * @param beanName bean名称 * @return */ public Object getBean(String beanName) { //从bean缓存中取 Object bean = beanRegister.getSingletonBean(beanName); if (bean != null) { return bean; } //根据bean定义,创建bean return createBean(beanDefinitionMap.get(beanName)); } /** * 创建Bean * * @param beanDefinition bean定义 * @return */ private Object createBean(BeanDefinition beanDefinition) { try { Object bean = beanDefinition.getBeanClass().newInstance(); //缓存bean beanRegister.registerSingletonBean(beanDefinition.getBeanName(), bean); return bean; } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } return null; } }
-
-
测试
-
UserDao.java
我们的Bean类,很简单
public class UserDao { public void queryUserInfo(){ System.out.println("A good man."); } } -
单元测试
public class ApiTest { @Test public void test_BeanFactory() { //1.创建bean工厂(同时完成了加载资源、创建注册单例bean注册器的操作) BeanFactory beanFactory = new BeanFactory(); //2.第一次获取bean(通过反射创建bean,缓存bean) UserDao userDao1 = (UserDao) beanFactory.getBean("userDao"); userDao1.queryUserInfo(); //3.第二次获取bean(从缓存中获取bean) UserDao userDao2 = (UserDao) beanFactory.getBean("userDao"); userDao2.queryUserInfo(); } } -
运行结果
A good man. A good man.
-
至此,我们一个乞丐+破船版的Spring就完成了,代码也比较完整,有条件的可以跑一下。
PS:因为时间+篇幅的限制,这个demo比较简陋,没有面向接口、没有解耦、边界检查、异常处理……健壮性、扩展性都有很大的不足,感兴趣可以学习参考[15]。
7.说说BeanFactory和ApplicantContext?
可以这么形容,BeanFactory是Spring的“心脏”,ApplicantContext是完整的“身躯”。
BeanFactory和ApplicantContext的比喻
-
BeanFactory(Bean工厂)是Spring框架的基础设施,面向Spring本身。
-
ApplicantContext(应用上下文)建立在BeanFactoty基础上,面向使用Spring框架的开发者。
BeanFactory 接口
BeanFactory是类的通用工厂,可以创建并管理各种类的对象。
Spring为BeanFactory提供了很多种实现,最常用的是XmlBeanFactory,但在Spring 3.2中已被废弃,建议使用XmlBeanDefinitionReader、DefaultListableBeanFactory。
Spring5 BeanFactory继承体系
BeanFactory接口位于类结构树的顶端,它最主要的方法就是getBean(String var1),这个方法从容器中返回特定名称的Bean。
BeanFactory的功能通过其它的接口得到了不断的扩展,比如AbstractAutowireCapableBeanFactory定义了将容器中的Bean按照某种规则(比如按名字匹配、按类型匹配等)进行自动装配的方法。
这里看一个 XMLBeanFactory(已过期) 获取bean 的例子:
public class HelloWorldApp{
public static void main(String[] args) {
BeanFactory factory = new XmlBeanFactory (new ClassPathResource("beans.xml"));
HelloWorld obj = (HelloWorld) factory.getBean("helloWorld");
obj.getMessage();
}
}
ApplicationContext 接口
ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能。可以这么说,使用BeanFactory就是手动档,使用ApplicationContext就是自动档。
Spring5 ApplicationContext部分体系类图
ApplicationContext 继承了HierachicalBeanFactory和ListableBeanFactory接口,在此基础上,还通过其他的接口扩展了BeanFactory的功能,包括:
-
Bean instantiation/wiring
-
Bean 的实例化/串联
-
自动的 BeanPostProcessor 注册
-
自动的 BeanFactoryPostProcessor 注册
-
方便的 MessageSource 访问(i18n)
-
ApplicationEvent 的发布与 BeanFactory 懒加载的方式不同,它是预加载,所以,每一个 bean 都在 ApplicationContext 启动之后实例化
这是 ApplicationContext 的使用例子:
public class HelloWorldApp{
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();
}
}
ApplicationContext 包含 BeanFactory 的所有特性,通常推荐使用前者。
8.你知道Spring容器启动阶段会干什么吗?
PS:这道题老三面试被问到过
Spring的IOC容器工作的过程,其实可以划分为两个阶段:容器启动阶段和Bean实例化阶段。
其中容器启动阶段主要做的工作是加载和解析配置文件,保存到对应的Bean定义中。
容器启动和Bean实例化阶段
容器启动开始,首先会通过某种途径加载Congiguration MetaData,在大部分情况下,容器需要依赖某些工具类(BeanDefinitionReader)对加载的Congiguration MetaData进行解析和分析,并将分析后的信息组为相应的BeanDefinition。
xml配置信息映射注册过程
最后把这些保存了Bean定义必要信息的BeanDefinition,注册到相应的BeanDefinitionRegistry,这样容器启动就完成了。
9.能说一下Spring Bean生命周期吗?
可以看看:Spring Bean生命周期,好像人的一生。。
在Spring中,基本容器BeanFactory和扩展容器ApplicationContext的实例化时机不太一样,BeanFactory采用的是延迟初始化的方式,也就是只有在第一次getBean()的时候,才会实例化Bean;ApplicationContext启动之后会实例化所有的Bean定义。
Spring IOC 中Bean的生命周期大致分为四个阶段:实例化(Instantiation)、属性赋值(Populate)、初始化(Initialization)、销毁(Destruction)。
Bean生命周期四个阶段
我们再来看一个稍微详细一些的过程:
-
实例化:第 1 步,实例化一个 Bean 对象
-
属性赋值:第 2 步,为 Bean 设置相关属性和依赖
-
初始化:初始化的阶段的步骤比较多,5、6步是真正的初始化,第 3、4 步为在初始化前执行,第 7 步在初始化后执行,初始化完成之后,Bean就可以被使用了
-
销毁:第 8~10步,第8步其实也可以算到销毁阶段,但不是真正意义上的销毁,而是先在使用前注册了销毁的相关调用接口,为了后面第9、10步真正销毁 Bean 时再执行相应的方法
简单总结一下,Bean生命周期里初始化的过程相对步骤会多一些,比如前置、后置的处理。
最后通过一个实例来看一下具体的细节:
-
定义一个
PersonBean类,实现DisposableBean,InitializingBean,BeanFactoryAware,BeanNameAware这4个接口,同时还有自定义的init-method和destroy-method。
public class PersonBean implements InitializingBean, BeanFactoryAware, BeanNameAware, DisposableBean {
/**
* 身份证号
*/
private Integer no;
/**
* 姓名
*/
private String name;
public PersonBean() {
System.out.println("1.调用构造方法:我出生了!");
}
public Integer getNo() {
return no;
}
public void setNo(Integer no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
System.out.println("2.设置属性:我的名字叫"+name);
}
@Override
public void setBeanName(String s) {
System.out.println("3.调用BeanNameAware#setBeanName方法:我要上学了,起了个学名");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("4.调用BeanFactoryAware#setBeanFactory方法:选好学校了");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("6.InitializingBean#afterPropertiesSet方法:入学登记");
}
public void init() {
System.out.println("7.自定义init方法:努力上学ing");
}
@Override
public void destroy() throws Exception {
System.out.println("9.DisposableBean#destroy方法:平淡的一生落幕了");
}
public void destroyMethod() {
System.out.println("10.自定义destroy方法:睡了,别想叫醒我");
}
public void work(){
System.out.println("Bean使用中:工作,只有对社会没有用的人才放假。。");
}
}
-
定义一个
MyBeanPostProcessor实现BeanPostProcessor接口。
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("5.BeanPostProcessor.postProcessBeforeInitialization方法:到学校报名啦");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("8.BeanPostProcessor#postProcessAfterInitialization方法:终于毕业,拿到毕业证啦!");
return bean;
}
}
-
配置文件,指定
init-method和destroy-method属性
-
测试
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
PersonBean personBean = (PersonBean) context.getBean("personBean");
personBean.work();
((ClassPathXmlApplicationContext) context).destroy();
}
}
-
运行结果:
关于源码,Bean创建过程可以查看AbstractBeanFactory#doGetBean方法,在这个方法里可以看到Bean的实例化,赋值、初始化的过程,至于最终的销毁,可以看看ConfigurableApplicationContext#close()。
Bean生命周期源码追踪
10.Bean定义和依赖定义有哪些方式?
有三种方式:直接编码方式、配置文件方式、注解方式。
Bean依赖配置方式
-
直接编码方式:我们一般接触不到直接编码的方式,但其实其它的方式最终都要通过直接编码来实现。
-
配置文件方式:通过xml、propreties类型的配置文件,配置相应的依赖关系,Spring读取配置文件,完成依赖关系的注入。
-
注解方式:注解方式应该是我们用的最多的一种方式了,在相应的地方使用注解修饰,Spring会扫描注解,完成依赖关系的注入。
11.有哪些依赖注入的方法?
Spring支持构造方法注入、属性注入、工厂方法注入,其中工厂方法注入,又可以分为静态工厂方法注入和非静态工厂方法注入。
Spring依赖注入方法
-
构造方法注入
通过调用类的构造方法,将接口实现类通过构造方法变量传入
public CatDaoImpl(String message){ this. message = message; } -
属性注入
通过Setter方法完成调用类所需依赖的注入
public class Id { private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } } -
工厂方法注入
-
静态工厂注入
静态工厂顾名思义,就是通过调用静态工厂的方法来获取自己需要的对象,为了让 Spring 管理所有对象,我们不能直接通过"工程类.静态方法()"来获取对象,而是依然通过 Spring 注入的形式获取:
public class DaoFactory { //静态工厂 public static final FactoryDao getStaticFactoryDaoImpl(){ return new StaticFacotryDaoImpl(); } } public class SpringAction { //注入对象 private FactoryDao staticFactoryDao; //注入对象的 set 方法 public void setStaticFactoryDao(FactoryDao staticFactoryDao) { this.staticFactoryDao = staticFactoryDao; } }//factory-method="getStaticFactoryDaoImpl"指定调用哪个工厂方法 -
非静态工厂注入
非静态工厂,也叫实例工厂,意思是工厂方法不是静态的,所以我们需要首先 new 一个工厂实例,再调用普通的实例方法。
//非静态工厂 public class DaoFactory { public FactoryDao getFactoryDaoImpl(){ return new FactoryDaoImpl(); } } public class SpringAction { //注入对象 private FactoryDao factoryDao; public void setFactoryDao(FactoryDao factoryDao) { this.factoryDao = factoryDao; } }
-
12.Spring有哪些自动装配的方式?
什么是自动装配?
Spring IOC容器知道所有Bean的配置信息,此外,通过Java反射机制还可以获知实现类的结构信息,如构造方法的结构、属性等信息。掌握所有Bean的这些信息后,Spring IOC容器就可以按照某种规则对容器中的Bean进行自动装配,而无须通过显式的方式进行依赖配置。
Spring提供的这种方式,可以按照某些规则进行Bean的自动装配,元素提供了一个指定自动装配类型的属性:autowire=""
Spring提供了哪几种自动装配类型?
Spring提供了4种自动装配类型:
Spring四种自动装配类型
-
byName:根据名称进行自动匹配,假设Boss又一个名为car的属性,如果容器中刚好有一个名为car的bean,Spring就会自动将其装配给Boss的car属性
-
byType:根据类型进行自动匹配,假设Boss有一个Car类型的属性,如果容器中刚好有一个Car类型的Bean,Spring就会自动将其装配给Boss这个属性
-
constructor:与 byType类似, 只不过它是针对构造函数注入而言的。如果Boss有一个构造函数,构造函数包含一个Car类型的入参,如果容器中有一个Car类型的Bean,则Spring将自动把这个Bean作为Boss构造函数的入参;如果容器中没有找到和构造函数入参匹配类型的Bean,则Spring将抛出异常。
-
autodetect:根据Bean的自省机制决定采用byType还是constructor进行自动装配,如果Bean提供了默认的构造函数,则采用byType,否则采用constructor。
13.Spring 中的 Bean 的作用域有哪些?
Spring的Bean主要支持五种作用域:
Spring Bean支持作用域
-
singleton : 在Spring容器仅存在一个Bean实例,Bean以单实例的方式存在,是Bean默认的作用域。
-
prototype : 每次从容器重调用Bean时,都会返回一个新的实例。
以下三个作用域于只在Web应用中适用:
-
request : 每一次HTTP请求都会产生一个新的Bean,该Bean仅在当前HTTP Request内有效。
-
session : 同一个HTTP Session共享一个Bean,不同的HTTP Session使用不同的Bean。
-
globalSession:同一个全局Session共享一个Bean,只用于基于Protlet的Web应用,Spring5中已经不存在了。
14.Spring 中的单例 Bean 会存在线程安全问题吗?
首先结论在这:Spring中的单例Bean不是线程安全的。
因为单例Bean,是全局只有一个Bean,所有线程共享。如果说单例Bean,是一个无状态的,也就是线程中的操作不会对Bean中的成员变量执行查询以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。
假如这个Bean是有状态的,也就是会对Bean中的成员变量进行写操作,那么可能就存在线程安全的问题。
Spring单例Bean线程安全问题
单例Bean线程安全问题怎么解决呢?
常见的有这么些解决办法:
-
将Bean定义为多例
这样每一个线程请求过来都会创建一个新的Bean,但是这样容器就不好管理Bean,不能这么办。
-
在Bean对象中尽量避免定义可变的成员变量
削足适履了属于是,也不能这么干。
-
将Bean中的成员变量保存在ThreadLocal中⭐
我们知道ThredLoca能保证多线程下变量的隔离,可以在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal里,这是推荐的一种方式。
15.说说循环依赖?
什么是循环依赖?
Spring循环依赖
Spring 循环依赖:简单说就是自己依赖自己,或者和别的Bean相互依赖。
鸡和蛋
只有单例的Bean才存在循环依赖的情况,原型(Prototype)情况下,Spring会直接抛出异常。原因很简单,AB循环依赖,A实例化的时候,发现依赖B,创建B实例,创建B的时候发现需要A,创建A1实例……无限套娃,直接把系统干垮。
Spring可以解决哪些情况的循环依赖?
Spring不支持基于构造器注入的循环依赖,但是假如AB循环依赖,如果一个是构造器注入,一个是setter注入呢?
看看几种情形:
循环依赖的几种情形
第四种可以而第五种不可以的原因是 Spring 在创建 Bean 时默认会根据自然排序进行创建,所以 A 会先于 B 进行创建。
所以简单总结,当循环依赖的实例都采用setter方法注入的时候,Spring可以支持,都采用构造器注入的时候,不支持,构造器注入和setter注入同时存在的时候,看天。
16.那Spring怎么解决循环依赖的呢?
PS:其实正确答案是开发人员做好设计,别让Bean循环依赖,但是没办法,面试官不想听这个。
我们都知道,单例Bean初始化完成,要经历三步:
Bean初始化步骤
注入就发生在第二步,属性赋值,结合这个过程,Spring 通过三级缓存解决了循环依赖:
-
一级缓存 : Map singletonObjects,单例池,用于保存实例化、属性赋值(注入)、初始化完成的 bean 实例
-
二级缓存 : Map earlySingletonObjects,早期曝光对象,用于保存实例化完成的 bean 实例
-
三级缓存 : Map
关注打赏
