Spring

一、 基础知识

1、说说你对Spring的理解?(高频)

1、Spring是一个开源框架,主要是为简化企业级应用开发而生,是一个IOC和AOP容器框架。

作用:1、分层解耦。2、简化框架的使用难度。

控制反转(IOC):将创建Bean对象的权利交给Spring,Spring创建Bean对象之后保存到Spring容器中,我们从Spring容器中获取需要的Bean对象就行了,解除了类与类之间的耦合。例如:@Component、@Service

依赖注入(DI):Spring使用Java Bean对象的 Set方法 或者 带参数的构造方法 为我们在创建所需对象时,给对象类型的属性赋值。例如:@Autowired、@ConfigurationProperties(读取配置文件中的信息)

面向切面编程(AOP):底层使用的是动态代理,使用配置的方式来完成动态代理要实现的功能。在不修改源码的基础上,对目标对象中方法进行增强。实现业务之间解耦。

2、在Spring中,所有管理的都是JavaBean对象,而BeanFactory和ApplicationContext就是Spring框架的那个IOC容器,现在一般使用ApplicationContext,其不但包括了BeanFactory的作用,同时还进行了更多的扩展。


2、Spring由哪些模块组成?

截止到目前Spring框架已集成了20多个模块。这些模块主要被分如下图所示的核心容器、数据访问/集成 、Web、AOP (面向切面编程)、 工具、消息和测试模块 。

Spring常见的模块说明:

1、Spring Core(核心容器): 核心容器提供Spring框架的基本功能。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。Spring使用BeanFactory来产生和管理Bean,它是工厂模式的实现。BeanFactory使用控制反转(IOC)模式将应用的配置和依赖性规范与实际的应用程序代码分开

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

3、Spring AOP(面向切面编程):是面向对象编程的有效补充和完善,Spring的AOP是基于动态代理实现的

4、Spring Dao(JDBC和Dao模块): JDBC、DAO的抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理,和不同数据库供应商所抛出的错误信息。异常层次结构简化了错误处理,并且极大的降低了需要编写的代码数量,比如打开和关闭链接

5、Spring ORM(对象实体映射):Spring框架插入了若干个ORM框架,从而提供了ORM对象的关系工具,其中包括了Hibernate、JDO和 IBatis SQL Map等,所有这些都遵从Spring的通用事物和DAO异常层次结构。

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

7、Spring Web MVC(MVC模块):MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的。MVC容纳了大量视图技术,其中包括JSP、POI等,模型由JavaBean构成,存放于m当中,而视图是一个接口,负责实现模型,控制器表示逻辑代码,由c的事情。

Spring框架的功能可以用在任何J2EE(企业级分布式应用程序开发规范)服务器当中,大多数功能也适用于不受管理的环境。

Spring的核心要点就是支持不绑定到特定J2EE服务的可重用业务和数据的访问的对象,毫无疑问这样的对象可以在不同的J2EE环境,独立应用程序和测试环境之间重用。


3、在Spring中有几种配置Bean的方式?(高频)

配置方式:

1、基于XML的配置

<bean id="jdbcBean" class="com.hxstrive.spring.core.properties.JdbcBean">
    <property name="driver" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

2、基于注解的配置:

为了解决 bean 太多时,XML 文件过大,从而导致膨胀不好维护的问题。在 Spring2.5 中开始支持:@Component、@Repository、@Service、@Controller 等注解定义 bean。@Component 放在类名上面,然后通过 @ComponentScan 指定一个路径,Spring 进行扫描带有 @Componet 注解的 bean,然后加至容器中。

@Service
public class UserService {
    //...
}

3、基于 @Configuration 类

这种方式其实也是我们最常用的方式之一,@Configuration 用来声明一个配置类,然后使用 @Bean 注解声明一个 bean,将其加入到 Spring 容器中。通常情况下,如果项目中有使用到第三方类库中的工具类的话,我们都是采用这种方式注册 Bean。

