目录
8、BeanFactory和ApplicationContext有什么区别?
11、Spring框架中的Bean是线程安全的么?如果线程不安全,那么如何处理?
1、spring是什么?
Spring是一款轻量级的IOC和Aop容器框架。是为java应用程序提供基础性服务的一套框架,目的是为了简化企业应用的开发,它使得开发者只需要关心业务需求。
spring主要包括以下七个模块:
- Spring Context:提供框架是的Bean访问方式,以及企业级功能(JNDI、定时任务等等);
- Spring Code:核心类库,所有功能都依赖于类库,提供IOC和Aop服务;
- Spring Aop:Aop服务;
- Spring Web:提供了面向Web的综合性功能,提供了对很多优秀框架的支持,如Struts2,Spring能够管理这些框架,将spring的资源注入到框架中,也能在这些框架的前后插入拦截器;
- Spring MVC:提供面向Web应用的Model-View-Controller,即MVC实现。
- Spring DAO:对JDBC的抽象封装,简化了数据访问异常的处理,并能统一管理JDBC事务;
- Spring ORM:对现有的ORM框架的支持;
下图对应的是Spring 4.x的版本,5.x版本中Web模块的Portlet组件已经被废弃
2、Spring 有哪些优点?
(1)spring属于低侵入式设计,代码的污染极低;
(2)spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;
(3)Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。
(4)spring对于主流的应用框架提供了集成支持。
3、谈谈你对IOC的理解
IOC(Inversion of Control),控制反转,指将对象的控制权转移给Spring框架,由 Spring 来负责控制对象的生命周期(比如创建、销毁)和对象间的依赖关系。
最直观的表达就是,以前创建对象的时机和主动权都是由自己把控的,如果在一个对象中使用另外的对象,就必须主动通过new指令去创建依赖对象,使用完后还需要销毁(比如jdbc Connection等),对象始终会和其他接口或类耦合起来。而 IOC 则是由专门的容器来帮忙创建对象,将所有的类都在 Spring 容器中登记,当需要某个对象时,不再需要自己主动去 new 了,只需告诉 Spring 容器,然后 Spring 就会在系统运行到适当的时机,把你想要的对象主动给你。也就是说,对于某个具体的对象而言,以前是由自己控制它所引用对象的生命周期,而在IOC中,所有的对象都被 Spring 控制,控制对象生命周期的不再是引用它的对象,而是Spring容器,由 Spring 容器帮我们创建、查找及注入依赖对象,而引用对象只是被动的接受依赖对象,所以这叫控制反转。
4、什么是DI?
控制反转IoC是一个很大的概念,可以有不同的实现方式。其主要实现方式有两种:依赖注入和依赖查找
依赖注入:相对于IoC而言,依赖注入(DI)更加准确地描述了IoC的设计理念。所谓依赖注入(Dependency Injection),即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。组件不做定位查询,只提供普通的Java方法,让容器去决定依赖关系。
依赖查找(Dependency Lookup):容器提供回调接口和上下文环境给组件。EJB和Apache Avalon都使用这种方式。
依赖查找也有两种类型:依赖拖拽(DP)和上下文依赖查找(CDL)。
DI是实现IOC的一个重要手段和核心思路。IoC 的一个重点就是在程序运行时,动态的向某个对象提供它所需要的其他对象,这一点是通过DI(Dependency Injection,依赖注入)来实现的,即应用程序在运行时依赖 IoC 容器来动态注入对象所需要的外部依赖。而 Spring 的 DI 具体是通过反射实现注入的,反射允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性。
不理解反射的可以参考这篇文章:
(84条消息) 五、什么是java反射?如何使用它?有哪些例子?_艾尚波的博客-CSDN博客
5、谈谈你对AOP的理解
OOP面向对象,允许开发者定义纵向的关系,但并不适用于定义横向的关系,会导致大量代码的重复,而不利于各个模块的重用。
AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、日志、事务处理。
AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。
(1)AspectJ是静态代理,也称为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
(2)Spring AOP使用的动态代理,也称为运行时增强,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
① JDK动态代理只提供接口的代理,不支持类的代理,要求被代理类实现接口。JDK动态代理的核心是InvocationHandler接口和Proxy类,在获取代理对象时,使用Proxy类来动态创建目标类的代理类(即最终真正的代理类,这个类继承自Proxy并实现了我们定义的接口),当代理对象调用真实对象的方法时, InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;
InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理对象; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
② 如果被代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
(3)静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。
5、可以谈谈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允许引入新的接口(以及对应的实现)到任何被代理的对象。
几个概念的关系图可以参考下图:
网上有张非常形象的图,描述了各个概念所处的场景和作用,贴在这里供大家理解:
6、Spring通知(Advice)有哪些类型?
6.1、Advice的类型:
(1)前置通知(Before Advice):在连接点(Join point)之前执行的通知。
(2)后置通知(After Advice):当连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
(3)环绕通知(Around Advice):包围一个连接点的通知,这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也可以选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
(4)返回后通知(AfterReturning Advice):在连接点正常完成后执行的通知(如果连接点抛出异常,则不执行)
(5)抛出异常后通知(AfterThrowing advice):在方法抛出异常退出时执行的通知
6.2、Advice的执行顺序:
(1)没有异常情况下的执行顺序:
- around before advice
- before advice
- target method 执行
- after advice
- around after advice
- afterReturning advice
(2)出现异常情况下的执行顺序:
- around before advice
- before advice
- target method 执行
- after advice
- around after advice
- afterThrowing advice
- java.lang.RuntimeException:异常发生
7、说说Spring容器的启动流程
-
加载 Spring 配置文件 Spring 容器通过读取 XML、Properties 等配置文件或者 Java 配置类来获取配置信息。在这个阶段,Spring 会解析各种 Bean 定义、依赖关系、AOP 配置等信息,并将其转换为 Spring 容器内部数据结构中的 BeanDefinition。
-
实例化 Bean 实例化 Bean 的过程是根据 BeanDefinition 中的信息,使用反射技术创建 Bean 的实例。在这个阶段,Spring 容器会判断 Bean 的作用域,如果是 singleton,那么 Spring 容器会将 Bean 实例化后缓存到 Spring 容器中,供后续使用;如果是 prototype,那么每次从 Spring 容器中获取 Bean 的时候都会重新创建一个实例。
-
依赖注入 依赖注入是 Spring 容器的核心功能之一,它是通过反射机制,根据 BeanDefinition 中的属性值或者注解等信息来完成的。在这个阶段,Spring 容器会对 Bean 进行属性注入、构造函数注入、方法注入等操作,保证 Bean 中的各个属性都得到了正确的初始化。
-
Bean 生命周期回调方法 在 Bean 实例化、属性注入之后,Spring 容器会调用 Bean 的各个生命周期回调方法。包括:
BeanPostProcessor.postProcessBeforeInitialization()
,在 Bean 初始化前调用;InitializingBean.afterPropertiesSet()
,在 Bean 的属性设置之后调用;- 自定义的
init-method
方法,作为 Bean 的初始化回调。
5、创建完毕 当 Spring 容器执行完成以上步骤之后,所有的 Bean 实例都已经创建完成,可以通过调用容器提供的 getBean()
方法来获取需要的 Bean 实例。
以上就是 Spring 容器启动流程的主要步骤,当然在其中还有一些扩展,例如对 Bean 的作用域、AOP 的代理、事件监听等等,这些都是 Spring 容器的一些高级功能。
8、BeanFactory和ApplicationContext有什么区别?
Spring 提供了两种类型的容器来管理 Bean:BeanFactory 和 ApplicationContext。BeanFactory 是最基础的容器,提供了最基本的 Bean 容器的功能,ApplicationContext 继承自 BeanFactory 接口,提供了更多的功能和扩展,因此 ApplicationContext 更常用一些。
对于简单的应用系统,可以采用 BeanFactory,而对于复杂应用系统和集成其他框架等情况则建议使用 ApplicationContext。
以下是 BeanFactory 和 ApplicationContext 的一些优缺点:
-
BeanFactory
优点:
- BeanFactory 更轻量,启动时间短,资源占用少。
- BeanFactory 拥有更低的内存消耗,适用于较小的应用程序。
缺点:
- 调用 getBean() 方法时才会实例化 Bean,只有 Bean 被使用时才会创建。相应的,Bean 的初始化时间会稍微慢一些,因为它们只是在第一次被访问时才被创建。
-
ApplicationContext
优点:
- ApplicationContext 更强大,提供了更多的扩展特性,满足更多的框架需求。
- ApplicationContext 在启动时对所有 Bean 进行加载和初始化,这就确保了 ApplicationContext 启动时能够及时发现配置文件中存在的错误。
缺点:
- ApplicationContext 较为笨重。
因此,建议在一般情况下优先使用 ApplicationContext 初始化 Bean。另外,Spring Boot 中使用的是 SpringApplication.run() 方法初始化应用程序,默认创建的是 ApplicationContext 容器。
其他不同:
- BeanFactory需要手动注册,而ApplicationContext则是自动注册。
- BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
ApplicationContext的额外功能:
- 继承MessageSource,因此支持国际化。
- 资源文件访问,如URL和文件(ResourceLoader)。
- 载入多个(有继承关系)上下文(即同时加载多个配置文件) ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
- 提供在监听器中注册bean的事件。
9、Spring Bean的生命周期?
简单来说,Spring Bean的生命周期只有四个阶段:实例化 Instantiation --> 属性赋值 Populate --> 初始化 Initialization --> 销毁 Destruction
但具体来说,Spring Bean的生命周期包含下图的流程:
Spring Bean 的生命周期可以分为以下几个阶段:
-
实例化 在 Spring 容器启动时,根据配置文件或者注解等信息创建 BeanDefinition,并基于这些信息创建出 Bean 对象的实例。如果 Bean 对象是 singleton 作用域,那么实例化的 Bean 对象会被缓存以备后续使用,如果是 prototype 作用域,那么每次获取 Bean 对象都会新创建一个实例。
-
属性赋值 在实例化 Bean 对象之后,Spring 容器将会根据 BeanDefinition 中的配置信息对 Bean 对象的属性进行赋值。有多种方式可以实现属性赋值,例如 XML 配置、注解、API 等方式。其中包括如下三种常见的方式:
public class Student {
private String name;
private int age;
private School school;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setSchool(School school) {
this.school = school;
}
}
- Setter 方法注入 通过调用 Bean 对象中的 Setter 方法,传递属性值给 Bean 对象。
<bean id="student" class="com.example.Student">
<property name="name" value="张三" />
<property name="age" value="18" />
<property name="school" ref="school" />
</bean>
<bean id="school" class="com.example.School" />
- 构造函数注入 通过调用 Bean 对象的构造函数,传递属性值给 Bean 对象。
<bean id="student" class="com.example.Student">
<constructor-arg value="张三" />
<constructor-arg value="18" />
<constructor-arg ref="school" />
</bean>
<bean id="school" class="com.example.School" />
- 字段注入 通过字段的方式,直接给 Bean 对象的属性赋值。
3、 初始化阶段,Spring 容器会对 Bean 对象进行各种初始化工作,其大致流程如下:
public class Student {
// ...
public void init() {
System.out.println("在 Bean 对象初始化之后,执行自定义的初始化方法");
}
@PostConstruct
public void postConstruct() {
System.out.println("在 Bean 对象初始化之后,执行 PostConstruct 注解标记的方法");
}
@Override
public void afterPropertiesSet() {
System.out.println("在 Bean 对象初始化之后,执行 InitializingBean 接口的 afterPropertiesSet 方法");
}
}
-
BeanPostProcessor 处理 在 Bean 对象初始化之前和之后,Spring 容器会先执行注册在容器中的
BeanPostProcessor
的实现类的postProcessBeforeInitialization
和postProcessAfterInitialization
方法来对 Bean 对象进行处理。 -
自定义初始化方法 开发者可以在 Bean 对象中自定义一个
void init()
的方法来进行自定义的初始化操作。 -
使用 @PostConstruct 注解 开发者还可以使用标记在 Bean 对象上的
@PostConstruct
注解来标记一个初始化方法。 -
实现 InitializingBean 接口 开发者也可以让 Bean 对象实现
InitializingBean
接口,并实现其中的afterPropertiesSet()
方法来进行初始化操作。
4、使用 这个时候,Bean 对象已经完成了初始化工作,可以提供各种服务和使用。
5、销毁 当 Spring 容器关闭时,容器会释放掉实例化的 Bean 对象,这个时候会调用 Bean 对象的销毁方法。其大致流程如下:
public class Student {
// ...
public void destroy() {
System.out.println("在 Bean 对象销毁之前,执行自定义的销毁方法");
}
@PreDestroy
public void preDestroy() {
System.out.println("在 Bean 对象销毁之前,执行 PreDestroy 注解标记的方法");
}
@Override
public void destroy() {
System.out.println("在 Bean 对象销毁之前,执行 DisposableBean 接口的 destroy 方法");
}
}
-
自定义销毁方法 开发者可以在 Bean 对象中自定义一个
void destroy()
的方法来进行自定义的销毁操作。 -
实现 DisposableBean 接口 开发者也可以让 Bean 对象实现
DisposableBean
接口,并实现其中的destroy()
方法来进行销毁操作。 -
使用 @PreDestroy 注解 开发者还可以使用标记在 Bean 对象上的
@PreDestroy
注解来标记一个销毁方法。
以上就是 Spring Bean 的生命周期,开发者可以根据需要实现 Bean 对象特定的初始化和销毁逻辑。
10、 Spring中bean的作用域:
(1)singleton:默认作用域,单例bean,每个容器中只有一个bean的实例。(2)prototype:为每一个bean请求创建一个实例。
(3)request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
(4)session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例。
(5)global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中。
11、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会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。
12、有哪些不同类型的依赖注入实现方式
-
依赖注入是时下最流行的IoC实现方式,依赖注入分为接口注入(Interface Injection),Setter方法注入(Setter Injection)和构造器注入(Constructor Injection)三种方式。其中接口注入由于在灵活性和易用性比较差,现在从Spring4开始已被废弃。
构造器注入:构造器注入是容器通过调用一个类的构造器来实现的,该构造器有一系列参数,每个参数都必须注入。
Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参static工厂方法实例化bean之后,调用该bean的setter方法来实现的依赖注入。
@$构造器注入和 Setter方法注入的区别
构造器注入 setter 注入 部分注入 没有部分注入 有部分注入 覆盖setter属性 不会覆盖 setter 属性 会覆盖 setter 属性 任意修改是否创建新实例 任意修改都会创建一个新实例 任意修改不会创建一个新实例 适用场景 适用于设置很多属性 适用于设置少量属性 最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖。
13、Spring如何解决循环依赖问题:
详细内容强烈建议参考这篇文章:Spring如何解决循环依赖问题
循环依赖问题在Spring中主要有三种情况:
(1)通过构造方法进行依赖注入时产生的循环依赖问题。
(2)通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。
(3)通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。
在Spring中,只有第(3)种方式的循环依赖问题被解决了,其他两种方式在遇到循环依赖问题时都会产生异常。这是因为:
第一种构造方法注入的情况下,在new对象的时候就会堵塞住了,其实也就是”先有鸡还是先有蛋“的历史难题。
第二种setter方法(多例)的情况下,每一次getBean()时,都会产生一个新的Bean,如此反复下去就会有无穷无尽的Bean产生了,最终就会导致OOM问题的出现。
第三种Spring在单例模式下的setter方法依赖注入引起的循环依赖问题,主要是通过二级缓存和三级缓存来解决的,其中三级缓存是主要功臣。解决的核心原理就是:在对象实例化之后,依赖注入之前,Spring提前暴露的Bean实例的引用在第三级缓存中进行存储。
Spring 在解决第三种循环依赖问题时,使用了“提前暴露”的策略,即:
- 先创建 Bean 对象实例,并将其放入缓存中;
- 然后处理 Bean 对象中的属性注入;
- 最后调用 Bean 对象的初始化方法。
在第二步中,当处理到一个 Bean 对象的属性注入时,如果该属性所依赖的 Bean 对象还未被创建,则 Spring 会先创建该 Bean 对象实例并放入缓存中,再对该 Bean 对象实例进行属性注入。
示例代码:
public class A {
private B b;
public A() {
System.out.println("A 实例化");
}
public void setB(B b) {
this.b = b;
System.out.println("A 注入 B");
}
public void init() {
System.out.println("A 初始化");
}
}
public class B {
private A a;
public B() {
System.out.println("B 实例化");
}
public void setA(A a) {
this.a = a;
System.out.println("B 注入 A");
}
public void init() {
System.out.println("B 初始化");
}
}
// 配置文件
<bean id="a" class="com.example.A" init-method="init">
<property name="b" ref="b" />
</bean>
<bean id="b" class="com.example.B" init-method="init">
<property name="a" ref="a" />
</bean>
在这个例子中,Bean A 和 Bean B 互相依赖,如果按照普通的顺序,创建 A 时需要先创建 B,创建 B 时也需要先创建 A,因此会出现循环依赖问题。
但是 Spring 容器会先实例化 A 和 B,然后只调用 A 的 setB()
方法,不调用 B 的 setA()
方法,等 A 初始化完成后再调用 B 的 setA()
方法,从而避免了循环依赖问题。
14、Spring的自动装配:
在spring中,使用autowire来配置自动装载模式,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象。
(1)在Spring框架xml配置中共有5种自动装配:
- no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。
- byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。
- byType:通过参数的数据类型进行自动装配。
- constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。
- autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。
(2)基于注解的自动装配方式:
使用@Autowired、@Resource注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,。在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:
- 如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
- 如果查询的结果不止一个,那么@Autowired会根据名称来查找;
- 如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。
@Autowired可用于:构造函数、成员变量、Setter方法
注:@Autowired和@Resource之间的区别:
-
@Autowired
是 Spring 框架的注解,而@Resource
是 Java EE 的注解。因此,在使用@Autowired
时必须导入 Spring 的相关依赖包,而在使用@Resource
时则不需要。 -
@Autowired
是按照属性名进行自动装配的,如果同一类型的依赖对象有多个,可以使用@Qualifier
指定具体的依赖对象。例如:@Autowired @Qualifier("userService") // 指定依赖对象为名为 "userService" 的对象 private UserService userService;
而
@Resource
则是按照类型进行自动装配的,如果同一类型的依赖对象有多个,可以使用name
属性指定具体的依赖对象。例如:@Resource(name = "userService") // 指定依赖对象为名为 "userService" 的对象 private UserService userService;
-
@Autowired
可以用于字段、方法和构造函数上,而@Resource
只能用于字段和方法上,不能用于构造函数。因为在使用构造函数进行注入时,需要使用@Autowired
中的@Autowired(required = true)
手动标识是否必须注入才能构造对象。 -
@Autowired
是 Spring 的默认注入方式,而@Resource
是 Java EE 的默认注入方式。因此,在使用@Autowired
时,如果要注入的依赖对象没有与任何一个 Bean 类型相匹配,程序会报错。而在使用@Resource
时,如果指定的依赖对象没有与任何一个 Bean 名称相匹配,程序不会报错,但是依赖对象的值仍然为null
。
总之,
@Autowired
和@Resource
都可以实现依赖注入,但是在使用过程中需要根据具体的业务场景选择合适的注解。如果您使用的是 Spring 框架,建议使用@Autowired
进行注入,这样可以兼容更多的 Spring 功能。如果您使用的是 Java EE,建议使用@Resource
进行注入。
15、Spring事务的实现方式和实现原理:
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。Spring只提供统一事务管理接口,具体实现都是由各数据库自己实现,数据库事务的提交和回滚是通过 redo log 和 undo log实现的。Spring会在事务开始时,根据当前环境中设置的隔离级别,调整数据库隔离级别,由此保持一致。
(1)Spring事务的种类:
spring支持编程式事务管理和声明式事务管理两种方式:
1、编程式事务管理
编程式事务管理指的是通过编写代码手动管理事务的提交、回滚等操作。在 Spring 中,可以通过 TransactionTemplate
或 PlatformTransactionManager
等现成的工具类进行编程式事务管理。
编程式事务管理的特点是:
- 事务管理代码直接嵌入到业务方法中,可见性高,方便调试和修改;
- 事务粒度较细,可以灵活控制事务的开始、提交或回滚;
- 但是,编程式事务管理代码冗长,不易维护、扩展和复用。
因此,编程式事务管理适用于事务粒度较细,需要更细粒度的控制,并且只有非常少量的事务管理代码的情况。
2、声明式事务管理
声明式事务管理指的是通过配置的方式管理事务的提交、回滚等操作,将事务管理与业务逻辑分离。在 Spring 中,可以通过 AOP 面向切面编程的方式,使用 @Transactional
注解来实现声明式事务管理(需要在启动类上添加 @EnableTransactionManagement
注解,才能使得事务注解生效)。
声明式事务管理的特点是:
- 事务管理代码不出现在业务方法中,使代码更加清晰简洁,易于维护、扩展和复用;
- 事务粒度较粗,不能灵活控制事务的开始、提交或回滚,但在大部分情况下都足够使用;
- 可以通过注解配置、XML 配置或 Java 配置的方式定义事务管理策略。
因此,声明式事务管理适用于事务粒度较粗,需要对事务管理进行配置、维护和复用的情况。
(2)spring的事务传播机制:
spring事务的传播机制说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。事务传播机制实际上是使用简单的ThreadLocal实现的,所以,如果调用的方法是在新线程调用的,事务传播实际上是会失效的。
① PROPAGATION_REQUIRED:(默认传播行为)如果当前没有事务,就创建一个新事务;如果当前存在事务,就加入该事务。
② PROPAGATION_REQUIRES_NEW:无论当前存不存在事务,都创建新事务进行执行。
③ PROPAGATION_SUPPORTS:如果当前存在事务,就加入该事务;如果当前不存在事务,就以非事务执行。‘
④ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
⑤ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则按REQUIRED属性执行。
⑥ PROPAGATION_MANDATORY:如果当前存在事务,就加入该事务;如果当前不存在事务,就抛出异常。
⑦ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
(3)Spring中的隔离级别:
① ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。
② ISOLATION_READ_UNCOMMITTED:读未提交,允许事务在执行过程中,读取其他事务未提交的数据。
③ ISOLATION_READ_COMMITTED:读已提交,允许事务在执行过程中,读取其他事务已经提交的数据。
④ ISOLATION_REPEATABLE_READ:可重复读,在同一个事务内,任意时刻的查询结果都是一致的。
⑤ ISOLATION_SERIALIZABLE:所有事务逐个依次执行。
16、Spring 框架中都用到了哪些设计模式?
Spring设计模式的详细使用案例可以阅读这篇文章:Spring中所使用的设计模式_张维鹏的博客-CSDN博客_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)桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问中根据需要会去访问不同的数据库
17、Spring框架中有哪些不同类型的事件?
Spring 提供了以下5种标准的事件:
(1)上下文更新事件(ContextRefreshedEvent):在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。
(2)上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。
(3)上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
(4)上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
(5)请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。
如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。
18、注解的原理:
(1)什么是注解:
Java 注解就是代码中的一些特殊标记(元信息),用于在编译、类加载、运行时进行解析和使用,并执行相应的处理。它本质是继承了 Annotation 的特殊接口,其具体实现类是 JDK 动态代理生成的代理类,通过反射获取注解时,返回的也是 Java 运行时生成的动态代理对象 $Proxy1。通过代理对象调用自定义注解的方法,会最终调用 AnnotationInvocationHandler 的 invoke 方法,该方法会从 memberValues 这个Map中查询出对应的值,而 memberValues 的来源是Java常量池。注解可以用于类、方法、字段等程序元素上,它本身不影响代码的执行,但可以被其他程序(如 Spring 框架、JPA、JUnit 等)读取,并根据注解的信息进行相应的处理和操作。
注解在实际开发中非常常见,比如 Java 原生的 @Overried、@Deprecated 等,Spring的 @Controller、@Service等,Lombok 工具类也有大量的注解,不过在原生 Java 中,还提供了元 Annotation(元注解),一种修饰注解的注解,比如 @Target、@Retention、@Document、@Inherited 等。
- @Target:标识注解可以修饰哪些地方,比如方法、成员变量、包等,具体取值有以下几种:ElementType.TYPE/FIELD/METHOD/PARAMETER/CONSTRUCTOR/LOCAL_VARIABLE/ANNOTATION_TYPE/PACKAGE/TYPE_PARAMETER/TYPE_USE
- @Retention:什么时候使用注解:SOURCE(编译阶段就丢弃) / CLASS(类加载时丢弃) / RUNTIME(始终不会丢弃),一般来说,我们自定义的注解都是 RUNTIME 级别的,因为大多数情况我们是根据运行时环境去做一些处理,一般需要配合反射来使用,因为反射是 Java 获取运行是的信息的重要手段
- @Document:注解是否会包含在 javadoc 中;
- @Inherited:定义该注解与子类的关系,子类是否能使用。
(2)如何创建注解
除了使用 Spring 自带的注解,我们还可以自定义注解,来满足特定的业务需求。自定义注解的定义和使用都非常简单,只需要按照以下步骤即可:
1、使用 @interface
关键字创建注解的定义,同时在定义中添加相应的属性。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
以上代码定义了一个名为 MyAnnotation
的注解,该注解有一个名为 value
的属性。
2、在需要使用的代码中,使用 @MyAnnotation
注解标记需要注解的属性。
public class MyBean {
@MyAnnotation("hello")
private String message;
}
以上代码中,我们在 message
属性上使用了 @MyAnnotation
注解,并赋值为 "hello"
。
3、在需要处理注解的地方,使用反射获取注解并进行相应的处理。
MyAnnotation myAnnotation = field.getAnnotation(MyAnnotation.class);
String value = myAnnotation.value();
System.out.println(value); // 输出 "hello"
以上代码中,我们通过反射获取了 MyBean
类中的 message
属性,并获取了其上的 @MyAnnotation
注解及其值。
这就是自定义注解的基本使用方法。
3、一些常用spring注解
@Autowired
:自动注入依赖的 Bean 对象。@Qualifier
:指定注入 Bean 的名称,解决歧义性问题。@Value
:注入基本类型、字符串、系统属性等。@Service
:将 Bean 标记为服务层组件,自动扫描并加入到 Spring 容器中。@Controller
:将 Bean 标记为控制层组件,自动扫描并加入到 Spring 容器中。@Repository
:将 Bean 标记为数据访问层组件,自动扫描并加入到 Spring 容器中。@Component
:将 Bean 标记为通用组件,自动扫描并加入到 Spring 容器中。@Scope
:指定 Bean 的作用范围,如 singleton、prototype、request、session、global session 等。@PostConstruct
:标记在方法上,表示在 Bean 实例化后调用该方法。@PreDestroy
:标记在方法上,表示在 Bean 销毁前调用该方法。@Transactional
:标记在方法上,表示该方法需要进行事务管理。@EnableTransactionManagement
:启用事务管理。@Aspect
:标记一个类为 AOP 方面,实现面向切面编程。@Pointcut
:定义切入点,用于指定在何处进行切入操作。@Before
:表示在目标方法执行前执行增强操作。@After
:表示在目标方法执行后执行增强操作。@AfterReturning
:表示在目标方法成功执行后执行增强操作。@AfterThrowing
:表示在目标方法抛出异常后执行增强操作。@RequestMapping
:将 URL 映射到方法上,用于处理 HTTP 请求。@PathVariable
:将 URL 中的变量绑定到方法参数上,用于获取 RESTful 风格 URL 中的参数值。
原文链接:https://blog.csdn.net/a745233700/article/details/80959716