Spring核心问题解答

1.谈谈对Spring的理解

Spring是Java EE编程领域的一个轻量级开源框架,该框架由一个叫Rod Johnson的程序员在 2002 年最早提出并随后创建,是为了解决企业级编程开发中的复杂性,实现敏捷开发的应用型框架 。

Spring是一个开源容器框架,它集成各类型的工具,通过核心的BeanFactory实现了底层的类的实例化和生命周期的管理。在整个框架中,各类型的功能被抽象成一个个的 Bean,这样就可以实现各种功能的管理,包括动态加载和切面编程。

Spring定位的领域是许多其他流行的Framework没有的。Spring致力于提供一种方法管理你的业务对象。Spring是全面的和模块化的。Spring有分层的体系结构,这意味着你能选择使用它孤立的任何部分,它的架构仍然是内在稳定的。例如,你可能选择仅仅使用Spring来简单化JDBC的使用,或用来管理所有的业务对象。它的设计从底部帮助你编写易于测试的代码。Spring是用于测试驱动工程的理想的Framework。Spring是潜在地一站式解决方案,定位于与典型应用相关的大部分基础结构。它也涉及到其他Framework没有考虑到的内容。

2.Spring 有哪些组织模块?

Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring模块构建在核心容器之上,核心容器定义了创建、配置和管理 Bean 的方式,如图所示:
请添加图片描述

组成Spring框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

  • 1、Spring Core核心容器:核心容器提供 Spring 框架的基本功能(Spring Core)。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开 。

  • 2、Spring 上下文(Context):Spring 上下文是一个配置文件,向 Spring框架提供上下文信息。Spring 上下文包括企业服务,例如JNDI、EJB、电子邮件、国际化、校验和调度功能。

  • 3、Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。

  • 4、Spring DAO:JDBC DAO抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。

  • 5、Spring ORM:负责框架中对象关系映射,提供相关ORM 接入框架的关系对象管理工具。Spring 框架插入了若干个ORM框架,从而提供了 ORM 的对象关系工具,其中包括JDO、Hibernate和iBatisSQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。

  • 6、Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。

  • 7、Spring MVC 框架:MVC框架是一个全功能的构建 Web应用程序的 MVC 实现。通过策略接口,MVC框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。模型由javabean构成,存放于Map;视图是一个接口,负责显示模型;控制器表示逻辑代码,是Controller的实现。Spring框架的功能可以用在任何J2EE服务器中,大多数功能也适用于不受管理的环境。Spring 的核心要点是:支持不绑定到特定 J2EE服务的可重用业务和数据访问对象。毫无疑问,这样的对象可以在不同J2EE 环境(Web 或EJB)、独立应用程序、测试环境之间重用。

3.Spring 有哪些优点?

总结起来,Spring有如下优点:

  • 1.低侵入式设计,代码污染极低

  • 2.独立于各种应用服务器,基于Spring框架的应用,可以真正实现Write Once,Run Anywhere的承诺

  • 3.Spring的DI机制降低了业务对象替换的复杂性,提高了组件之间的解耦

  • 4.Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式管理,从而提供了更好的复用

  • 5.Spring的ORM和DAO提供了与第三方持久层框架的良好整合,并简化了底层的数据库访问

  • 6.Spring并不强制应用完全依赖于Spring,开发者可自由选用Spring框架的部分或全部

4.@Autowired和@Resource的区别

共同点

@Resource和@Autowired都可以作为注入属性的修饰,在接口仅有单一实现类时,两个注解的修饰效果相同,可以互相替换,不影响使用。

不同点

  • 1、@Resource是JDK原生的注解,@Autowired是Spring2.5 引入的注解

  • 2、@Resource有两个属性name和type。Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。
    所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。
    如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
    @Autowired只根据type进行注入,不会去匹配name。如果涉及到type无法辨别注入对象时,那需要依赖@Qualifier或@Primary注解一起来修饰。

5.Spring的生命周期

在这里插入图片描述

6.Spring框架中用到了哪些设计模式

(1)工厂模式:Spring使用工厂模式,通过BeanFactory和ApplicationContext来创建对象

(2)单例模式:Bean默认为单例模式

(3)策略模式:例如Resource的实现类,针对不同的资源文件,实现了不同方式的资源获取策略

(4)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术

(5)模板方法:可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。比如RestTemplate, JmsTemplate, JpaTemplate

(6)适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式,Spring MVC中也是用到了适配器模式适配Controller

(7)观察者模式:Spring事件驱动模型就是观察者模式的一个经典应用。

(8)桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问中根据需要会去访问不同的数据库

7.循环依赖

在这里插入图片描述

7.1 什么是循环依赖?