例如:

@Configuration
public class RedisConfig {
 
    @Bean
    public RedisTemplate<String,String> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String,String> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        // 设置键序列化方式
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // 设置简单类型值的序列化方式
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        // 设置默认序列化方式
        redisTemplate.setDefaultSerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();
 
        return redisTemplate;
    }
 
}


4、BeanFactory和ApplicationContext有什么区别?(高频)

  • BeanFactory:

BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。

  • ApplicationContext:

ApplicationContext是BeanFactory的派生容器,功能更强大,是在容器启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化。

ApplicationContext提供了更完整的功能:

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

②统一的资源文件访问方式。

③提供在监听器中注册bean的事件。

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

  • BeanFactory和ApplicationContext都支持 BeanFactoryPostProcessor(实现该接口,可以在spring的bean创建之前,修改bean的定义属性)、BeanPostProcessor(在执行bean的初始化方法前后,添加一些自己的处理逻辑) 的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册

  • BeanFactory是懒加载,使用的时候才会创建Bean,但如果属性不存在,使用的时候才会报错,优点是前期节省空间。内存不足时,可以使用BeanFactory。ApplicationContext默认在容器初始化的时候就加载所有的bean,好处是使用的时候拿来即用,无需等待,而且如果属性不存在,容器初始化的时候就会报错,缺点是起始阶段比较耗费内存。


5、Spring中BeanFactory和FactoryBean区别 (高频)

BeanFactory是一个factory,是Spring的IOC的工厂,是用来存放Spring创建的对象的容器

FactoryBean是是一个特殊的bean,是一个工厂bean,用来创建对象,创建的对象类型在泛型里。


6、Spring框架中的单例Bean是线程安全的吗?(高频)

不是安全的。

Spring中的Bean默认是单例模式的,框架并没有对bean进行多线程的封装处理,所以不是线程安全的。

注:单例bean是指IOC容器中就只有这么一个bean,是全局共享的,有多少个线程来访问用的都是这个bean。但是,共享不一定会有线程安全问题。

如果Bean是有状态的,那就需要开发人员自己来进行线程安全的保证,最简单的办法就是改变bean的作用域把 "singleton" 改为 "protopyte" ,这样每次请求Bean就相当于是 new Bean() ,就可以保证线程的安全了。

  • 有状态就是有数据存储功能。假设某个bean中我们定义了一个共享数据count++,且可以对共享数据进行修改,那么这个数就是不安全的,每次线程到来都会被执行一次,count值会+1,这样才会造成线程安全问题。

  • 无状态就是不会保存数据

controller、service和dao层本身并不是线程安全的,但如果只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变量,那么就不会产生线程安全问题。

Dao层会操作数据库,但是每一个和数据库连接的connection,都会被数据库的事务机制管理,如果开启了Spring事务机制,那么也会被其管理。这都不会造成线程安全问题。

不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么我们可以有以下几种办法,保证安全。

  • 使用ThreadLocal,把变量变成线程私有的

  • 使用synchronized、lock、CAS等这些实现线程同步的方法,保证互斥访问临界区

  • 把bean从单例(singleton)设置成原型(prototype)


7、Spring如何保证高并发下Controller线程安全 ?

不要在bean中声明任何有状态的实例变量或静态变量,如果必须如此,那么就使用ThreadLocal把变量变为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用synchronized、lock、CAS等这些实现线程同步的方法了。


8、Spring Bean有哪些作用域,它们之间有什么区别?(高频)

1、singleton :单例模式,这种bean范围是默认的,这种范围确保不管接受到多少个请求,每个容器中只有一个bean的实例,单例的模式由bean factory自身来维护 。

2、prototype :原型模式,原形范围与单例范围相反,每次通过容器的getBean方法获取 prototype 定义的 bean 时,都产生一个新的 bean 实例。

