Spring核心问题解答

news2024/11/24 22:36:38

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三个方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/770841.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python+Qt窗体或Django网页支付宝收款码-扫码付款实例

程序示例精选 PythonQt窗体或Django网页支付宝收款码-扫码付款实例 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<PythonQt窗体或Django网页支付宝收款码-扫码付款实例>>编写代…

智安网络|保护数据资产:不同行业下的数据安全建设策略

在当今数字化时代&#xff0c;数据安全已经成为各行各业无法忽视的重要议题。保持良好网络卫生习惯并及时了解不断变化的网络威胁是企业中每个人的责任。企业、政府机构、医疗机构、金融机构以及其他组织和行业都面临着日益复杂和频繁的网络安全威胁。为了有效应对这些威胁&…

22.代理模式

代理模式 二十三种设计模式中的一种&#xff0c;属于结构型模式。它的作用就是通过提供一个代理类&#xff0c;在调用目标方法的时候&#xff0c;不再是直接对目标方法进行调用&#xff0c;而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦…

自定义表单设计:办公协同效率提高新工具!

为了提高工作效率和表格制作效率&#xff0c;低代码开发平台成为广大用户喜爱的主流平台。因为它包含了非常多的功能内容&#xff0c;在快节奏发展的社会环境中可以满足日益增长的业务需求&#xff0c;是助力通信业、医疗、高校、物流等众多行业办公效率提质增效的得力助手。其…

解决MySQL中分页查询时多页有重复数据,实际只有一条数据的问题

0 前言 有一个离奇的BUG&#xff0c;在查询时&#xff0c;第一页跟第二页有一个共同的数据。有的数据却不显示。 后来发现是在SQL排序时没用主键排序。 解决&#xff1a;使用主键排序 以下是我准备的举例&#xff0c;可以自己试试。 1 数据准备 SET NAMES utf8mb4; SET FORE…

JVM系列(7)——java内存模型

一、什么是JMM 一种抽象的规范。每个JVM 的实现都要遵守这样的规范&#xff0c;这样才能保证Java程序能够“一次编写&#xff0c;到处运行”。 内存模型描述了程序中各个变量&#xff08;实例域、静态域和数组元素&#xff09;之间的关系&#xff0c;以及在实际计算机系统中将…

华为产品测评官-开发者之声 - ModelArts 真实体验感想

华为产品测评官&#xff0d;开发者之声 - ModelArts 真实体验感想 我先是在6月17日参加了华为在深圳举办的开发者大会&#xff0c;后面看到群里发的"2023华为产品测评官&#xff0d;开发者之声"活动&#xff0c;简单看了一下体验活动的具体事情&#xff0c;感觉好玩…

mysql 第六章

目录 1.子查询 2.exists 3.as 别名 4.视图 5.null 6.连接查询 7.总结 1.子查询 2.exists 3.as 别名 4.视图 5.null 6.连接查询 7.总结 对 mysql 数据库的查询&#xff0c;除了基本的查询外&#xff0c;有时候需要对查询的结果集进行处理。

【Policy】使用 InitializingBean 实现策略时如何避免AOP失效