例如以下代码: A对象依赖了B对象,B对象依赖了A对象。

	// A依赖了B
	class A{
		public B b;
	} 

	// B依赖了A
	class B{
		public A a;
	}

在 Spring 中,一个对象并不是简单 new 出来了,而是会经过一系列的 Bean 的生命周期,就是因为 Bean 的生命周期所以才会出现循环依赖问题。当然,在 Spring 中,出现循环依赖的场景很多,有的场景 Spring 自动帮我们解决了,而有的场景则需要程序员来解决。

7.2 Bean生成的步骤

被 Spring 管理的对象叫做 Bean 。Bean的生成步骤如下:

  • 1.Spring 扫描 class 得到 BeanDefinition;
  • 2.根据得到的 BeanDefinition 去生成 Bean;
  • 3.首先根据 class 推断构造方法;
  • 4.根据推断出来的构造方法,反射,得到一个对象(暂时叫做原始对象);
  • 5.填充原始对象中的属性(依赖注入);
  • 6.如果原始对象中的某个方法被 AOP 了,那么则需要根据原始对象生成一个代理对象;
  • 7.把最终生成的代理对象放入单例池(源码中叫做 singletonObjects)中,下次 getBean 时就直接从单例池拿即可;

对于 Spring 中的 Bean 的生成过程,步骤还是很多的,并且不仅仅只有上面的7步,还有很多很多,这里不详细说了。

我们可以发现,在得到一个原始对象后,Spring 需要给对象中的属性进行依赖注入,那么这个注入过程是怎样的?
比如上文说的 A 类,A 类中存在一个 B 类的 b 属性,所以,当 A 类生成了一个原始对象之后,就会去给 b 属性去赋值,此时就会根据 b 属性的类型和属性名去 BeanFactory 中去获取 B 类所对应的单例bean。

  1. 如果此时 BeanFactory 中存在 B 对应的 Bean,那么直接拿来赋值给 b 属性;
  2. 如果此时 BeanFactory 中不存在 B 对应的 Bean,则需要生成一个 B 对应的 Bean,然后赋值给 b属性。

问题就出现在「第二种」情况,如果此时 B 类在 BeanFactory 中还没有生成对应的 Bean,那么就需要去生成,就会经过 B 的 Bean 的生命周期。

那么在创建 B 类的 Bean 的过程中,如果 B 类中存在一个 A 类的 a 属性,那么在创建 B 的 Bean 的过程中就需要 A 类对应的 Bean,但是,触发 B 类 Bean 的创建的条件是 A 类 Bean 在创建过程中的依赖注入,所以这里就出现了循环依赖:

A Bean创建–>依赖了 B 属性–>触发 B Bean创建—>B 依赖了 A 属性—>需要 A Bean(但A Bean还在创建过程中)

从而导致 A Bean 创建不出来,B Bean 也创建不出来。

这是循环依赖的场景,但是上文说了,在 Spring 中,通过某些机制帮开发者解决了部分循环依赖的问题,这个机制就是「三级缓存」。

7.3 三级缓存及其作用

/** Cache of singleton objects: bean name –> bean instance */
private final Map singletonObjects = new ConcurrentHashMap(256);

/** Cache of singleton factories: bean name –> ObjectFactory */
private final Map> singletonFactories = new HashMap>(16);

/** Cache of early singleton objects: bean name –> bean instance */
private final Map earlySingletonObjects = new HashMap(16);

  • 一级缓存(singletonObjects): 缓存的是已经经历了完整生命周期的bean对象。

  • 二级缓存(earlySingletonObjects): 比 singletonObjects 多了一个 early ,表示缓存的是早期的 bean对象。早期指的是 Bean 的生命周期还没走完就把这个 Bean 放入了 earlySingletonObjects。

  • 三级缓存(singletonFactories): 缓存的是 ObjectFactory,表示对象工厂,用来创建某个对象的。

7.4 分析循环依赖的原因

之所以产生循环依赖的问题

主要是:A创建时—>需要B---->B去创建—>需要A,从而产生了循环。

在这里插入图片描述
那么如何打破这个循环,加个缓存就可以了!

在这里插入图片描述
A 的 Bean 在创建过程中,在进行依赖注入之前,先把 A 的原始 Bean 放入缓存(提早暴露,只要放到缓存了,其他 Bean 需要时就可以从缓存中拿了),放入缓存后,再进行依赖注入,此时 A 的Bean 依赖了 B 的 Bean 。

如果 B 的 Bean 不存在,则需要创建 B 的 Bean,而创建 B 的 Bean 的过程和 A 一样,也是先创建一个 B 的原始对象,然后把 B 的原始对象提早暴露出来放入缓存中,然后在对 B 的原始对象进行依赖注入 A,此时能从缓存中拿到 A 的原始对象(虽然是 A 的原始对象,还不是最终的 Bean),B 的原始对象依赖注入完了之后,B 的生命周期结束,那么 A 的生命周期也能结束。