3、只有在 Web 应用中使用Spring时,request、session、global-session 作用域才有效

  • Request :在请求bean范围内,每次 HTTP 请求将会在IOC容器中产生不同的 bean 实例,在请求完成以后, bean会失效并被垃圾回收器回收 。

  • Session:与请求范围类似,确保每个Session中有一个 Bean 的实例,同一个 Session 共享一个 bean 实例,在Session过期后, Bean会随之失效 。

  • global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。全局作用域与Servlet容器中的session作用域效果相同。


9、你用过哪些重要的Spring注解?(高频)

  • IOC相关的注解

1.创建Bean注解:@Component @Service @Repository @Controller

2.依赖注入注解

@Autowired和@Qualifier

-- @Autowired - 用于在 spring bean 中自动装配依赖项。通过类型来实现自动注入bean。和@Qualifier注解配合使用可以实现根据name注入bean。

-- @Qualifier和@Autowired一块使用,在同一类型的bean有多个的情况下可以实现根据name注入的需求。

@Value

3.作用范围注解:@Scope - 用于配置 Spring bean 的范围

4.条件装配:@Conditional

5.配置类和组件扫描

@Configuration

@Bean

@ComponentScan

@Import

6.延迟加载

@Lazy,标注在类上,表示延迟创建bean。第一次使用创建

@Lazy,标注在属性或者方法参数上,先给代理对象

7.配置文件加载:@PropertySource

  • 核心:@Order控制Bean的执行顺序

  • 事务

@EnableTransactionManager:开启平台事务管理器

@Transactional

  • 任务调度,异步

@EnableShaduling:@Schaduled

@EnbaleAync:@Aync

  • 切面注解:

@EnableAspectJAutoProxy

@Aspect,@Before,@After,@Around,@Pointcut - 用于切面编程(AOP)

  • Spring Cache相关注解

@EnableCaching:开启缓存注解功能

@Cacheable:在方法没有执行前,spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中

@CachePut:将方法的返回值放到缓存中

@CacheEvict:将一条或多条数据从缓存中删除


10、@Autowied和@Qualifier,@Resource区别 ?(高频)

@Autowird和@Qualifier是Spring提供的注解,@Resource是jdk在jsr250中定义的注解

  • @Autowired:按照类型注入,如果IOC容器中有多个bean的类型和注入的属性类型一致,可能会报错;

判断你的bean是否有加属性autowireCandidate为false,优先排除这些bean;

在排除后的bean中,看那个bean加了@Primary,优先使用该bean;

将属性的名称作为bean的id进行注入;

@Autowired + @Qualifier可以根据id注入

@Autowired
@Qualifier("adUserMapper")
  • @Resource:该注解等价于@Autowired + @Qualifier


11、Spring的一级缓存、二级缓存和三级缓存,如何理解?(高频)

Spring Framework 中并不存在一级缓存、二级缓存和三级缓存的概念。这些概念通常是用于描述Hibernate 框架中的缓存机制。

在 Hibernate 中,

一级缓存:指的是 Session 级别的缓存,它是默认开启的缓存机制,可以减少重复查询数据库的次数。

二级缓存:是全局级别的缓存,它可以跨多个 Session 对象使用,从而更加有效地利用缓存提升应用性能。

三级缓存:是基于集群的缓存,通过共享二级缓存数据,使得多个节点间可以共享缓存数据,降低缓存失效的风险。

需要注意的是,Hibernate 的缓存机制与 Spring 并不是直接相关的,Spring 主要通过对各种数据访问技术的封装来提供缓存支持,如对 JdbcTemplate 和 MyBatis 的支持等,它们各自有自己的缓存机制。同时,Spring 还提供了一个抽象的 Cache 接口,用于支持各种缓存实现的统一管理。


12、请解释一下Spring框架有哪些自动装配模式,它们之间有何区别?(高频)

spring的自动装配功能的定义:无须在Spring配置文件中描述JavaBean之间的依赖关系(如配置<property>、<constructor-arg>)。

