本人收集了一些在大家在面试时被经常问及的关于Spring的主要问题,这些问题有可能在你下次面试时就会被问到。对于本文中未提及的Spring其他模块,我会单独分享面试的问题和答案。 欢迎大家向我推荐你在面试过程中遇到关于Spring的问题。我会把大家推荐的问题添加到下面的Spring常用面试题清单*大家参考。
以下问题,用于复习面试也是可以的。
- IOC和DI是什么?
- Spring IOC 的理解,其初始化过程?
- BeanFactory 和 FactoryBean的区别?
- BeanFactory和ApplicationContext的区别?
- ApplicationContext 上下文的生命周期?
- Spring Bean 的生命周期?
- Spring AOP的实现原理?
- Spring 是如何管理事务的,事务管理机制?
- Spring 的不同事务传播行为有哪些,干什么用的?
- Spring 中用到了那些设计模式?
- Spring MVC 的工作原理?
- Spring如何解决循环依赖?
- Spring 如何保证 Controller 并发的安全?
IOC和DI是什么?
IoC (Inversion of Control) 控制反转什么是控制反转?
控制反转是就是应用本身不负责依赖对象的创建和维护,依赖对象的创建及维护是由外部容器负责的,这样控制权就有应用转移到了外部容器,控制权的转移就是控制反转。
DI (Dependency Injection) 依赖注入什么是依赖注入?
依赖注入是指:在程序运行期间,由外部容器动态地将依赖对象注入到组件中 如:一般,通过构造函数注入或者setter注入。
Spring IOC 的理解,其初始化过程?
IOC 容器的初始化分为三个过程实现:
- Resource资源定位,这个Resouce指的是BeanDefinition的资源定位,这个过程就是容器找数据的过程。
- BeanDefinition的载入过程,这个载入过程是把用户定义好的Bean表示成Ioc容器内部的数据结构,而这个容器内部的数据结构就是BeanDefition。
- 向IOC容器注册这些BeanDefinition的过程,这个过程就是将前面的BeanDefition保存到HashMap中的过程。
Resource定位
我们一般使用外部资源来描述Bean对象,所以IOC容器第一步就是需要定位Resource外部资源。Resource的定位其实就是BeanDefinition的资源定位,它是由ResourceLoader通过统一的Resource接口来完成的,这个Resource对各种形式的BeanDefinition的使用都提供了统一接口。
载入
第二个过程就是BeanDefinition的载入,BeanDefinitionReader读取,解析Resource定位的资源,也就是将用户定义好的Bean表示成IOC容器的内部数据结构也就是BeanDefinition,在IOC容器内部维护着一个BeanDefinition Map的数据结构,通过这样的数据结构,IOC容器能够对Bean进行更好的管理。
在配置文件中每一个都对应着一个BeanDefinition对象。
注册
第三个过程则是注册,即向IOC容器注册这些BeanDefinition,这个过程是通过BeanDefinitionRegistery接口来实现的。
在IOC容器内部其实是将第二个过程解析得到的BeanDefinition注入到一个HashMap容器中,IOC容器就是通过这个HashMap来维护这些BeanDefinition的。
上面提到的过程一般是不包括Bean的依赖注入的实现,Bean的载入和依赖注入是两个独立的过程,依赖注入是发生在应用第一次调用getBean向容器所要Bean时。
当然我们可以通过设置预处理,即对某个Bean设置lazyinit属性,那么这个Bean的依赖注入就会在容器初始化的时候完成。
经过这 (Resource定位,载入,注册)三个步骤,IOC容器的初始化过程就已经完成了。
BeanFactory 和 FactoryBean的区别?
- BeanFactory是个Factory,也就是IOC容器或对象工厂,在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的,提供了实例化对象和拿对象的功能。
- FactoryBean是个Bean,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。
BeanFactory和ApplicationContext的区别?
BeanFactory
是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能。
两者装载bean的区别
- BeanFactory:在启动的时候不会去实例化Bean,中有从容器中拿Bean的时候才会去实例化;
- ApplicationContext:在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化;
我们该用BeanFactory还是ApplicationContent
BeanFactory 延迟实例化的优点:
应用启动的时候占用资源很少,对资源要求较高的应用,比较有优势;
缺点:速度会相对来说慢一些。而且有可能会出现空指针异常的错误,而且通过bean工厂创建的bean生命周期会简单一些
ApplicationContext 不延迟实例化的优点:
- 所有的Bean在启动的时候都加载,系统运行的速度快;
- 在启动的时候所有的Bean都加载了,我们就能在系统启动的时候,尽早的发现系统中的配置问题
- 建议web应用,在启动的时候就把所有的Bean都加载了。
缺点:把费时的操作放到系统启动中完成,所有的对象都可以预加载,缺点就是消耗服务器的内存
ApplicationContext其他特点
除了提供BeanFactory所支持的所有功能外,ApplicationContext还有额外的功能
- 默认初始化所有的Singleton,也可以通过配置取消预初始化。
- 继承MessageSource,因此支持国际化。
- 资源访问,比如访问URL和文件(ResourceLoader);
- 事件机制,(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层;
- 同时加载多个配置文件。
- 消息发送、响应机制(ApplicationEventPublisher);
- 以声明式方式启动并创建Spring容器。
由于ApplicationContext会预先初始化所有的Singleton Bean,于是在系统创建前期会有较大的系统开销,但一旦ApplicationContext初始化完成,程序后面获取Singleton Bean实例时候将有较好的性能。
也可以为bean设置lazy-init属性为true,即Spring容器将不会预先初始化该bean。
spring的AOP(常用的是拦截器)
一般拦截器都是实现HandlerInterceptor,其中有三个方法preHandle、postHandle、afterCompletion
- preHandle:执行controller之前执行
- postHandle:执行完controller,return modelAndView之前执行,主要操作modelAndView的值
- afterCompletion:controller返回后执行
spring载入多个上下文
不同项目使用不同分模块策略,spring配置文件分为
- applicationContext.xml(主文件,包括JDBC配置,hibernate.cfg.xml,与所有的Service与DAO基类)
- applicationContext-cache.xml(cache策略,包括hibernate的配置)
- applicationContext-jmx.xml(JMX,调试hibernate的cache性能)
- applicationContext-security.xml(acegi安全)
- applicationContext-transaction.xml(事务)
- moduleName-Service.xml
- moduleName-dao.xml
ApplicationContext 上下文的生命周期?
PS:可以借鉴Servlet的生命周期,实例化、初始init、接收请求service、销毁destroy;
Spring上下文中的Bean也类似,【Spring上下文的生命周期】
- 实例化一个Bean,也就是我们通常说的new;
- 按照Spring上下文对实例化的Bean进行配置,也就是IOC注入
- 如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的是Spring配置文件中Bean的ID;
- 如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(),传递的是Spring工厂本身(可以用这个方法获取到其他Bean);
- 如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文,该方式同样可以实现步骤4,但比4更好,以为ApplicationContext是BeanFactory的子接口,有更多的实现方法;
- 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用After方法,也可用于内存或缓存技术;
- 如果这个Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法;
- 如果这个Bean关联了BeanPostProcessor接口,将会调用postAfterInitialization(Object obj, String s)方法;
注意:以上工作完成以后就可以用这个Bean了,那这个Bean是一个single的,所以一般情况下我们调用同一个ID的Bean会是在内容地址相同的实例
- 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean接口,会调用其实现的destroy方法
- 最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法
以上10步骤可以作为面试或者笔试的模板,另外这里描述的是应用Spring上下文Bean的生命周期,如果应用Spring的工厂也就是BeanFactory的话去掉第5步就Ok了;
Spring Bean 的生命周期?
Spring框架中,一旦把一个Bean纳入Spring IOC容器之中,这个Bean的生命周期就会交由容器进行管理,一般担当管理角色的是BeanFactory或者ApplicationContext,认识一下Bean的生命周期活动,对更好的利用它有很大的帮助:
下面以BeanFactory为例,说明一个Bean的生命周期活动。
- Bean的建立, 由BeanFactory读取Bean定义文件,并生成各个实例;
- Setter注入,执行Bean的属性依赖注入;
- BeanNameAware的setBeanName(), 如果实现该接口,则执行其setBeanName方法;
- BeanFactoryAware的setBeanFactory(),如果实现该接口,则执行其setBeanFactory方法;
- BeanPostProcessor的processBeforeInitialization(),如果有关联的processor,则在Bean初始化之前都会执行这个实例的processBeforeInitialization()方法;
- InitializingBean的afterPropertiesSet(),如果实现了该接口,则执行其afterPropertiesSet()方法;
- Bean定义文件中定义init-method;
- BeanPostProcessors的processAfterInitialization(),如果有关联的processor,则在Bean初始化之前都会执行这个实例的processAfterInitialization()方法;
- DisposableBean的destroy(),在容器关闭时,如果Bean类实现了该接口,则执行它的destroy()方法;
- Bean定义文件中定义destroy-method,在容器关闭时,可以在Bean定义文件中使用“destory-method”定义的方法;
如果使用ApplicationContext来维护一个Bean的生命周期,则基本上与上边的流程相同,只不过在执行BeanNameAware的setBeanName()后,若有Bean类实现了org.springframework.context.ApplicationContextAware接口,则执行其setApplicationContext()方法,然后再进行BeanPostProcessors的processBeforeInitialization() 实际上,ApplicationContext除了向BeanFactory那样维护容器外,还提供了更加丰富的框架功能,如Bean的消息,事件处理机制等
Spring AOP的实现原理?
所谓AOP,即Aspect orientied program,就是面向方面(切面)的编程。
特点
让关注点代码与业务代码分离,可以动态地添加和删除在切面上的逻辑而不影响原来的执行代码。
- 模块之间的耦合度;
- 统容易扩展;
- 的代码复用;
AOP核心概念
1、横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
2、切面(aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象,面向切面编程,就是指 对很多功能都有的重复的代码抽取,再在运行的时候往业务方法上动态植入“切面类代码”。
3、连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
4、切入点(pointcut)
切入点在AOP中的通知和切入点表达式关联,指定拦截哪些类的哪些方法, 给指定的类在运行的时候动态的植入切面类代码。
5、通知(advice)
所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
6、目标对象
被一个或者多个切面所通知的对象。
7、织入(weave)
将切面应用到目标对象并导致代理对象创建的过程
8、引入(introduction)
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
9、AOP代理(AOP Proxy)
在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理。
使用注解
- @Aspect指定一个类为切面类;
- @Pointcut("execution(* cn.itcast.e_aop_anno..(..))") 指定切入点表达式;
- @Before("pointCut_()")前置通知: 目标方法之前执行;
- @After("pointCut_()")后置通知:目标方法之后执行(始终执行);
- @AfterReturning("pointCut_()")返回后通知: 执行方法结束前执行(异常不执行);
- @AfterThrowing("pointCut_()")异常通知: 出现异常时候执行;
- @Around("pointCut_()")环绕通知:环绕目标方法执行;
AOP的实现原理
Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
Spring对AOP的支持
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。
Spring创建代理的规则为:
- 默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
- 当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB
AOP编程其实是很简单的事情,程序员只需要参与三个部分:
- 定义普通业务组件
- 定义切入点,一个切入点可能横切多个业务组件
- 定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作
所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理
即:代理对象的方法=增强处理+被代理对象的方法。
Spring 是如何管理事务的,事务管理机制?
事务管理可以帮助我们保证数据的一致性,对应企业的实际应用很重要。
Spring的事务机制包括声明式事务和编程式事务。
- 编程式事务管理:Spring推荐使用TransactionTemplate,实际开发中使用声明式事务较多。
- 声明式事务管理:将我们从复杂的事务处理中解脱出来,获取连接,关闭连接、事务提交、回滚、异常处理等这些操作都不用我们处理了,Spring都会帮我们处理。
声明式事务管理使用了AOP面向切面编程实现的,本质就是在目标方法执行前后进行拦截。在目标方法执行前加入或创建一个事务,在执行方法执行后,根据实际情况选择提交或是回滚事务。
如何管理的
Spring事务管理主要包括3个接口,Spring的事务主要是由它们(PlatformTransactionManager,TransactionDefinition,TransactionStatus)三个共同完成的。
1. PlatformTransactionManager:事务管理器--主要用于平台相关事务的管理
主要有三个方法:
- commit 事务提交;
- rollback 事务回滚;
- getTransaction 获取事务状态。
2. TransactionDefinition:事务定义信息--用来定义事务相关的属性,给事务管理器PlatformTransactionManager使用
这个接口有下面四个主要方法:
- getIsolationLevel:获取隔离级别;
- getPropagationBehavior:获取传播行为;
- getTimeout:获取超时时间;
- isReadOnly:是否只读(保存、更新、删除时属性变为false--可读写,查询时为true--只读)
事务管理器能够根据这个返回值进行优化,这些事务的配置信息,都可以通过配置文件进行配置。
3. TransactionStatus:事务具体运行状态--事务管理过程中,每个时间点事务的状态信息。
例如它的几个方法:
- hasSavepoint():返回这个事务内部是否包含一个保存点,
- isCompleted():返回该事务是否已完成,也就是说,是否已经提交或回滚
- isNewTransaction():判断当前事务是否是一个新事务
声明式事务的优缺点:
- 优点:不需要在业务逻辑代码中编写事务相关代码,只需要在配置文件配置或使用注解(@Transaction),这种方式没有侵入性。
- 缺点:声明式事务的最细粒度作用于方法上,如果像代码块也有事务需求,只能变通下,将代码块变为方法。
事务的4个特性:
原子性(Atomicity)
- 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
一致性(Consistency)
- 一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
隔离性(Isolation)
- 隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。 持久性(Durability)
- 持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
事务的隔离级别
从理论上来说, 事务应该彼此完全隔离, 以避免并发事务所导致的问题,然而, 那样会对性能产生极大的影响, 因为事务必须按顺序运行, 在实际开发中, 为了提升性能, 事务会以较低的隔离级别运行, 事务的隔离级别可以通过隔离事务属性指定。
- ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.另外四个与JDBC的隔离级别相对应
- ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
- ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
- ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
- ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。
- 事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持.
- Oracle 支持的 2 种事务隔离级别:READ_COMMITED , SERIALIZABLE
- Mysql 支持 4 中事务隔离级别.
事务回滚属性
- 默认情况下只有未检查异常(RuntimeException和Error类型的异常)会导致事务回滚. 而受检查异常不会.
- 事务的回滚规则可以通过@Transactional 注解的 rollbackFor 和 noRollbackFor 属性来定义,这两个属性被声明为 Class[] 类型的, 因此可以为这两个属性指定多个异常类。
- rollbackFor: 遇到时必须进行回滚
- noRollbackFor: 一组异常类,遇到时必须不回滚
不推荐使用的手动回滚事务的方法:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
复制代码
Spring 的不同事务传播行为有哪些,干什么用的?
当事务方法被另一个事务方法调用时, 必须指定事务应该如何传播. 例如: 方法可能继续在现有事务中运行, 也可能开启一个新事务, 并在自己的事务中运行. 事务的传播行为可以由传播属性指定.
Spring 定义了7 种类传播行为.(一二两种最常用)
- ==PROPAGATION_REQUIRED==: 如果存在一个事务,则支持当前事务,如果没有事务则开启。
- ==PROPAGATION_REQUIRES_NEW==: 总是开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起。
- PROPAGATION_SUPPORTS: 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行
- PROPAGATION_MANDATORY: 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
- PROPAGATION_NOT_SUPPORTED: 总是非事务地执行,并挂起任何存在的事务。
- PROPAGATION_NEVER: 总是非事务地执行,如果存在一个活动事务,则抛出异常
- PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行
Spring 中用到了那些设计模式?
Spring框架中使用到了大量的设计模式,下面列举了比较有代表性的:
- 代理模式—在AOP和remoting中被用的比较多。
- 单例模式—在spring配置文件中定义的bean默认为单例模式。
- 模板方法—用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
- 工厂模式—BeanFactory用来创建对象的实例。
- 适配器--spring aop
- 装饰器--spring data hashmapper
- 观察者-- spring 时间驱动模型
- 回调--Spring ResourceLoaderAware回调接口
工厂模式(Factory Method)
Spring容器就是实例化和管理Bean的工厂
工厂模式隐藏了创建类的细节,返回值必定是接口或者抽象类,而不是具体的某个对象,工厂类根据条件生成不同的子类实例。当得到子类的实例后,就可以调用基类中的方法,不必考虑返回的是哪一个子类的实例。
这个很明显,在各种BeanFactory以及ApplicationContext创建中都用到了;
Spring通过配置文件,就可以管理所有的bean,而这些bean就是Spring工厂能产生的实例,因此,首先我们在Spring配置文件中对两个实例进行配置。
单态模式【单例模式】(Singleton)
Spring默认将所有的Bean设置成 单例模式,即对所有的相同id的Bean的请求,都将返回同一个共享的Bean实例。这样就可以大大降低Java创建对象和销毁时的系统开销。
使用Spring将Bean设置称为单例行为,则无需自己完成单例模式。
可以通过singleton=“true|false” 或者 scope=“?”来指定
适配器(Adapter)
在Spring的Aop中,使用的Advice(通知)来增强被代理类的功能。Spring实现这一AOP功能的原理就使用代理模式(1、JDK动态代理。2、CGLib字节码生成技术代理。)对类进行方法级别的切面增强,即,生成被代理类的代理类, 并在代理类的方法前,设置拦截器,通过执行拦截器重的内容增强了代理方法的功能,实现的面向切面编程。
代理(Proxy)
Spring实现了一种能够通过额外的方法调用完成任务的设计模式 - 代理设计模式,比如JdkDynamicAopProxy和Cglib2AopProxy。
代理设计模式的一个很好的例子是org.springframework.aop.framework.ProxyFactoryBean。该工厂根据Spring bean构建AOP代理。该类实现了定义getObject()方法的FactoryBean接口。此方法用于将需求Bean的实例返回给bean factory。在这种情况下,它不是返回的实例,而是AOP代理。在执行代理对象的方法之前,可以通过调用补充方法来进一步“修饰”代理对象(其实所谓的静态代理不过是在装饰模式上加了个要不要你来干动作行为而已,而不是装饰模式什么也不做就加了件衣服,其他还得由你来全权完成)。
观察者(Observer)
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 spring中Observer模式常用的地方是listener的实现。如ApplicationListener。
Spring MVC 的工作原理?
SpringMVC流程
- 用户发送请求至前端控制器DispatcherServlet。
- DispatcherServlet收到请求调用HandlerMapping处理器映射器。
- 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
- DispatcherServlet调用HandlerAdapter处理器适配器。
- HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
- Controller执行完成返回ModelAndView。
- HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
- ViewReslover解析后返回具体View。
- DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
- DispatcherServlet响应用户。
- 第一步: 用户发起请求到前端控制器(DispatcherServlet)
- 第二步:前端控制器请求处理器映射器(HandlerMappering)去查找处理器(Handle):通过xml配置或者注解进行查找
- 第三步:找到以后处理器映射器(HandlerMappering)像前端控制器返回执行链(HandlerExecutionChain)
- 第四步:前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)去执行处理器(Handler)
- 第五步:处理器适配器去执行Handler
- 第六步:Handler执行完给处理器适配器返回ModelAndView
- 第七步:处理器适配器向前端控制器返回ModelAndView
- 第八步:前端控制器请求视图解析器(ViewResolver)去进行视图解析
- 第九步:视图解析器像前端控制器返回View
- 第十步:前端控制器对视图进行渲染
- 第十一步:前端控制器向用户响应结果
看到这些步骤我相信大家很感觉非常的乱,这是正常的,但是这里主要是要大家理解springMVC中的几个组件:
前端控制器(DispatcherServlet):接收请求,响应结果,相当于电脑的CPU。
处理器映射器(HandlerMapping):根据URL去查找处理器
处理器(Handler):(需要程序员去写代码处理逻辑的)
处理器适配器(HandlerAdapter):会把处理器包装成适配器,这样就可以支持多种类型的处理器,类比笔记本的适配器(适配器模式的应用)
视图解析器(ViewResovler):进行视图解析,多返回的字符串,进行处理,可以解析成对应的页面
Spring如何解决循环依赖?
一、构造器循环依赖:表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖。
如在创建CircleA类时,构造器需要CircleB类,那将去创建CircleB,在创建CircleB类时又发现需要CircleC类,则又去创建CircleC,最终在创建CircleC时发现又需要CircleA;从而形成一个环,没办法创建。
Spring容器将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉
1)首先让我们看一下配置文件(chapter3/circleInjectByConstructor.xml):
<bean id="circleA" class="cn.javass.spring.chapter3.bean.CircleA">
<constructor-arg index="0" ref="circleB"/>
</bean>
<bean id="circleB" class="cn.javass.spring.chapter3.bean.CircleB">
<constructor-arg index="0" ref="circleC"/>
</bean>
<bean id="circleC" class="cn.javass.spring.chapter3.bean.CircleC">
<constructor-arg index="0" ref="circleA"/>
</bean>
复制代码
2)写段测试代码(cn.javass.spring.chapter3.CircleTest)测试一下吧:
@Test(expected = BeanCurrentlyInCreationException.class)
public void testCircleByConstructor() throws Throwable {
try {
new ClassPathXmlApplicationContext("chapter3/circleInjectByConstructor.xml");
}
catch (Exception e) {
//因为要在创建circle3时抛出;
Throwable e1 = e.getCause().getCause().getCause();
throw e1;
}
}
复制代码
让我们分析一下吧:
1、Spring容器创建“circleA” Bean,首先去“当前创建Bean池”查找是否当前Bean正在创建,如果没发现,则继续准备其需要的构造器参数“circleB”,并将“circleA” 标识符放到“当前创建Bean池”;
2、Spring容器创建“circleB” Bean,首先去“当前创建Bean池”查找是否当前Bean正在创建,如果没发现,则继续准备其需要的构造器参数“circleC”,并将“circleB” 标识符放到“当前创建Bean池”;
3、Spring容器创建“circleC” Bean,首先去“当前创建Bean池”查找是否当前Bean正在创建,如果没发现,则继续准备其需要的构造器参数“circleA”,并将“circleC” 标识符放到“当前创建Bean池”;
4、到此为止Spring容器要去创建“circleA”Bean,发现该Bean 标识符在“当前创建Bean池”中,因为表示循环依赖,抛出BeanCurrentlyInCreationException。
二、setter循环依赖:表示通过setter注入方式构成的循环依赖。
对于setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的Bean来完成的,而且只能解决单例作用域的Bean循环依赖。
addSingletonFactory(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
复制代码
具体步骤如下:
1、Spring容器创建单例“circleA” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory ”用于返回一个提前暴露一个创建中的Bean,并将“circleA” 标识符放到“当前创建Bean池”;然后进行setter注入“circleB”;
2、Spring容器创建单例“circleB” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的Bean,并将“circleB” 标识符放到“当前创建Bean池”,然后进行setter注入“circleC”;
3、Spring容器创建单例“circleC” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory ”用于返回一个提前暴露一个创建中的Bean,并将“circleC” 标识符放到“当前创建Bean池”,然后进行setter注入“circleA”;进行注入“circleA”时由于提前暴露了“ObjectFactory”工厂从而使用它返回提前暴露一个创建中的Bean;
4、最后在依赖注入“circleB”和“circleA”,完成setter注入。
Spring 如何保证 Controller 并发的安全?
Spring 多线程请求过来调用的Controller对象都是一个,而不是一个请求过来就创建一个Controller对象。
并发的安全? 原因就在于Controller对象是单例的,那么如果不小心在类中定义了类变量,那么这个类变量是被所有请求共享的,这可能会造成多个请求修改该变量的值,出现与预期结果不符合的异常
那有没有办法让Controller不以单例而以每次请求都重新创建的形式存在呢?
答案是当然可以,只需要在类上添加注解@Scope("prototype")即可,这样每次请求调用的类都是重新生成的(每次生成会影响效率)
虽然这样可以解决问题,但增加了时间成本,总让人不爽,还有其他方法么?答案是肯定的!
使用ThreadLocal来保存类变量,将类变量保存在线程的变量域中,让不同的请求隔离开来。