因为整个过程中,都只有一个 A 原始对象,所以对于 B 而言,就算在属性注入时,注入的是 A 原始对
象,也没有关系,因为A 原始对象在后续的生命周期中在堆中没有发生变化。

7.5为什么 Spring 中还需要 singletonFactories 呢?

基于上面的场景思考一个问题:

如果 A 的原始对象注入给 B 的属性之后,A 的原始对象进行了 AOP 产生了一个代理对象,此时就会出现,对于 A 而言,它的 Bean 对象其实应该是 AOP 之后的代理对象,而 B 的 a 属性对应的并不是 AOP 之后的代理对象,这就产生了冲突。

B 依赖的 A 和最终的 A 不是同一个对象。

那么如何解决这个问题?这个问题可以说没有办法解决。因为在一个 Bean 的生命周期最后,Spring提供了 BeanPostProcessor 可以去对 Bean 进行加工,这个加工不仅仅只是能修改 Bean 的属性值,也可以替换掉当前 Bean 。

在BeanPostProcessor 中可以完全替换掉某个 beanName 对应的 bean 对象。

而 BeanPostProcessor 的执行在 Bean 的生命周期中是处于属性注入之后的,循环依赖是发生在属性注入过程中的,所以很有可能导致,注入给 B 对象的 A 对象和经历过完整生命周期之后的 A 对象,不是一个对象。这就是有问题的。

所以在这种情况下的循环依赖,Spring 是解决不了的,因为在属性注入时,Spring 也不知道 A 对象后续会经过哪些 BeanPostProcessor 以及会对 A 对象做什么处理。

7.6 Spring解决了哪种情况下的循环依赖

虽然上面的情况可能发生,但是肯定发生得很少。某个 beanName 对应的最终对象和原始对象不是一个对象却会经常出现,这就是 AOP 。

AOP 就是通过一个 BeanPostProcessor 来实现的,在 Spring 中 AOP 利用的要么是 JDK 动态代理,要么 CGLib 的动态代理,所以如果给一个类中的某个方法设置了切面,那么这个类最终就需要生成一个代理对象。

一般过程就是:A 类—>生成一个普通对象–>属性注入–>基于切面生成一个代理对象–>把代理对象
放入 singletonObjects 单例池中。

在这里插入图片描述

而 AOP 可以说是 Spring 中除开 IOC 的另外一大功能,而循环依赖又是属于 IOC 范畴的,所以这两大功能想要并存,Spring 需要特殊处理。

如何处理的,就是利用了第三级缓存 singletonFactories。

首先,singletonFactories 中存的是某个 beanName 对应的 ObjectFactory,在 Bean 的生命周期中,生成完原始对象之后,就会构造一个 ObjectFactory 存入 singletonFactories 中。

在这里插入图片描述

7.7 ObjectFactory是什么?

这个 ObjectFactory 是一个函数式接口,支持Lambda表达式:

() ->getEarlyBeanReference(beanName, mbd, bean)

上面的Lambda表达式就是一个ObjectFactory,执行该Lambda表达式就会去执行getEarlyBeanReference方法,而该方法如下:

在这里插入图片描述
该方法会去执行SmartInstantiationAwareBeanPostProcessor中的getEarlyBeanReference方法,而这个接口下的实现类中只有两个类实现了这个方法,一个是AbstractAutoProxyCreator,一个是InstantiationAwareBeanPostProcessorAdapter,它的实现如下:

InstantiationAwareBeanPostProcessorAdapter:
在这里插入图片描述

AbstractAutoProxyCreator:
在这里插入图片描述
由上图可以得知,在整个Spring中,默认就只有AbstractAutoProxyCreator真正意义上实现了getEarlyBeanReference方法,而该类就是用来进行AOP的。

7.8 getEarlyBeanReference()方法

在这里插入图片描述

首先得到一个cachekey,cachekey就是beanName。然后把beanName和bean(这是原始对象)存入 earlyProxyReferences 中。调用 wrapIfNecessary 进行AOP,得到一个代理对象。

那么什么时候会调用 getEarlyBeanReference 方法呢?让我们继续看如下这张图。
在这里插入图片描述
图中的 ObjectFactory 就是上文说的 labmda 表达式,中间有 getEarlyBeanReference 方法。

注意存入 singletonFactories 时并不会执行 lambda 表达式,也就是不会执行getEarlyBeanReference 方法。

从 singletonFactories 根据 beanName 得到一个 ObjectFactory ,然后执行 ObjectFactory ,也就是执行 getEarlyBeanReference 方法,此时会得到一个 A 原始对象经过 AOP 之后的代理对象,然后把该代理对象放入 earlySingletonObjects 中。

