Spring 常见面试题

1、Spring概述

1.1、Spring是什么?

  1. Spring是一个轻量级Java开发框架,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题
  2. Spring最根本的使命是解决企业级应用开发的复杂性,即简化Java开发。
  3. 这些功能的底层都依赖于它的两个核心特性,也就是依赖注入(dependency injection,DI)和面向切面编程(aspect-oriented programming,AOP)
1.2 Spring的优缺点

优点:

  1. 方便解耦,简化开发 :Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护,交给Spring管理。
  2. AOP编程的支持: Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。
  3. 声明式事务的支持:只需要通过配置就可以完成对事务的管理,而无需手动编程。
  4. 方便程序的测试: Spring对Junit4支持,可以通过注解方便的测试Spring程序。
  5. 方便集成各种优秀框架: Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。
  6. 降低JavaEE API的使用难度:Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低

缺点

  1. Spring明明一个很轻量级的框架,却给人感觉大而全
  2. Spring依赖反射,反射影响性能
  3. 使用门槛升高,入门Spring需要较长时间
1.3 Spring的组成模块

Spring 总共大约有 20 个模块, 由 1300 多个不同的文件构成。 而这些组件被分别整合在核心容器(Core Container) 、 AOP(Aspect Oriented Programming)和设备支持(Instrmentation) 、数据访问与集成(Data Access/Integeration) 、 Web、 消息(Messaging) 、 Test等 6 个模块中。 以下是 Spring 5 的模块结构图:
img

  • **spring core:**提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。
  • **spring beans:**提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean。
  • **spring context:**构建于 core 封装包基础上的 context 封装包,提供了一种框架式的对象访问方法。
  • **spring jdbc:**提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBC。
  • **spring aop:**提供了面向切面的编程实现,让你可以自定义拦截器、切点等。
  • **spring Web:**提供了针对 Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。
  • **spring test:**主要为测试提供支持的,支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试。
1.4 Spring中应用的设计模式
  • 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
  • 单例模式:Bean默认为单例模式。
  • 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
  • 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
  • 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。

2、Spring IOC

2.1、什么是IOC

IOC,Inversion of Control,控制反转,指将对象的控制权转移给Spring框架,由 Spring 来负责控制对象的生命周期(比如创建、销毁)和对象间的依赖关系。

​ 最直观的表达就是,以前创建对象的时机和主动权都是由自己把控的,如果在一个对象中使用另外的对象,就必须主动通过new指令去创建依赖对象,使用完后还需要销毁(比如Connection等),对象始终会和其他接口或类耦合起来。而 IOC 则是由专门的容器来帮忙创建对象,将所有的类都在 Spring 容器中登记,当需要某个对象时,不再需要自己主动去 new 了,只需告诉 Spring 容器,然后 Spring 就会在系统运行到适当的时机,把你想要的对象主动给你。也就是说,对于某个具体的对象而言,以前是由自己控制它所引用对象的生命周期,而在IOC中,所有的对象都被 Spring 控制,控制对象生命周期的不再是引用它的对象,而是Spring容器,由 Spring 容器帮我们创建、查找及注入依赖对象,而引用对象只是被动的接受依赖对象,所以这叫控制反转。

看不懂,没有关系,帅哥公主请看代码:

一般情况:

public class Test {public static void main(String[] args) {A a = new A();//a.b = new B();  !!! 如果我们没有这句代码,运行是会报错的。a.b.shout();}
}
class A{B b; //这相当于是一个指针,但是没有指向任何对象
}
class B{void shout(){System.out.println("我是B");}
}

Spring情况

@Component
public class A {@Autowiredpublic B b;
}
@Component
public class B {public void shout(){System.out.println("我是B");}
}
public class MyTest {@Testpublic void test1(){ApplicationContext context =  new ClassPathXmlApplicationContext("beans.xml");A a = context.getBean("a", A.class);//这个地方,我们 new B() 了吗,没有吧,但是可以直接使用//因为 spring 容器帮我们创建了,也处理了二者之间的依赖关系。我们直接用就行了a.b.shout();}
}

2.2、什么是DI

IoC 的一个重点就是在程序运行时,动态的向某个对象提供它所需要的其他对象,这一点是通过DI(Dependency Injection,依赖注入)来实现的,即应用程序在运行时依赖 IoC 容器来动态注入对象所需要的外部依赖。而 Spring 的 DI 具体就是通过反射实现注入的,反射允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性

能注入的数据类型

  • 基本类型和String。
  • 其他bean类型(在配置文件中或者注解配置过的bean)。
  • 复杂类型/集合类型。

注入的方式