自动装配模式:

1、no:这是 Spring 框架的默认设置,在该设置下自动装配是关闭的,开发者需要自行在 bean 定义中用标签明确的设置依赖关系 。

2、byName:该选项可以根据bean名称设置依赖关系 。 当向一个bean中自动装配一个属性时,容器将根据bean的名称自动在在配置文件中查询一个匹配的bean。 如果找到的话,就装配这个属性,如果没找到的话就报错 。

3、byType:该选项可以根据 bean 类型设置依赖关系 。 当向一个 bean 中自动装配一个属性时,容器将根据 bean 的类型自动在在配置文件中查询一个匹配的 bean。 如果找到的话,就装配这个属性,如果没找到的话就报错 。

4、constructor :构造器的自动装配和byType模式类似,但是仅仅适用于与有构造器相同参数的bean,如果在容器中没有找到与构造器参数类型一致的bean ,那么将会抛出异常 。

5、default:设置为该值,那么此时在进行自动装配的时候会参考全局的装配方式

比如如下注入:

可以改造为:


13、Spring框架中使用了哪些设计模式及应用场景?(高频)

https://www.bilibili.com/video/BV15b4y117RJ?p=174


二、 AOP原理

14、Spring中AOP的底层是怎么实现的?(高频)

Spring中AOP底层的实现是基于动态代理进行实现的。

常见的动态代理技术有两种:JDK的动态代理和CGLIB。

两者的区别如下所示:

1、JDK动态代理只能对实现了接口的类生成代理,而不能针对类

2、Cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法进行增强,但是因为采用的是继承,所以该类或方法最好不要声明为final,对于final类或方法,是无法继承的。

Spring如何选择是用JDK还是cglib?

1、当bean实现接口时,会用JDK代理模式

2、当bean没有实现接口,会用cglib实现

3、可以强制使用cglib


15、切入点、通知、连接点、切面是什么?

切入点:目标对象中要被增强的方法;

通知:要增强的功能;

连接点:目标对象中可以被增强的方法;

切面:切入点和通知的组合


16、Spring AOP代理方式有几种,怎么配置,实现原理是什么 ?(高频)

SpringAOP代理方式有两种:

JDK动态代理:目标类必须实现一个接口

CGLIB动态代理:目标类不能是最终类


17、Spring AOP机制都有哪些应用场景?(高频)

应用场景:

1、统一日志处理

2、统一幂等性的处理

3、spring中内置的事务处理


三、 事务管理

18、Spring事务的实现方式以及原理?(高频)

Spring支持编程式事务管理声明式事务管理两种方式!

事务这个概念是数据库层面的,Spring只是基于数据库中的事务进行了扩展,以及提供了一些能让程序员更加方便操作事务的方式。比如我们可以通过在某个方法上增加@Transactional注解,就可以开启事务。

  • 编程式事务控制

需要使用TransactionTemplate来进行实现,这种方式实现对业务代码有侵入性,因此在项目中很少被使用到。

  • 声明式事务管理:声明式又分为基于xml和基于注解

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

声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中。

在一个方法上加了@Transactional注解后,Spring会基于这个类生成一个代理对象,会将这个代理对象作为bean,当在使用这个代理对象的方法时,检测到这个方法上存在@Transactional注解,那么代理逻辑会先把事务的自动提交设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有出现异常,那么代理逻辑就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务进行回滚。

针对哪些异常,回滚事务是可以配置的,可以利用@Transactional注解中的rollbackFor属性进行配置,默认情况下会对RuntimeException和Error进行回滚。


19、Spring事务的隔离级别

  • spring事务隔离级别就是数据库的隔离级别

read uncommitted(未提交读)

read committed(提交读、不可重复读)

repeatable read(可重复读)

serializable(可串行化)

  • 数据库的配置隔离级别是Read Commited,而Spring配置的隔离级别是Repeatable Read,这时隔离 级别是以Spring配置的为准,如果spring设置的隔离级别数据库不支持,效果取决于数据库