此时并没有把代理对象放入 singletonObjects 中,那什么时候放入到 singletonObjects 中呢?

此时,我们只得到了 A 原始对象的代理对象,这个对象还不完整,因为 A 原始对象还没有进行属性填充,所以此时不能直接把A的代理对象放入 singletonObjects 中,所以只能把代理对象放入earlySingletonObjects 。

假设现在有其他对象依赖了 A,那么则可以从 earlySingletonObjects 中得到 A 原始对象的代理对象了,并且是A的同一个代理对象。

当 B 创建完了之后,A 继续进行生命周期,而 A 在完成属性注入后,会按照它本身的逻辑去进行AOP,而此时我们知道 A 原始对象已经经历过了 AOP ,所以对于 A 本身而言,不会再去进行 AOP了,那么怎么判断一个对象是否经历过了 AOP 呢?

会利用上文提到的 earlyProxyReferences,在 AbstractAutoProxyCreator 的 postProcessAfterInitialization 方法中,会去判断当前 beanName 是否
在 earlyProxyReferences,如果在则表示已经提前进行过 AO P了,无需再次进行 AOP。

对于 A 而言,进行了 AOP 的判断后,以及 BeanPostProcessor 的执行之后,就需要把 A 对应的对象放入 singletonObjects 中了,但是我们知道,应该是要 A 的代理对象放入 singletonObjects 中,所以此时需要从 earlySingletonObjects 中得到代理对象,然后入 singletonObjects 中。

至此,整个循环依赖解决完毕。

7.9 总结一下三级缓存

「singletonObjects」:缓存某个 beanName 对应的经过了完整生命周期的bean;
「earlySingletonObjects」:缓存提前拿原始对象进行了 AOP 之后得到的代理对象,原始对象还没有进行属性注入和后续的 BeanPostProcesso r等生命周期;
「singletonFactories」:缓存的是一个 ObjectFactory ,主要用来去生成原始对象进行了 AOP之后得到的「代理对象」,在每个 Bean 的生成过程中,都会提前暴露一个工厂,这个工厂可能用到,也可能用不到,如果没有出现循环依赖依赖本 bean,那么这个工厂无用,本 bean 按照自己的生命周期执行,执行完后直接把本 bean 放入 singletonObjects 中即可,如果出现了循环依赖依赖了本 bean,则另外那个 bean 执行 ObjectFactory 提交得到一个 AOP 之后的代理对象(如果有 AOP 的话,如果无需 AOP ,则直接得到一个原始对象)。

8.Spring中常用的注解有哪些?

  • 1.@Component
    它是这些注解里面最普通的一个注解,一般用于把普通POJO实例化到Spring容器中。
    @Controller和@Service和@Repository是它的特殊情况,当一个类不需要进行这几种特殊归类的时候,只是作为一个普通的类,被Spring管理就OK的时候,比较适合采用@Component注解。

  • 2.@Controller
    用于标注控制层,表示向控制层注入服务层的数据

  • 3.@Service
    用于标注服务层,来进行业务的逻辑处理,在服务层注入DAO层数据

  • 4.@Repository
    用于标注数据访问层,也可以说用于标注数据访问组件,即DAO组件

  • 5.@ComponentScan
    这个注解长得和@Component有点像,但是他们是完全两个不同类型的注解,@Component像一个标签,标志着你这类是个啥,而@ComponentScan像一个路标,告诉你去哪找东西

    应用场景:在定义Spring中的Bean的时候,一般有两步
    1.在Bean上面添加@Controller/@Service/@Repository/@Component这几个注解,标注这个类是个Bean.        
    2.然后还需要让Spring能够找到这个Bean,这时候就需要使用到@ComponentScan这个注解了使用.
    @ComponentScan(“com.demo”) 引号里面是要扫描的包路径SpringBoot中的使用:
    在SpringBoot中也许不会经常看到这个注解,因为@SpringBootApplication这个注解里面集成
    了@ComponentScan注解,它里面会自动扫描这个类所在包以及子包下面的Bean。
    所以如果我们要找的Bean不在它的所在包或者子包里面,就需要自己再添加
    一个@ComponentScan注解。
    例如:@ComponentScan({“com.demo.springboot”,”com.demo.rp”})
    
  • 6.@ResponseBody
    加了这个注解的类会将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到Response对象的body区,通常用来返回JSON数据或者是XML。

需要注意的是,在使用此注解之后的数据不会再走ViewResolver,而是直接将数据写入到输入流中,它的效果相当于用Response对象输出指定格式的数据。

当它返回json串的时候,效果相当于:

response.getWriter.write(JSONObject.fromObject(对象).toString());
  • 7.@RestController
    @RestController = @Controller + @ResponseBody