  • 使用构造函数提供。
  • 使用set方法提供。
  • 使用注解提供。
  • 接口注入(不推荐)

2.2.1 构造函数注入

顾名思义,就是使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入。
标签中的属性:
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型。
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始。
name:用于指定给构造函数中指定名称的参数赋值。
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
优势
在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
弊端
改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。

<!--默认构造器方式-->
<bean id="user" class="com.kuang.pojo.User"><property name="name" value="张三"/>
</bean><!--通过有参构造创建对象。方式一:下标赋值-->
<bean id="user" class="com.kuang.pojo.User"><constructor-arg index="0" value="jerry"/>
</bean><!--通过有参构造创建对象。方式二:类型创建,不建议使用-->
<bean id="user" class="com.kuang.pojo.User"><constructor-arg type="java.lang.String" value="jarry"/>
</bean><!--通过有参构造创建对象。方式三:通过参数名,推荐使用-->
<bean id="user" class="com.kuang.pojo.User"><constructor-arg name="name" value="jarry"/><constructor-arg name="birthday" ref="now"/>
</bean>
<!-- 配置一个日期对象 -->
<bean id="now" class="java.util.Date"></bean>

2.2.2 set方法注入

顾名思义,就是在类中提供需要注入成员的 set 方法。

set方法注入【常用】
标签的属性
name:用于指定注入时所调用的set方法名称
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象。
优势:
创建对象时没有明确的限制,可以直接使用默认构造函数。
弊端:
如果有某个成员必须有值,则获取对象是有可能set方法没有执行。

<bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2"><property name="name" value="tom" ></property><property name="age" value="23"></property><property name="birthday" ref="now"></property>
</bean>

2.2.3 注解注入

//等价于 <bean id="user" class="com.x.pojo.User"/>
@Component //组件
@Scope("prototype")
public class User {//等价于 <property name="name" value="嘿嘿"/>
//    @Value("嘿嘿")public String name;//放set方法也行@Value("嘿嘿")public void setName(String name) {this.name = name;}
}

@Component注解用来将类声明为容器的组件,当容器启动后,通过扫描,会生成对应的对象交给容器托管。

@Value注解用来给属性赋值,可以直接放在属性上,也可以放在对应的set方法上

@Scope注解声明 类的作用域

2.3 自动装配

自动装配针对的是引入型对象

自动装配和依赖注入有什么联系与区别???

让我们来看看 Gpt 的回答

自动装配(Autowired)和依赖注入(Dependency Injection)在Spring框架中是相关概念,它们实现了相同的目标,即将一个对象的依赖关系注入到另一个对象中。然而,它们之间存在一些联系和区别:

联系:

  1. 实现方式: 自动装配是一种依赖注入的方式,它可以通过@Autowired注解(基于注解的自动装配)或者在XML配置文件中使用<property>元素(基于XML的自动装配)来实现。
  2. 目标: 自动装配和依赖注入的共同目标是减少硬编码,提高代码的灵活性和可维护性,同时降低了对象之间的耦合度。
  3. 由Spring容器管理: 在两种情况下,Spring容器负责管理Bean之间的关系,确保依赖的Bean在需要的时候能够被正确注入到目标Bean中。

区别:

  1. 显式配置 vs 隐式配置: 依赖注入可以通过显式配置(在XML配置文件或Java配置类中明确指定依赖关系)或者隐式配置(使用@Autowired注解进行自动装配)来实现。自动装配是一种依赖注入的方式,它是一种自动化的、隐式的依赖注入。
  2. 灵活性: 自动装配提供了更大的灵活性,因为它不需要在配置文件中显式地指定每个Bean的依赖关系。相反,Spring容器会根据自动装配规则自动地完成依赖注入,这样就减少了配置的工作量,同时也更容易适应变化。
  3. 粒度: 依赖注入的粒度更细,可以对一个类的特定属性进行注入,也可以对构造方法进行注入,而自动装配通常是针对整个类进行的,它会自动装配类中所有符合条件的属性。

综上所述,自动装配是依赖注入的一种实现方式,它通过隐式的方式完成依赖注入,提高了配置的便利性和灵活性。依赖注入是一种更广泛的概念,它包括了显式配置和隐式配置两种方式,而自动装配则是依赖注入的一种特殊形式。

public class People {private Dog dog; //用来测试 xml 文件private Cat cat; // 用来测试 注解
}
public class Cat {String name;
}
public class Dog {private String name;
}

放在此处,下面要用到

2.3.1 基于XML配置文件

自动装配是spring满足bean依赖的一种方式。spring会通过在上下文寻找匹配,实现bean的自动装配。

测试文件

public void test1(){ApplicationContext context =  new ClassPathXmlApplicationContext("beans.xml");People people = context.getBean("people", People.class);System.out.println(people.getDog().getName());
}
ByName:

使用 byName 它会根据 people对应的属性名去匹配 id 名与之相同的 bean

(注意:它匹配id之后会进行一个类型的检查,总不能给 cat属性装配一个 dog 对象吧)

<bean id="dog" class="com.x.pojo.Dog"><property name="name" value="dog"/>
</bean>
<bean id="dog2" class="com.x.pojo.Dog"><property name="name" value="dong2"/>
</bean>
<bean id="people" class="com.x.pojo.People" autowire="byName"/>

输出结果为 : dog ;

总结: byName 根据 name 去匹配 ,如果 没有匹配到 就会报错

ByType :

使用 byType 它会根据 people对应的属性的类型名去匹配 class属性对应与之对应的 bean

<bean id="dog1" class="com.x.pojo.Dog"><property name="name" value="dog"/>
</bean>
<bean id="people" class="com.x.pojo.People" autowire="byType"/>

输出结果: dog

总结:byType的根据属性去匹配,如果有多个 匹配 的 bean 则会报错

2.3.2 基于注解

xml文件的形式 是在 标签使用 autowired属性

注解则有两种常用的: @Autowired@Resource

使用方法很简单:我们直接再对应的属性上或者 set方法上添加注解就行

public class People {@Autowired //注意 Resource 不能用在 构造方法上People(){}@Autowired//@Resourceprivate Cat cat;private String name;public Cat getCat() {return cat;}//@Resource@Autowiredpublic void setCat(Cat cat) {this.cat = cat;}

二者还是有区别的

1、来源不同:

  • @Autowired 是 Spring 定义的注解
  • @Resource 是 Java 定义的注解(记得导入相应的依赖)

2、依赖查找顺序不同

  • @Autowired 是先根据类型(byType)查找,如果存在多个 Bean 再根据名称(byName)进行查找
  • @Resource 是先根据名称查找,如果(根据名称)查找不到,再根据类型进行查找

3、参数不同

  • @Autowired 只支持设置一个 required 的参数
    • 可以搭配@Qualifier注解,但是一旦指定名字且没有对应的id,也不会进行type匹配了
  • @Resource支持很多个

4、用法不同