20、什么是事务的传播行为?在Spring框架中都有哪些事务的传播机制?

Spring的事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法对事务的态度。

举例:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。

在Spring中提供了7种事务的传播行为(当前方法对调用者方法事务的态度):

1、required(Spring默认的事务传播类型):如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。

2、requires_new :创建新事务,无论当前存不存在事务,都创建新事务。

3、supports:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。

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

5、mandatory:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

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

7、nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。


21、Spring如何管理事务的?(高频)

Spring事务管理主要包括3个接口,Spring事务主要由以下三个共同完成的:

1、PlatformTransactionManager:事务管理器,主要用于平台相关事务的管理。

主要包括三个方法:

① commit:事务提交。

② rollback:事务回滚。

③ getTransaction:获取事务状态。

2、TransacitonDefinition:事务定义信息,用来定义事务相关属性,给事务管理器PlatformTransactionManager使用

主要包含的方法:

① getIsolationLevel:获取隔离级别。

② getPropagationBehavior:获取传播行为。

③ getTimeout获取超时时间。

④ isReadOnly:是否只读(保存、更新、删除时属性变为false--可读写,查询时为true--只读)

3、TransationStatus:事务具体运行状态,事务管理过程中,每个时间点事务的状态信息。

主要包含的方法:

① hasSavepoint():返回这个事务内部是否包含一个保存点。

② isCompleted():返回该事务是否已完成,也就是说,是否已经提交或回滚。

③ isNewTransaction():判断当前事务是否是一个新事务。


22、Spring事务什么情况下会失效?(高频)

Spring事务的底层原理是通过AOP的环绕通知对切入点方法增强;

1.在代理对象方法中开始开启事务

2.调用切入点方法

3.提交事务|回滚事务;

事务失效的常见场景:

1、异常类型错误:事务默认回滚的是Error或者RuntimeException,而对编译期(Exception中排除RuntimeException剩下的)异常并不会回滚。

public void save() throws Exception {
    this.show();
}

@Transactional
public void show() throws Exception{

    Account account = new Account() ;
    account.setName("itcast");
    account.setMoney(300d);
    accountDao.save(account);

    try {

        // 抛出异常
        int i = 1/0;            // 事务是否回滚?

    }catch (Exception e) {
        throw new Exception("保存订单数据失败") ;
    }

}

这样事务也是不生效的,因为默认回滚的是:RuntimeException,如果你想触发其他异常的回滚,需要在注解上配置一下,如:

@Transactional(rollbackFor=Exception.class)

2、异常在切入点方法中通过try...catch处理掉了:如果切入点方法自己处理异常,导致代理对象方法它不知道目标对象的方法出现了异常;

public void save() {
    this.show();
}

@Transactional
public void show() {

    Account account = new Account() ;
    account.setName("itcast");
    account.setMoney(300d);
    accountDao.save(account);

    try {

        // 抛出异常
        int i = 1/0;            // 事务是否回滚?

    }catch (Exception e) {
        e.printStackTrace();
    }
}

3、AOP的通知中对切入点方法的异常进行try catch:如果先执行事务通知,再执行自定义通知;事务通知中无法知道自定义通知的返回值。

解决:

调整自定义通知和事务通知的顺序,再执行自定义通知;如果先执行事务通知;事务通知就可以获取业务方法抛出的异常;使用@Order注解来调整顺序

4、业务方法不是public的:@Transactional只能用于public的方法上,否则事务会失效。

5、Bean没有被Spring管理:子容器扫描到没有事务控制的service,导致Controller注入service的时候,注入了没有事务控制的service

// @Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    public void updateOrder(Order order) {
        // update order
    }
}

如果此时把 @Service注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被Spring 管理了,导致Controller注入service的时候,注入了没有事务控制的service,事务自然就失效了。