写这一个注解就相当于写了后面的两个注解,在返回值是json串的非常方便,但同时也会有一个问题,加了这个注解就不能返回jsp或者html页面,这时可以通过返回视图数据的方式来返回页面。
例如:

ModelAndView mv = new ModelAndView("index")return mv;

8.@RequestMapping(“xxxx”)
它用于 映射客户端的访问地址,可以被应用于类和方法上面,客户进行访问时,URL应该为类+方法上面的这个注解里面的内容。
例如:
下面这个类里面方法的访问地址就应该是:http://localhost:8080/AB

@Controller
@RequestMapping("/A")
public class HelloWorld{
	
	@RequestMapping("/B")
	public String helloworld(){
	}
}
  • 9.@AutoWired
    这个注解的英文直译是“自动装配”,“自动注入”

  • 10.Qualifer
    这个注解是用来辅助@AutoWired注解来使用的。
    用于当@AutoWired在注入父类属性时有两个或以上实现类时,指定要用哪个。

👆上面在@AutoWired注解里面说了,当实现类有多个的时候,它会自动去找和它名称相同的实现类(首字母小写),但如果我们不想这样,就可以加一个@Qualifer注解来指定具体要注入哪一个实现类。

@Autowired
@Qualifier("menuService1")
private IMenuService menuService;
  • 11.@Resource

9.Spring之@Indexed注解

Spring包org.springframework.stereotype下,除了@Component、@Controller、@Service、@Repository外,在5.0版本中新增了@Indexed注解。

应用中使用<context:component-scan />或@ComponentScan扫描的package包含的类越来越多的时候,Spring启动时模式注解解析时间就会变得越长。

@Indexed注解的引入正是为了解决这个问题,项目编译打包时,会在自动生成META-INF/spring.components文件,文件包含被@Indexed注释的类的模式解析结果。当Spring应用上下文进行组件扫描时,META-INF/spring.components会被org.springframework.context.index.CandidateComponentsIndexLoader读取并加载,转换为CandidateComponentsIndex对象,此时组件扫描会读取CandidateComponentsIndex,而不进行实际扫描,从而提高组件扫描效率,减少应用启动时间。

10.Spring支持几种作用域

当通过Spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下5种作用域:

  • singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例
  • prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例
  • request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效
  • session:对于每次HTTP Session,使用session定义的Bean豆浆产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效
  • globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效

其中比较常用的是singleton和prototype两种作用域。对于singleton作用域的Bean,每次请求该Bean都将获得相同的实例。容器负责跟踪Bean实例的状态,负责维护Bean实例的生命周期行为;如果一个Bean被设置成prototype作用域,程序每次请求该id的Bean,Spring都会新建一个Bean实例,然后返回给程序。在这种情况下,Spring容器仅仅使用new 关键字创建Bean实例,一旦创建成功,容器不在跟踪实例,也不会维护Bean实例的状态。

如果不指定Bean的作用域,Spring默认使用singleton作用域。Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。

设置Bean的基本行为,通过scope属性指定,该属性可以接受singleton、prototype、request、session、globlesession5个值,分别代表以上5种作用域

11.BeanFactory和ApplicationContext的区别

Spring 框架带有两个 IOC 容器—— BeanFactory和ApplicationContext。BeanFactory是 IOC 容器的最基本版本,ApplicationContext扩展了BeanFactory的特性。

Spring容器最基本的接口就是BeanFactory。BeanFactory负责配置、创建、管理Bean,它有一个子接口ApplicationContext,也被称为Spring上下文,容器同时还管理着Bean和Bean之间的依赖关系。

在这里插入图片描述
BeanFactory负责读取Bean配置文档,管理Bean的加载,实例化,维护Bean之间的依赖关系,负责Bean的声明周期。

ApplicationContext除了提供上述BeanFactory所能提供的功能之外,还提供了更完整的框架功能:

  • a. 国际化支持
  • b. 资源访问:Resource rs = ctx. getResource(“classpath:config.properties”), “file:c:/config.properties”
  • c. 事件传递:通过实现ApplicationContextAware接口。

BeanFactory按需加载 Bean,而ApplicationContext在启动时加载所有 Bean。

因此, BeanFactory与 ApplicationContext相比是轻量级的

ApplicationContext以更加面向框架的风格增强了BeanFactory,并提供了一些适用于企业应用程序的特性。

  • 默认初始化所有的Singleton,也可以通过配置取消预初始化。

  • 继承MessageSource,因此支持国际化。

  • 资源访问,比如访问URL和文件。

  • 事件传播特性,即支持aop特性。

  • 同时加载多个配置文件。

  • 以声明式方式启动并创建Spring容器。