  • @Autowired 可以用在 属性,set方法,构造器方法
  • @Resource不能用在构造方法

2.4 使用注解实现bean的托管

我们发现前面我们每声明一个类都要在 xml 文件中添加一个对应的标签,当类比较多时,这个工作量就会很大。有没有别的方法呢?

这就要提到我们的@Component注解了。使用这个注解,我们要在配置文件中进行声明

<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.x"/>
<!--开启注解配置-->
<context:annotation-config/> 
<context:component-scan base-package="com.x"/>

当spring容器启动后,他就会自动去扫描 com.x包下所有添加了

@Component、@controller、@service、@repository注解的类,将其看成bean交给容器托管。

我们在类上添加一个 @Component 注解,就可以将类标记为Spring中的bean了

@Component //组件 
public class User {//等价于 <property name="name" value="嘿嘿"/>public String name;@Value("嘿嘿")public void setName(String name) {this.name = name;}
}

基于 @Component 扩展的注解还有三个: @controller、@service、@repository

1@controller:   controller控制器层(注入服务)
2@service :      service服务层(注入dao)
3@repository :  dao持久层(实现dao访问)
4@component:  标注一个类为Spring容器的Bean,(把普通pojo实例化到spring容器中,相当于配置文件中的<bean id="" class=""/>

这里不具体讲四者有什么区别,只是希望大家简单的知道 @service 标记的类,说说这个类是service层的实现就够了

2.5 配置类实现

JavaConfig是Spring的一个子项目 ,在Spring4之后,它变成了核心功能(SpringBoot中推荐使用这种方法)

配置类

myConfig2.class

@Configuration
@ComponentScan(basePackages = "com.x.pojo")
public class myConfig2 {}

myConfig.class

package com.x.config;
@Configuration //它也会被spring托管,它的本质也是一个组件
// @Configuration 代表他是一个配置类,就和beans.xml一样
@Import(myConfig2.class)
public class myConfig {//注册一个bean,就相当于之前写的一个bean标签//这个方法的名字,就相当于id//返回值,就相当于 class属性@BeanUser getUser(){return new User();//返回要注入bean的对象}
}

@Configration 注解:声明当前类是一个配置类,相当于 Spring 中的一个 XML 文件
@Bean 注解:作用在方法上,声明当前方法的返回值是一个 Bean
配置类的加载

我们发现此处用到了一个新标签 @Bean,我们对比最开始用到的

@Component

2.4.1 @Bean 和 @Component 的区别

@Component(和@Service和@Repository)用于自动检测和使用类路径扫描自动配置bean。注释类和bean之间存在隐式的一对一映射(即每个类一个bean)。
@Bean用于显式声明单个bean,而不是让Spring像上面那样自动执行它。它将bean的声明与类定义分离,并允许您精确地创建和配置bean

该怎么理解这段话呢?

首先 我们知道 @Component 是放在类上的,这样就可以将这个类标记为bean,并交给spring托管。我们完完全全就是在 这个类的 “源码”上进行的配置。那如果我们引入的是第三方的类,不知道源码,那我们怎么用@Component注释呢?这个时候就要用 @Bean了。也就是所谓的声明与类定义分离。我们不需要知道类是怎么定义的。我们只需要声明一个这个类的对象?

那可能又有疑问了,既然是声明对象为什么不这样写呢?

@Configuration
public class AppConfig {@BeanUser user;
}

现在我们去看@Bean的源码:这是源码的第一句话

Indicates that a method produces a bean to be managed by the Spring container.

中文意思是:指示方法生成要由 Spring 容器管理的 Bean。这样就应该能明白为啥放在方法上了吧。

3、Spring Bean

3.1、Spring Bean是什么?

Bean作为Spring框架面试中不可或缺的概念,其本质上是指代任何被Spring加载生成出来的对象。(本质上区别于Java Bean,Java Bean是对于Java类的一种规范定义。)Spring Bean代表着Spring中最小的执行单位,其加载、作用域、生命周期的管理都由Spring操作。可见Spring Bean在整个Spring框架中的重要地位。

什么是Java Bean

并非所有的类都是 Java Bean,其是一种特殊的类,具有以下特征:

  • 提供一个默认的无参构造函数。
  • 需要被序列化并且实现了 Serializable 接口。
  • 可能有一系列可读写属性,并且一般是 private 的。
  • 可能有一系列的 getter 或 setter 方法。

3.2、Spring Bean的作用域

1、singleton:默认作用域,单例bean,每个容器中只有一个bean的实例。

2、prototype:为每一个bean请求创建一个实例。

3、request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。

4、session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例。

5、global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中。

3.3、Spring 的容器启动流程

详细见此文章 Spring Ioc 容器启动流程-CSDN博客

3.4、Spring Bean的生命周期

简单来说,Spring Bean的生命周期只有四个阶段:实例化 Instantiation --> 属性赋值 Populate --> 初始化 Initialization --> 销毁 Destruction

但具体来说,Spring Bean的生命周期包含下图的流程:

(1)实例化Bean:

对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。

对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。

(2)设置对象属性(依赖注入):实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成属性设置与依赖注入。

(3)处理Aware接口:Spring会检测该对象是否实现了xxxAware接口,通过Aware类型的接口,可以让我们拿到Spring容器的一些资源:

①如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,传入Bean的名字;
②如果这个Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
②如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。
③如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;
(4)BeanPostProcessor前置处理:如果想对Bean进行一些自定义的前置处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。

(5)InitializingBean:如果Bean实现了InitializingBean接口,执行afeterPropertiesSet()方法。

(6)init-method:如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。

(7)BeanPostProcessor后置处理:如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;

以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。

(8)DisposableBean:当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;

(9)destroy-method:最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。

如果对bean详细加载流程的感兴趣的读者,可以阅读这篇文章(自己跟源码跟了两天,看到最后有点崩溃,放弃了,大家一起看看大佬的文章吧,还是太菜了,后续如果有能力和事件的话,一定会再次尝试读取源码):SpringBean的生命周期

3.5、Spring Bean的线程安全问题

Spring容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体情况还是要结合Bean的作用域来讨论。

(1)对于prototype作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题。

(2)对于singleton作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Controller类、Service类和Dao等,这些Bean大多是无状态的,只关注于方法本身。

有状态Bean(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。

无状态Bean(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。

对于有状态的bean(比如Model和View),就需要自行保证线程安全,最浅显的解决办法就是将有状态的bean的作用域由“singleton”改为“prototype”。

也可以采用ThreadLocal解决线程安全问题,为每个线程提供一个独立的变量副本,不同线程只操作自己线程的副本变量。

ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。

3.6、Spring Bean的循环依赖问题

循环依赖问题在Spring中主要有三种情况:

(1)通过构造方法进行依赖注入时产生的循环依赖问题。
(2)通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。
(3)通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。
在Spring中,只有第(3)种方式的循环依赖问题被解决了,其他两种方式在遇到循环依赖问题时都会产生异常。这是因为:

第一种构造方法注入的情况下,在new对象的时候就会堵塞住了,其实也就是”先有鸡还是先有蛋“的历史难题。
第二种setter方法(多例)的情况下,每一次getBean()时,都会产生一个新的Bean,如此反复下去就会有无穷无尽的Bean产生了,最终就会导致OOM问题的出现。
Spring在单例模式下的setter方法依赖注入引起的循环依赖问题,主要是通过二级缓存和三级缓存来解决的,其中三级缓存是主要功臣。解决的核心原理就是:在对象实例化之后,依赖注入之前,Spring提前暴露的Bean实例的引用在第三级缓存中进行存储。

大家一起来膜拜大佬的文章吧,写不动了,脑袋不够用 :Spring循环依赖问题

3.7、BeanFactory 和 ApplicationContext 区别

BeanFactory是Spring里面最底层的接口,是IoC的核心,定义了IoC的基本功能,包含了各种Bean的定义、加载、实例化,依赖注入和生命周期管理

ApplicationContext接口作为BeanFactory的子类,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:

  • 继承MessageSource,因此支持国际化。
  • 资源文件访问,如URL和文件(ResourceLoader)。
  • 载入多个(有继承关系)上下文(即同时加载多个配置文件) ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
  • 提供在监听器中注册bean的事件。

区别:

1、

BeanFactroy采用的是延迟加载形式来注入Bean的,只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能提前发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。

ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。

ApplicationContext启动后预载入所有的单实例Bean,所以在运行的时候速度比较快,因为它们已经创建好了。相对于BeanFactory,ApplicationContext 唯一的不足是占用内存空间,当应用程序配置Bean较多时,程序启动较慢。

2、

BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。

3、

BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。

4、Spring Aop

4.1、什么是 Aop

OOP面向对象,允许开发者定义纵向的关系,但并不适用于定义横向的关系,会导致大量代码的重复,而不利于各个模块的重用。

AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、日志、事务处理。

4.2、Spring AOP里面的几个名词的概念:

(1)连接点(Join point):指程序运行过程中所执行的方法。在Spring AOP中,一个连接点总代表一个方法的执行。

(2)切面(Aspect):被抽取出来的公共模块,可以用来会横切多个对象。Aspect切面可以看成 Pointcut切点 和 Advice通知 的结合,一个切面可以由多个切点和通知组成。

在Spring AOP中,切面可以在类上使用 @AspectJ 注解来实现。

(3)切点(Pointcut):切点用于定义 要对哪些Join point进行拦截。

切点分为execution方式和annotation方式。execution方式可以用路径表达式指定对哪些方法拦截,比如指定拦截add*、search*。annotation方式可以指定被哪些注解修饰的代码进行拦截。

(4)通知(Advice):指要在连接点(Join Point)上执行的动作,即增强的逻辑,比如权限校验和、日志记录等。通知有各种类型,包括Around、Before、After、After returning、After throwing。

(5)目标对象(Target):包含连接点的对象,也称作被通知(Advice)的对象。 由于Spring AOP是通过动态代理实现的,所以这个对象永远是一个代理对象。

(6)织入(Weaving):通过动态代理,在目标对象(Target)的方法(即连接点Join point)中执行增强逻辑(Advice)的过程。

(7)引入(Introduction):添加额外的方法或者字段到被通知的类。Spring允许引入新的接口(以及对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。

几个概念的关系图可以参考下图:

(1)连接点(Join point):指程序运行过程中所执行的方法。在Spring AOP中,一个连接点总代表一个方法的执行。

(2)切面(Aspect):被抽取出来的公共模块,可以用来会横切多个对象。Aspect切面可以看成 Pointcut切点 和 Advice通知 的结合,一个切面可以由多个切点和通知组成。

在Spring AOP中,切面可以在类上使用 @AspectJ 注解来实现。

(3)切点(Pointcut):切点用于定义 要对哪些Join point进行拦截。

切点分为execution方式和annotation方式。execution方式可以用路径表达式指定对哪些方法拦截,比如指定拦截add*、search*。annotation方式可以指定被哪些注解修饰的代码进行拦截。

(4)通知(Advice):指要在连接点(Join Point)上执行的动作,即增强的逻辑,比如权限校验和、日志记录等。通知有各种类型,包括Around、Before、After、After returning、After throwing。

(5)目标对象(Target):包含连接点的对象,也称作被通知(Advice)的对象。 由于Spring AOP是通过动态代理实现的,所以这个对象永远是一个代理对象。

(6)织入(Weaving):通过动态代理,在目标对象(Target)的方法(即连接点Join point)中执行增强逻辑(Advice)的过程。

(7)引入(Introduction):添加额外的方法或者字段到被通知的类。Spring允许引入新的接口(以及对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。

几个概念的关系图可以参考下图:

img

4.3 通知

Advice的类型:

(1)前置通知(Before Advice):在连接点(Join point)之前执行的通知。

(2)后置通知(After Advice):当连接点退出的时候执行的通知(不论是正常返回还是异常退出)。

(3)环绕通知(Around Advice):包围一个连接点的通知,这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也可以选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

(4)返回后通知(AfterReturning Advice):在连接点正常完成后执行的通知(如果连接点抛出异常,则不执行)

(5)抛出异常后通知(AfterThrowing advice):在方法抛出异常退出时执行的通知

Advice的执行顺序:

没有异常情况下的执行顺序:

around before advice
before advice
target method 执行
after advice
around after advice
after Returning advice

出现异常情况下的执行顺序:

around before advice
before advice
target method 执行
after advice
around after advice
afterThrowing advice
java.lang.RuntimeException:异常发生

4.4、使用方法

public void test(){ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");//动态代理代理的是接口UserService userService = context.getBean("userService", UserService.class);userService.add();
}

用来测试的代码

第一种:原生的AOP

<bean id="userService" class="com.x.service.UserServiceImpl"/>
<bean id="log" class="com.x.Log.Log"/>
<bean id="afterLog" class="com.x.Log.AfterLog"/>
<!--配置AOP:需要导入AOP的约束-->
<aop:config><!--切入点,我们需要在哪执行我们的方法--><aop:pointcut id="pointcut" expression="execution(* com.x.service.UserServiceImpl.*(..))"/><!--执行环绕增加--><aop:advisor advice-ref="log" pointcut-ref="pointcut"/><aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>

自定义的log类,要实现相应的接口

public class AfterLog implements AfterReturningAdvice {//返回后的//returnValue : 返回值@Overridepublic void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {System.out.println("执行了"+method.getName()+"方法,返回结果为:"+returnValue);}
}
public class Log implements MethodBeforeAdvice {//method:要执行对象的目标方法//objects:参数//target:目标对象@Overridepublic void before(Method method, Object[] args, Object target) throws Throwable {System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");}
}

输出结果为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第二种:自定义方法

 <bean id="diy" class="com.x.diy.DiyPointCut"/><aop:config><!--自定义切面,ref要引用的类--><aop:aspect ref="diy"><!--切入点--><aop:pointcut id="point" expression="execution(* com.x.service.UserServiceImpl.*(..))"/><!--通知--><aop:before method="before" pointcut-ref="point"/><aop:after method="after" pointcut-ref="point"/></aop:aspect></aop:config>
public class DiyPointCut {public void before(){System.out.println("==========方法执行前==========");}public void after(){System.out.println("==========方法执行后==========");}
}

执行结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第三种:使用注解

 <!--方式三--><bean id="apc" class="com.x.diy.AnnotationPointCut"/><!--开启注解支持  JDK(默认 proxy-target-class="false")  cglib(proxy-target-class="true")--><aop:aspectj-autoproxy />
@Aspect//标注这个类是一个切面
public class AnnotationPointCut {@Before("execution(* com.x.service.UserServiceImpl.*(..))")public void before(){System.out.println("==========方法执行前==========");}@After("execution(* com.x.service.UserServiceImpl.*(..))")public void after(){System.out.println("==========方法执行后==========");}//在就环绕增强中,我们可以给定一个参数,待定我们要获取的处理切入的点@Around("execution(* com.x.service.UserServiceImpl.*(..))")public void around(ProceedingJoinPoint jp) throws Throwable {System.out.println("环绕前");Signature signature = jp.getSignature();//获得签名System.out.println("signature:"+signature);//执行方法jp.proceed();//方法执行System.out.println("环绕后");}
}

执行结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5、Spring 事务

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。Spring只提供统一事务管理接口,具体实现都是由各数据库自己实现,数据库事务的提交和回滚是通过 redo log 和 undo log实现的。Spring会在事务开始时,根据当前环境中设置的隔离级别,调整数据库隔离级别,由此保持一致。

5.1、Spring事务的种类:

spring支持编程式事务管理和声明式事务管理两种方式:

①编程式事务管理使用TransactionTemplate。

②声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

5.2、spring的事务传播机制:

pring事务的传播机制说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。事务传播机制实际上是使用简单的ThreadLocal实现的,所以,如果调用的方法是在新线程调用的,事务传播实际上是会失效的。

PROPAGATION_REQUIRED:(默认传播行为)如果当前没有事务,就创建一个新事务;如果当前存在事务,就加入该事务。

② PROPAGATION_REQUIRES_NEW:无论当前存不存在事务,都创建新事务进行执行。

③ PROPAGATION_SUPPORTS:如果当前存在事务,就加入该事务;如果当前不存在事务,就以非事务执行。‘

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

⑤ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则按REQUIRED属性执行。

⑥ PROPAGATION_MANDATORY:如果当前存在事务,就加入该事务;如果当前不存在事务,就抛出异常。

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

5.3、Spring中的隔离级别:

① ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。

② ISOLATION_READ_UNCOMMITTED:读未提交,允许事务在执行过程中,读取其他事务未提交的数据。

③ ISOLATION_READ_COMMITTED:读已提交,允许事务在执行过程中,读取其他事务已经提交的数据。

④ ISOLATION_REPEATABLE_READ:可重复读,在同一个事务内,任意时刻的查询结果都是一致的。

⑤ ISOLATION_SERIALIZABLE:所有事务逐个依次执行。

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

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

相关文章

vscode 访问本地或者远程docker环境

1、vscode 访问docker本地环境 直接点击左下角连接图标&#xff0c;弹出选项可以选择容器&#xff0c;只要容器在本地运行者&#xff0c;选择attach可以看到运行中的容器可以选择&#xff0c;选择其中需要选择的就行 ## 运行容器&#xff0c;可以-d后台运行都可以 docker run…

原型模式 rust和java的实现

文章目录 原型模式介绍优点缺点使用场景 实现java 实现rust 实现 rust代码仓库 原型模式 原型模式&#xff08;Prototype Pattern&#xff09;是用于创建重复的对象&#xff0c;同时又能保证性能。 这种模式是实现了一个原型接口&#xff0c;该接口用于创建当前对象的克隆。当…

POJ 3254 Corn Fields 状态压缩DP(铺砖问题)

一、题目大意 我们要在N * M的田地里种植玉米&#xff0c;有如下限制条件&#xff1a; 1、对已经种植了玉米的位置&#xff0c;它的四个相邻位置都无法继续种植玉米。 2、题目中有说一些块无论如何&#xff0c;都无法种植玉米。 求所有种植玉米的方案数&#xff08;不种植也…

JavaWeb Day08 Mybatis-入门

目录 ​编辑​编辑​编辑 一、快速入门程序 ①准备工作 ②引入Mybatis相关依赖&#xff0c;配置Mybatis ③编写SQL&#xff08;注解/XML&#xff09; ④单元测试 ⑤相关代码 1.pom.xml 2. application.properties 3.User.java 4. UserMapper.java 5.Test.java ⑥配置…

2390 高校实验室预约系统JSP【程序源码+文档+调试运行】

摘要 本文介绍了一个高校实验室预约系统的设计和实现。该系统包括管理员、教师和学生三种用户&#xff0c;具有基础数据管理、学生管理、教师管理、系统公告管理、实验室管理、实验室预约管理和系统管理等模块。通过数据库设计和界面设计&#xff0c;实现了用户友好的操作体验…

Halcon WPF 开发学习笔记(3):WPF+Halcon初步开发

文章目录 前言在MainWindow.xaml里面导入Halcon命名空间WPF简单调用Halcon创建矩形简单调用导出脚本函数 前言 本章会简单讲解如何调用Halcon组件和接口&#xff0c;因为我们是进行混合开发模式。即核心脚本在平台调试&#xff0c;辅助脚本C#直接调用。 在MainWindow.xaml里面…

如何安装Node.js? 创建Vue脚手架

1.进入Node.js官网&#xff0c;点击LTS版本进行下载 Node.js (nodejs.org)https://nodejs.org/en 2.然后一直【Next】即可 3.打开【cmd】,输入【node -v】注意node和-v中间的空格 查看已安装的Node.js的版本号&#xff0c;如果可以看到版本号&#xff0c;则安装成功 创建Vue脚手…

Python使用SQLAlchemy操作sqlite

Python使用SQLAlchemy操作sqlite sqllite1. SQLite的简介2. 在 Windows 上安装 SQLite3. 使用SQLite创建数据库3.1 命令行创建数据库3.2 navicat连接数据库 4.sqlite的数据类型存储类SQLite Affinity 类型Boolean 数据类型Date 与 Time 数据类型 5. 常用的sql语法**创建表(CREA…

微软和Red Hat合体:帮助企业更方便部署容器

早在2015年&#xff0c;微软就已经和Red Hat达成合作共同为企业市场开发基于云端的解决方案。时隔两年双方在企业市场的多个方面开展更紧密的合作&#xff0c;今天两家公司再次宣布帮助企业更方便地部署容器。 双方所开展的合作包括在微软Azure上部署Red Hat OpenShift&#xf…

竞赛 题目: 基于深度学习的疲劳驾驶检测 深度学习

文章目录 0 前言1 课题背景2 实现目标3 当前市面上疲劳驾驶检测的方法4 相关数据集5 基于头部姿态的驾驶疲劳检测5.1 如何确定疲劳状态5.2 算法步骤5.3 打瞌睡判断 6 基于CNN与SVM的疲劳检测方法6.1 网络结构6.2 疲劳图像分类训练6.3 训练结果 7 最后 0 前言 &#x1f525; 优…

手机开机入网流程 KPI接通率和掉线率

今天我们来学习手机开机入网流程是怎么样的。以及RRC连接和重建流程(和博主之前讲TCP三次握手&#xff0c;四次挥手原理很相似)是什么样的&#xff0c;还有天线的KPI指标都包括什么&#xff0c;是不是很期待啊~ 目录 手机开机入网流程 ATTACH/RRC连接建立过程 KPI接通率和掉…

安全通信网络(设备和技术注解)

网络安全等级保护相关标准参考《GB/T 22239-2019 网络安全等级保护基本要求》和《GB/T 28448-2019 网络安全等级保护测评要求》 密码应用安全性相关标准参考《GB/T 39786-2021 信息系统密码应用基本要求》和《GM/T 0115-2021 信息系统密码应用测评要求》 1网络架构 1.1保证网络…

hive和spark-sql中 日期和时间相关函数 测试对比

测试版本&#xff1a; hive 2.3.4 spark 3.1.1 hadoop 2.7.7 1、增加月份 add_months(timestamp date, int months)add_months(timestamp date, bigint months)Return type: timestampusage:add_months(now(),1) 2、增加日期 adddate(timestamp startdate, int days)…

【C++】特殊类实现——设计一个类、不能被拷贝、只能在堆上创建对象、只能在栈上创建对象、不能被继承、单例模式、饿汉模式、懒汉模式

文章目录 C特殊类实现1.设计一个类、不能被拷贝2.设计一个类、只能在堆上创建对象3.设计一个类、只能在栈上创建对象4.设计一个类、不能被继承5.设计一个类&#xff0c;只能创建一个对象(单例模式)5.1饿汉模式5.2懒汉模式 C 特殊类实现 1.设计一个类、不能被拷贝 在C中&#x…

node插件MongoDB(四)—— 库mongoose 操作文档使用(新增、删除、更新、查看文档)(二)

文章目录 前言&#xff08;1&#xff09;问题&#xff1a;安装的mongoose 库版本不应该过高导致的问题&#xff08;2&#xff09;重新安装低版本 一、插入文档1. 代码2. node终端效果3. 使用mongo.exe查询数据库的内容 二、删除文档1. 删除一条2. 批量删除3. 代码 三、修改文档…

pyTorch Hub 系列#4:PGAN — GAN 模型

一、主题描述 2014 年生成对抗网络的诞生及其对任意数据分布进行有效建模的能力席卷了计算机视觉界。两人范例的简单性和推理时令人惊讶的快速样本生成是使 GAN 成为现实世界中实际应用的理想选择的两个主要因素。 然而&#xff0c;在它们出现后的很长一段时间内&#xff0c;GA…

案例续集留言板

前端没有保存数据的功能,后端把数据保存下来(内存,数据库等等......) 前端代码如下 : <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initia…

ChatGPT 4 分析天猫双十一历年成交额趋势情况

收集历年的双十一成交额数据如下: 年份成交额:亿元20090.520109.362011

Linux——vim简介、配置方案(附带超美观的配置方案)、常用模式的基本操作

vim简介、配置方案、常用模式的基本操作 本章思维导图&#xff1a; 注&#xff1a;本章思维导图对应的xmind和.png文件都已同步导入至资源 1. vim简介 vim是Linux常用的文本编辑器&#xff0c;每个Linux账户都独有一个vim编辑器 本篇我们介绍vim最常用的三种模式&#xff1a;…

LeetCode 189.轮转数组(三种方法解决)

文章目录 题目暴力求解空间换时间三段逆置总结 题目 LeetCode 189.轮转数组 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5…