6、调用本类的方法导致事务失效,调用本类的方法,底层是调用目标对象中方法

依赖注入当前代理对象

7、数据库引擎不支持事务:这里以 MySQL为例,其MyISAM引擎是不支持事务操作的,InnoDB才是支持事务的引擎,一般要支持事务都会使用 InnoDB。

8、自身调用问题

public void save() {
    this.show();
}

@Transactional
public void show() {

    Account account = new Account() ;
    account.setName("itcast");
    account.setMoney(300d);
    accountDao.save(account);

    // 抛出异常
    int i = 1/0;            // 事务是否回滚?
}

9、数据源没有配置事务管理器

10、synchronized加在切入点方法失效;

1)在代理对象方法中开始开启事务

2)调用切入点方法

3)提交事务|回滚事务;

4)synchronized只能保证切入点方法的线程安全,切入点方法执行完毕后,锁失效,其他线程的事务就可以进入。


四、 循环依赖

23、请解释一下Spring bean的生命周期?(高频)

Spring Bean的生命周期如下图所示:

1、实例化Bean对象:

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

  • 对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。(BeanDefinition是Spring 中极其重要的一个概念,它存储了bean对象的所有特征信息,如是否单例,是否懒加载,factoryBeanName 等。BeanDefinition对象的创建时通过各种bean的配置信息进行构建 )

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

3、处理Aware接口:接着Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:

  • 如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值。

  • 如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。

  • 如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文。

4、Bean的后置处理器(BeanPostProcessor):如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization方法。由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;

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

6、Bean的后置处理器(BeanPostProcessor):如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;

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

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

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

示例代码:

User类:

@Component
@Slf4j
public class User implements BeanNameAware , BeanFactoryAware , ApplicationContextAware {

    public User() {
        System.out.println("a的构造方法执行了.........");
    }

    private String name ;

    @Value("张三")
    public void setName(String name) {
        System.out.println("setName方法执行了.........");
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("setBeanName方法执行了.........");
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("setBeanFactory方法执行了.........");
    }

    @PostConstruct
    public void init() {
        System.out.println("init方法执行了.................");
    }

    @PreDestroy
    public void destory() {
        System.out.println("destory方法执行了...............");
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("setApplicationContext方法执行了........");
    }

}

Bean的后置处理器(BeanPostProcessor):

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization方法执行了.........");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization方法执行了.........");
        return bean;
    }

}

测试类:

ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = applicationContext.getBean(User.class);
System.out.println(user);
applicationContext.close();

控制台输出结果:

Generic bean: class [com.itheima.user.domain.User]; scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [E:\idea-edu-workplace\spring\target\classes\com\itheima\user\domain\User.class]
a的构造方法执行了.........
setName方法执行了.........
setBeanName方法执行了.........
setBeanFactory方法执行了.........
setApplicationContext方法执行了........
postProcessBeforeInitialization方法执行了.........
init方法执行了.................
postProcessAfterInitialization方法执行了.........
com.itheima.user.domain.User@4386f16
destory方法执行了...............

InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法。


24、Spring bean生命周期,实现哪些接口可以自定义生命周期行为?

  • InitializingBean接口

  • DisposableBean接口

  • @PostConstruct 注解 和 @PreDestroy注解

  • xml方式<bean init-method="xmlInit" destroy-method="xmlDestroy"></bean>

  • 如果一个Bean这几种生命周期方法都有的话,那么执行顺序应该是怎样的呢?

Multiple lifecycle mechanisms configured for the same bean, with different initialization methods, are called as follows(为同一个bean配置的具有不同初始化方法的多个生命周期机制调用如下):

1.Methods annotated with @PostConstruct

2.afterPropertiesSet() as defined by the InitializingBean callback interface

3.A custom configured init() method

Destroy methods are called in the same order(销毁方法的调用顺序相同):

1.Methods annotated with @PreDestroy