ApplicationContext:是IOC容器另一个重要接口, 它继承了BeanFactory的基本功能, 同时也继承了容器的高级功能,如:MessageSource(国际化资源接口)、ResourceLoader(资源加载接口)、ApplicationEventPublisher(应用事件发布接口)等。

12.BeanFactoryPostProcessor的理解

在Spring框架中,BeanFactoryPostProcessor是一个非常重要的接口,它允许我们在Spring容器实例化任何其他Bean之前,修改应用上下文的Bean定义。这为我们提供了一种强大的方式来改变Spring应用上下文的Bean配置。

BeanFactoryPostProcessor的主要方法是postProcessBeanFactory(ConfigurableListableBeanFactory),这个方法在所有的Bean定义被加载,但是还没有Bean被实例化之前调用。这使得BeanFactoryPostProcessor可以读取配置元数据,并可能在Bean实例化之前改变它。

举个例子,假设我们有一个名为"dataSource"的Bean,它的一些属性(如URL,用户名和密码)需要从属性文件中读取。我们可以创建一个实现了BeanFactoryPostProcessor接口的类,然后在postProcessBeanFactory方法中,读取属性文件,并设置dataSource Bean的属性。

public class DataSourceBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition("dataSource");
        MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();

        // 读取属性文件
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream("datasource.properties"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // 设置dataSource bean的属性
        propertyValues.addPropertyValue("url", properties.getProperty("url"));
        propertyValues.addPropertyValue("username", properties.getProperty("username"));
        propertyValues.addPropertyValue("password", properties.getProperty("password"));
    }
}

在这个例子中,我们首先获取了名为"dataSource"的Bean定义,然后读取了属性文件,并设置了dataSource Bean的属性。这样,当Spring容器实例化dataSource Bean时,它会使用我们在BeanFactoryPostProcessor中设置的属性值。

BeanFactoryPostProcessor的优点是它提供了一种在Spring容器实例化Bean之前修改Bean定义的方式。这使得我们可以根据需要改变Bean的配置,例如,我们可以根据不同的环境(开发,测试,生产)使用不同的Bean配置。

然而,BeanFactoryPostProcessor也有一些缺点。首先,它的使用相对复杂,需要深入理解Spring的工作原理。其次,如果使用不当,可能会导致应用上下文的配置混乱,难以管理和维护。

总的来说,BeanFactoryPostProcessor是一个强大的工具,但是需要谨慎使用。在大多数情况下,我们可以通过其他方式(如使用Spring的@Value注解,或者使用Spring的环境抽象)来达到同样的目的,而无需使用BeanFactoryPostProcessor。

13.BeanPostProcessor的理解

这是一个在工程中看到的接口,从字面上 BeanPostProcessor 的意思就是 Bean 的后置处理器。主要作用就是帮助我们在Bean实例化之后,初始化前后做一些事情。

Spring 会自动从它的所有的 Bean 定义中检测 BeanPostProcessor 类型的Bean 定义,然后实例化它们,再将它们应用于随后创建的每一个Bean实例。

在 Bean 实例的初始化方法回调之前调用 BeanPostProcessor 的postProcessBeforeInitialization 的方法(进行 Bean 实例属性的填充)。

在 Bean 实例的初始化方法回调之后调用 BeanPostProcessor 的postProcessAfterInitialization 的方法(可以进行 Bean 实例的代理封装)

如果我们想在Spring容器中完成Bean实例化、配置以及其他初始化方法前后要添加一些自己逻辑处理。我们需要定义一个或多个BeanPostProcessor接口实现类,然后注册到Spring IoC容器中。

Spring中Bean的实例化过程图示:
在这里插入图片描述

各个注意事项:

接口中的两个方法都要将传入的Bean返回,而不能返回null,如果返回的是null那么我们通过getBean方法将得不到目标。
BeanFactory和ApplicationContext对待bean后置处理器稍有不同。ApplicationContext会自动检测在配置文件中实现了BeanPostProcessor接口的所有Bean,并把它们注册为后置处理器,然后在容器创建Bean的适当时候调用它,因此部署一个后置处理器同部署其他的Bean并没有什么区别。而使用BeanFactory实现的时候,Bean 后置处理器必须通过代码显式地去注册,在IoC容器继承体系中的ConfigurableBeanFactory接口中定义了注册方法

另外,不要将BeanPostProcessor标记为延迟初始化。因为如果这样做,Spring容器将不会注册它们,自定义逻辑也就无法得到应用。假如你在元素的定义中使用了’default-lazy-init’属性,请确信你的各个BeanPostProcessor标记为’lazy-init=“false”’。

14.Spring事务的4个特性(ACID)