使用InitializingBean实现策略模式 参考策略模式示例中的第一种实现方式.代码demo项目 不同的注入方式对AOP注解的影响 部分策略代码及测试代码 public interface TraditionOrderService extends InitializingBean {// ... } Service public class TraditionOrderServiceIm…

原生信息流广告APP应用内增收及计费模式

比起传统的广告宣传&#xff0c;信息流最大的优势就在于流量的庞大。与此同时&#xff0c;多样化的信息流广告形式和精准的定向&#xff0c;还可以帮助广告主准确获取意向流量。此外&#xff0c;它的广告形式不强迫推送&#xff0c;因此也受到了广泛用户的支持和青睐。 原生信…

Java:输入与输出

目录 输入输出args 输入Scanner 输入格式化输出文件输入与输出 输入输出 args 输入 利用main函数中的参数args&#xff0c;当然也可以起别的名字。其他语言也是一样的。输入时空格分隔。 args的作用&#xff1a;在程序启动时可以用来指定外部参数 Scanner 输入 需要import j…

26.JavaWeb-SpringSecurity安全框架

1.SpringSecurity安全框架 Spring Security是一个功能强大且灵活的安全框架&#xff0c;它专注于为Java应用程序提供身份验证&#xff08;Authentication&#xff09;、授权&#xff08;Authorization&#xff09;和其他安全功能。Spring Security可以轻松地集成到Spring框架中…

Docker高级——网络配置

Docker网络 默认网络 安装 Docker 以后&#xff0c;会默认创建三种网络&#xff0c;可以通过 docker network ls 查看 [roottest ~]# docker network ls NETWORK ID NAME DRIVER SCOPE 6f24f7cbfa10 bridge bridge local 2dc34a1c0f04 host host…

微信号长期没有使用或被回收?

7月17日&#xff0c;有网友表示自己之前申请了一个辅助账号作为树洞使用&#xff0c;如今登录的时候&#xff0c;弹出文字提示称“该微信号由于长期没有使用已被回收”。 腾讯客服回应表示&#xff0c;正常登录使用的微信号不会被系统回收&#xff0c;但对于长时间未登录的微信…

简用前后端的JSON格式注解:@DateTimeFormat、@JsonFormat、@JsonProperty

JsonFormat 【后端到前端】 在实体类属性上面使用JsonFormat注解了&#xff0c;要注意的是&#xff0c;它只会在声明返回类型为json时&#xff0c;比如使用ResponseBody返回json数据的时候&#xff0c;才会返回格式化的yyyy-MM-dd HH:mm:ss时间&#xff0c;如果直接使用Syste…

【分布式系统案例课】查询服务设计、计数栈选型、总结

查询服务设计 数据获取路径 两个问题考虑&#xff1a; 1、老数据归档的问题。 如果所有分钟小时级的数据一直存在这个DB当中&#xff0c;那么DB的存储空间会被不断的消耗&#xff0c;性能也会不断的下降。所以一旦小时天月的数据聚合完成&#xff0c;我们就可以将一些老的原始…

java: 错误: 不支持发行版本 5 java: 错误: 不支持发行版本8 java: 错误: 不支持发行版本17

&#x1f353;&#x1f353;原因 该错误表示你使用的Java编译器不支持Java 5版本的发行。Java版本的发行是根据不同的功能和语言变化来区分的。 要解决这个问题&#xff0c;你可以尝试以下几种方法&#xff1a; 检查编译器配置&#xff1a;确保你的IDE或编译器已正确配置为使…

Segment Tree 线段树算法(java)

线段树算法 Segment Tree 线段树算法代码演示 蓄水池算法 Segment Tree 线段树算法 什么是线段树算法&#xff1a; 线段树&#xff08;Segment Tree&#xff09;是一种基于树结构的数据结构&#xff0c;用于解决区间查询问题&#xff0c;例如区间最大值、最小值、区间和等。线段…

【数据结构】图解八大排序(下)

文章目录 一、前言二、快速排序1. hoare 版2. 挖坑法3. 前后指针法4. 快排的非递归实现5. 时空复杂度分析 三、归并排序1. 递归实现2. 非递归实现 四、计数排序 一、前言 在上一篇文章中&#xff0c;我们已经学习了五种排序算法&#xff0c;还没看过的小伙伴可以去看一下&…

C语言 —— 浮点类型详解及 IEEE754 规定

【C语言趣味教程】(3) 浮点类型&#xff1a;单精度浮点数 | 双精度浮点型 | IEEE754 标准 &#x1f517; 《C语言趣味教程》&#x1f448; 猛戳订阅&#xff01;&#xff01;&#xff01; ​—— 热门专栏《维生素C语言》的重制版 —— &#x1f4ad; 写在前面&#xff1a;这是…