2.destroy() as defined by the DisposableBean callback interface

3.A custom configured destroy() method

  • 建议在项目中使用@PostConstruct和@PreDestroy,因为InitializingBean和DisposableBean与代码强耦合。基于xml方式我们也可以使用<bean>中的init-method和destroy-method属性


25、Spring中的AOP是在bean生命周期的哪一步实现的?

Spring的AOP是通过AspectJAwareAdvisorAutoProxyCreator来实现的, 该类的类图如下:


26、什么是Spring的循环依赖?(高频)

简单的来说就是A依赖B的同时,B依赖A。在创建A对象的同时需要使用的B对象,在创建B对象的同时需要使用到A对象。如下代码所示:

@Component
public class A {

    public A(){
        System.out.println("A的构造方法执行了...");
    }

    private B b;

    @Autowired
    public void setB(B b) {
        this.b = b;
        System.out.println("给A注入B");
    }
}

@Component
public class B {

    public B(){
        System.out.println("B的构造方法执行了...");
    }

    private A a;

    @Autowired
    public void setA(A a) {
        this.a = a;
        System.out.println("给B注入了A");
    }

}


27、出现循环依赖以后会有什么问题?(高频)

对象的创建过程会产生死循环,如下所示:

在spring中通过某些机制(三级缓存)帮开发者解决了部分循环依赖的问题。


28、Spring如何解决循环依赖的?(高频)

生命周期回顾:

1、Spring扫描class得到BeanDefinition

2、根据得到的BeanDefinition,根据class推断构造方法, 通过反射得到一个对象(原始对象)

3、为原始对象填充属性(依赖注入)

4、如果原始对象中的某一个方法配置的有AOP,则需要针对于该原始对象生成一个代理对象

5、把最终的生成的代理对象放入单例池(singletonObjects)中, 下次getBean时直接从单例池拿即可

当然bean的整个生命周期很复杂, 还有很多的步骤, 这里就不一一列举了。

Spring解决循环依赖是通过三级缓存,对应的三级缓存如下所示:

缓存

源码名称

作用

一级缓存

singletonObjects

单例池; 缓存已经经历了完整声明周期, 已经初始化完成的bean对象

二级缓存

earlySingletonObjects

缓存早期的bean对象(生命周期还没有走完)

三级缓存

singletonFactories

缓存的是ObjectFactory, 表示对象工厂, 用来创建某个对象的

二级缓存的作用:如果要想打破上述的循环 , 就需要一个中间人的参与, 这个中间人就是缓存。

步骤如下所示:

1、实例化A得到A的原始对象

2、将A的原始对象存储到二级缓存(earlySingletonObjects)中

3、需要注入B,B对象在一级缓存中不存在,此时实例化B,得到原始对象B

4、将B的原始对象存储到二级缓存中

5、需要注入A,从二级缓存中获取A的原始对象

6、B对象创建成功

7、将B对象加入到一级缓存中

8、将B注入给A,A创建成功

9、将A对象添加到一级缓存中

三级缓存的作用:

从上面这个分析过程中可以得出,只需要一个缓存就能解决循环依赖了,那么为什么Spring中还需要singletonFactories ?

基于上面的场景想一个问题:如果A的原始对象注入给B的属性之后,A的原始对象进行了AOP产生了一个代理对象,此时就会出现,对于A而言,它的Bean对象其实应该是AOP之后的代理对象,而B的a属性对应的并不是AOP之后的代理对象,这就产生了冲突。 也就是说, 最终单例池中存放的A对象(代理对象)和B依赖的A对象不是同一个。

所以在该场景下, 上述提到的二级缓存就解决不了了。那这个时候Spring就利用了第三级缓存singletonFactories来解决这个问题。

singletonFactories中存的是某个beanName对应的ObjectFactory,在bean的生命周期中,生成完原始对象之后,就会构造一个ObjectFactory存入singletonFactories中,后期其他的Bean可以通过调用该ObjectFactory对象的getObject方法获取对应的Bean。