事务(Transaction): 事务一般是指数据库事务, 是基于关系型数据库(RDBMS)的企业应用的重要组成部分。在软件开发领域,事务扮演者十分重要的角色,用来确保应用程序数据的完整性和一致性。也就是要么完全执行,要么完全不执行。
事务允许我们将几个或一组操作组合成一个要么全部成功、要么全部失败的工作单元。如果事务中的所有操作都执行成功,那就是最终我们想要的正确的结果。但如果事务中的任何一个操作失败,那么事务中所有的操作都会被回滚,已经执行成功操作也会被完全还原,结果就跟操作之前一样.

事务管理的意义:保证数据操作的完整性。

事务的四个特性, 也就是ACID分别是: 原子性、一致性、隔离性 和 持久性

  • 1.原子性(Atomicity)
    事务的整个操作是一个整体,不可以分割,要么全部成功,要么全部失败。

  • 2.一致性(Consistency)
    事务必须保证数据库从一个一致性状态变到另一个一致性状态,一致性和原子性是密切相关的。

  • 3.隔离性(Isolation)
    一个事务的执行不能被其它事务干扰,即一个事务内部的操作及使用的数据对并发的其它事务是隔离的,并发执行的各个事务之间不能互相打扰。

  • 4.持久性(Durability)
    持久性也称为永久性,指一个事务一旦提交,它对数据库中数据的改变就是永久性的,后面的其它操作和故障都不应该对其有任何影响。

15.事务的隔离级别

事务隔离级别是对事务 4 大特性中隔离性的具体体现,使用事务隔离级别可以控制并发事务在同时执行时的某种行为。

Spring 中的事务隔离级别比 MySQL 中的事务隔离级别多了一种,它包含的 5 种隔离级别分别是:

  • Isolation.DEFAULT:默认的事务隔离级别,以连接的数据库的事务隔离级别为准。
  • Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。
  • Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。
  • Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL 数据库默认的事务隔离级别)。
  • Isolation.SERIALIZABLE:串行化,可以解决所有并发问题,但性能太低。

需要注意是 Spring 是事务隔离级别是建立在连接的数据库支持事务的基础上的 ,如果 Spring 项目连接的数据库不支持事务(或事务隔离级别),那么即使在 Spring 中设置了事务隔离级别,也是无效的设置
在这里插入图片描述
脏读:一个事务读取到了另一个事务修改的数据之后,后一个事务又进行了回滚操作,从而导致第一个事务读取的数据是错误的。
不可重复读:一个事务两次查询得到的结果不同,因为在两次查询中间,有另一个事务把数据修改了。
幻读:一个事务两次查询中得到的结果集不同,因为在两次查询中另一个事务有新增了一部分数据。

16.事务的传播行为

Spring的TransactionDefinition类中定义了7中事务传播类型,代码如下:

public interface TransactionDefinition {
    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;
    //......
}

我们先来假设一个场景
在 ServiceA 中方法 A() 调用 ServiceB 中方法 B()。
Spring 的事务传播行为就是解决方法之间的事务传播的。
基本方法调用场景如下:

方法A有事务,方法B也有事务
方法A有事务,方法B没有事务
方法A没有事务,方法B有事务
方法A没有事务,方法B也没有事务

public class ServiceA{

	void methodA(){
		ServiceB.methodB();
	}
}

public class ServiceB{

	void methodB(){
	
	}
}

    1. PROPAGATION_REQUIRED
      支持当前事务,如果当前没有事务,就新建一个事务。他也是Spring提供的默认事务传播行为,适合绝大数情况。

如果A方法有事务,那么B方法就使用A方法的事务。
如果A方法没有事务,那么B方法就创建一个新事物。

    1. PROPAGATION_SUPPORTS
      支持当前事务,如果当前没有事务,就以非事务方式执行。

如果A方法有事务,那么B方法就使用A方法的事务。
如果A方法没有事务,那么B方法就不使用事务的方式执行。

    1. PROPAGATION_MANDATORY
      支持当前事务,如果当前没有事务,就抛出异常。

如果A方法有事务,那么A方法就使用A方法事务。
如果A方法没有事务,那么就抛出异常。

该事务传播行为要求A方法必须以事务的方式运行

    1. PROPAGATION_REQUIRES_NEW
      新建事务,如果当前存在事务,把当前事务挂起。

如果A方法有事务,就把A方法的事务挂起,B方法新创建一个事务。
如果A方法没有事务,那么B方法就创建一个新事务。

    1. PROPAGATION_NOT_SUPPORTED
      以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

如果A方法有事务,那么就把A方法的事务挂起,B方法以非事务的方式执行。
如果A方法没有事务,那么B也不使用事务执行。

    1. PROPAGATION_NEVER
      以非事务方式执行,如果当前存在事务,则抛出异常。

如果方法A有事务,那么就抛出异常。
如果方法A没有事务,那么B方法就以非事务的方式运行。

跟 3. PROPAGATION_MANDATORY 事务传播行为相反。

    1. PROPAGATION_NESTED
      如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的。

如果A方法有事务,那么B方法就在A方法的事务中使用嵌套事务。
如果A方法没有事务,那么方法B就新创建一个事务。
嵌套事务
嵌套事务是使用数据库的SavePoint(事务保存点)。需要底层数据库的支持。

  • 只读事务(Readonly Transaction)
    Spring 为了忽略那些不需要事务的方法,比如读取数据,这样可以有效的提高一些性能。
    事务超时 (Transaction Timeout)
  • 为了解决事务执行时间太长,消耗太多资源的问题,可以设置一个超时时间。如果该事务支持超过设置的时间,就回滚该事务。

17.Spring事务实现方式

  • 编程式事务管理: 通过编程的方式管理事务,这种方式带来了很大的灵活性,但很难维护

  • 声明式事务管理: 将是我管理和业务代码分离,开发者只需要通过注解或者xml配置管理事务.

18.事务注解的本质是什么?

@Transactional 这个注解仅仅是一些(和事务相关的)元数据,在运行时被事务基础设施读取消费,并使用这些元数据来配置Bean的事务行为。 大致来说具有两方面功能,一是表明该方法要参与事务,二是配置相关属性来定制事务的参与方式和运行行为

声明式事务主要是得益于Spring AOP。使用一个事务拦截器,在方法调用的前后/周围进行事务性增强(advice),来驱动事务完成。

@Transactional注解既可以标注在类上,也可以标注在方法上。当在类上时,默认应用到类里的所有方法。如果此时方法上也标注了,则方法上的优先级高.另外注意方法一定要是public的.

Java 注解(Annotation)又称为 Java 标注,是 Java5开始支持加入源代码的特殊语法元数据。Java 语言中的类、方法、变量、参数和包等都可以被标注。Java 标注可以通过反射获取标注的内容。在编译器生成class文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容。
注解是一种用于做标注的“元数据”,什么意思呢?你可以将注解理解为一个标签,这个标签可以标记类、方法、变量、参数和包

19.请求转发或请求重定向

在返回视图的时候,我们不仅仅可以返回视图,还可以实现跳转,跳转的方式有两种:

  • redirect:请求重定向
  • forward:请求转发

请求转发和请求重定向区别:

请求重定向将请求重新定位到资源,而请求转发则是在服务器内部进行转发
请求重定向地址栏发生变化,请求转发地址栏没有发生变化‘
请求重定向与直接访问新地址效果一样,不存在原来的外部资源不可访问;
请求转发服务器端转发有可能造成外部资源不能访问的情况

20.Spring MVC是什么?

什么是Spring MVC?

Spring MVC 全名是 Spring Web MVC,简称 Spring MVC或者 Spring Web.
Spring MVC 是基于Servlet API的web框架,从一开始Spring框架是包含Spring MVC部分,也就是说Spring MVC是Spring 框架的一部分。

Spring MVC 各层的职责如下:
Model:负责对请求进行处理,并将结果返回给 Controller;
View:负责将请求的处理结果进行渲染,展示在客户端浏览器上;
Controller:是 Model 和 View 交互的纽带;主要负责接收用户请求,并调用 Model 对请求处理,然后将 Model 的处理结果传递给 View。

Spring MVC 的常用组件:
在这里插入图片描述
在这里插入图片描述

21.Spring和SpringMVC的关系

1.Spring和SpringMVC是父子容器关系。

2.Spring整体框架的核心思想是容器,用来管理Bean的生命周期,而一个项目中会包含很多容器,并且它们分上下层关系,目前最常用的一个场景是在一个项目中导入Spring和SpringMVC框架,而Spring和SpringMVC其实就是两个容器,Spring是父容器,SpringMVC是子容器,Spring父容器中注册的Bean对SpringMVC子容器是可见的,反之则不行。

3.按照官方文档推荐,根据不同的业务模块来划分不同的容器中注册不同的Bean,SpringMVC主要就是为我们构建Web应用程序,那么SpringMVC子容器用来注册Web组件的Bean,如控制器、处理器映射、视图解析器等。而Spring用来注册其他Bean,这些Bean通常是驱动应用后端的中间层和数据层组件。

22.DelegatingFilterProxy的作用

在这里插入图片描述

DelegatingFilterProxy类存在于Spring-Web包中,其作用就是一个Filter的代理,用这个类的好处是可以通过Spring容器来管理Filter的生命周期,可以通过Spring注入的形式,来代理一个Filter执行,如Shiro;有上图我们可以看到,DelegatingFilterProxy类继承GenericFilterBean,间接实现了Filter这个接口,故而该类属于一个过滤器。那么就会有实现Filter中init、doFilter、destroy三个方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值