整体的解决循环依赖问题的思路如下所示:

步骤如下所示:

1、实例化A,得到原始对象A,并且同时生成一个原始对象A对应的ObjectFactory对象

2、将ObjectFactory对象存储到三级缓存中

3、需要注入B,发现B对象在一级缓存和二级缓存都不存在,并且三级缓存中也不存在B对象所对应的ObjectFactory对象

4、实例化B,得到原始对象B,并且同时生成一个原始对象B对应的ObjectFactory对象,然后将该ObjectFactory对象也存储到三级缓存中

5、需要注入A,发现A对象在一级缓存和二级缓存都不存在,但是三级缓存中存在A对象所对应的ObjectFactory对象

6、通过A对象所对应的ObjectFactory对象创建A对象的代理对象

7、将A对象的代理对象存储到二级缓存中

8、将A对象的代理对象注入给B,B对象执行后面的生命周期阶段,最终B对象创建成功

9、将B对象存储到一级缓存中

10、将B对象注入给A,A对象执行后面的生命周期阶段,最终A对象创建成功,将二级缓存的A的代理对象存储到一级缓存中

注意:

1、后面的生命周期阶段会按照本身的逻辑进行AOP, 在进行AOP之前会判断是否已经进行了AOP,如果已经进行了AOP就不会进行AOP操作了。

2、singletonFactories : 缓存的是一个ObjectFactory,主要用来去生成原始对象进行了AOP之后得到的代理对象,在每个Bean的生成过程中,都会提前暴露一个工厂,这个工厂可能用到,也可能用不到,如果没有出现循环依赖去依赖本bean,那么这个工厂无用,本bean按照自己的生命周期执行,执行完后直接把本bean放入singletonObjects中即可,如果出现了循环依赖去依赖了本bean,则另外那个bean执行ObjectFactory提交得到一个AOP之后的代理对象(如果没有AOP,则直接得到一个原始对象)。


29、解决循环依赖,只有一级缓存和三级缓存是否可行?(高频)

不行,每次从三级缓存中拿到ObjectFactory对象,执行getObject()方法又会产生新的代理对象,因为A是单例的,所有这里我们要借助二级缓存来解决这个问题,将执行了objectFactory.getObject()产生的对象放到二级缓存中去,后面去二级缓存中拿,没必要再执行一遍objectFactory.getObject()方法再产生一个新的代理对象,保证始终只有一个代理对象。

总结:所以如果没有AOP的话确实可以两级缓存就可以解决循环依赖的问题,如果加上AOP,两级缓存是无法解决的,不可能每次执行objectFactory.getObject()方法都给我产生一个新的代理对象,所以还要借助另外一个缓存来保存产生的代理对象。


30、构造方法出现了循环依赖怎么解决?(高频)

Spring中大部分的循环依赖已经帮助我们解决掉了,但是有一些循环依赖还需要我们程序员自己进行解决。如下所示:

@Component
public class A {

    // B成员变量
    private B b;

    public A(B b){
        System.out.println("A的构造方法执行了...");
        this.b = b ;
    }
}

@Component
public class B {

    // A成员变量
    private A a;

    public B(A a){
        System.out.println("B的构造方法执行了...");
        this.a = a ;
    }

}

main方法程序:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {

    @Autowired
    private A a ;

    @Test
    public void testTransfer() throws Exception {
        System.out.println(a);
    }

}

控制台输出:

解决方案:使用@Lazy注解

@Component
public class A {

    // B成员变量
    private B b;

    public A(@Lazy B b){
        System.out.println("A的构造方法执行了...");
        this.b = b ;
    }
}

在构造参数前面加了@Lazy注解之后, 就不会真正的注入真实对象, 该注入对象会被延迟加载 , 此时注入的是一个代理对象 。


31、ApplicationContext Refresh的过程(自己看视频)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值