Java面试题总结(SpringMVC相关)

4 篇文章 0 订阅

1、Spring的循环依赖及解决方式:

循环依赖:就是循环引用,两个或者两个以上的bean互相持有对方,最终形成闭环。

Spring中循环依赖的场景:1、构造器的循环依赖 2、field属性的循环依赖  Spring可以解决field属性的循环依赖,并且必须是单例模式

如何检测:bean在创建的时候会给bean打标,如果递归调用回来发现正在创建中,则说明循环依赖了

具体:Spring容器会将所有正在创建的bean标识符放在一个“当前创建bean池”中,即singletonsCurrentlyInCreation。如果在创建bean过程中发现自己已经在“当前创建bean池”里时,将抛出BeanCurrentlyInCreationException异常表示循环依赖,bean创建完毕后将从singletonsCurrentlyInCreation中清除掉

以单例对象的初始化为例,右图为具体实例化的图

bean初始化

1、createBeanInstance 实例化,其实就是调用对象的构造方法实例化对象

2、populateBean 填充属性,这一步主要是对bean的依赖属性进行填充

3、initializeBean 调用Spring xml中的init方法

循环依赖主要发生在第一二步,即构造器循环依赖和field循环依赖

如何解决:三级缓存

/** Cache of singleton objects: bean name --> bean instance */
// 单例对象的cache
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

/** Cache of singleton factories: bean name --> ObjectFactory */
// 单例对象工厂的cache
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

/** Cache of early singleton objects: bean name --> bean instance */
// 提前曝光的单例对象的cache
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

我们在创建bean的时候,首先就会从cache中获取这个单例的bean,这个cache即为singletonObjects,具体方法为:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1、从一级缓存中取bean
    Object singletonObject = this.singletonObjects.get(beanName);
// 2、如果获取不到,并且对象正在创建中(即还没有初始化完成,比如A在populateBean的过程中依赖了B对象,得先去创建B对象,这时候A即处于创建中状态)
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
// 3、从二级缓存中获取,如果获取不到,并且允许singletonFactories通过getObject获取,就从三级缓存中获取
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
//4、如果从三级缓存中获取到了,则从singletonFactories中移除,并放到earlySingletonObjects,就是从三级缓存移动到了二级缓存
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

其中三级缓存在createBeanInstance之后被如下方法引用

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

这里就是解决循环依赖的关键:当对象通过createBeanInstance创造出来,但是还没有进行初始化的第二步和第三步,但是已经可以通过对象引用定位到堆中的对象,这时候放到三级缓存中,这样如果A的某个field依赖了B的实例对象,同时B的field依赖了A的实例对象,A先初始化完成第一步,然后发现B还没有被create,所以走B的create,B初始化的时候发现自己依赖了A,于是尝试get(A),这时候可以通过三级缓存拿到A,B拿到A对象后顺利完成初始化123,完成后会放到一级缓存中,这样A也能拿到B的对象进行23初始化,然后放入一级缓存中,完成初始化

所以Spring不能解决:构造器循环依赖问题,因为加入三级缓存的前提是执行了构造器,所以构造器的循环依赖无法解决,而且只能解决单例模式下创建bean的循环引用问题,如果是prototype作用域Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的bean,就无法解决循环依赖。关于bean的作用域:

对于singleton作用域,Spring管理了bean的创建初始化和销毁,但是对于prototype作用域,Spring只负责创建,创建了bean实例后,后续就交给客户端代码管理,即销毁需要客户端来处理,Spring只负责new功能

三级缓存图解:

A对象的创建过程:

1. 创建对象A,实例化的时候把A对象⼯⼚放⼊三级缓存

2. A注⼊属性时,发现依赖B,转⽽去实例化B

3. 同样创建对象B,注⼊属性时发现依赖A,⼀次从⼀级到三级缓存查询A,从三级缓存通过对象⼯⼚拿到A,把A放⼊⼆级缓存,同时删除三级缓存中的A,此时,B已经实例化并且初始化完成,把B放⼊⼀级缓存。

4. 接着继续创建A,顺利从⼀级缓存拿到实例化且初始化完成的B对象,A对象创建也完成,删除⼆级缓存中的A,同时把A放⼊⼀级缓存

5. 最后,⼀级缓存中保存着实例化、初始化都完成的A、B对象

因此,由于把实例化和初始化的流程分开了,所以如果都是⽤构造器的话,就没法分离这个操作,所以都是构造器的话就⽆法解决循环依赖的问题了。

为什么一定要三级缓存,二级是否可以:

不可以,因为AOP的原因,生成的可能是代理对象

三级缓存singletonFactory是传入的一个匿名内部类,调用ObjectFactory.getObject()最终会调用getEarlyBeanReference方法,如果bean被AOP切面代理则返回的是beanPorxy对象,否则就是bean实例,而如果是代理,每次都会产生一个新的代理对象,这样就会有问题,因为对象是单例的,所以需要二级缓存,将三级缓存产生的对象放到二级缓存中去,后面直接去二级缓存拿即可,避免再执行一遍singletonFactory.getObject()方法再产生一个新的代理对象,保证始终只有一个代理对象

关键代码:

2、BeanFactory和ApplicationContext区别:

1、BeanFactory是Spring最底层的接口,只提供了最简单的容器功能,即实例化对象和拿对象,而ApplicationContext(应用上下文),继承了BeanFactory接口,提供了更多功能,比如国际化,访问资源,载入多个上下文,消息发送,响应机制,AOP等  2、BeanFactory在启动的时候不会实例化bean,只有从容器中拿Bean的时候才会去实例化,而ApplicationContext在启动的时候就把所有的bean实例化了,不过可以为bean配置lazy-init=true来让bean延迟实例化

FactoryBean 和 BeanFactory有什么区别?

BeanFactory 是 Bean 的⼯⼚, ApplicationContext 的⽗类,IOC 容器的核⼼,负责⽣产和管理 Bean对象。

FactoryBean是个bean,在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式,是一个可以生产对象和装饰对象的工厂bean,由spring管理后,生产的对象是由getObject()方法决定的(从容器中获取到的对象不是
“ FactoryBeanTest ” 对象)。

 


 */
public interface FactoryBean<T> {

//创建的具体bean对象的类型
	@Nullable
	T getObject() throws Exception;

 //工厂bean 具体创建具体对象是由此getObject()方法来返回的
	@Nullable
	Class<?> getObjectType();
	
  //是否单例
	default boolean isSingleton() {
		return true;
	}

}

Spring容器提供的扩展方法:

0)BeanFactoryPostProcessor接口(在容器启动阶段)

1)各种的Aware接口,可以获取Spring框架的一些对象,比如ApplicationContextAware,可以获取ApplicationContext对象,并对这些对象做自定义初始化操作

2)BeanPostProcessor接口

3)InitializingBean接口(@PostConstruct, init-method)

4)DisposableBean接口(@PreDestroy, destory-method)

@PostConstruct和InitializingBean的执行顺序

Constructor > @PostConstruct > InitializingBean > init-method

原因是PostConstruct在BeanPostProcessor前置处理器中就被执行了

https://blog.csdn.net/chendaoqiu/article/details/50700246

3、SpringMVC 工作原理:

流程说明:1、客户端(浏览器)发送请求,请求到DispatcherServlet 

2、DispatcherServlet根据请求信息调用HandlerMapping,解析对应的Handler ,然后会返回一个包含interceptor,handler等的执行链

3、解析到对应的handler(也就是controller)以后,开始由HandlerAdapter适配器处理

4、HandlerAdapter根据handler来调用真正的处理器来处理请求,并处理相应的业务逻辑

5、处理完请求后,会返回一个ModelAndView对象,model是返回的数据对象,view是逻辑上的view

6、ViewResolver 会根据逻辑view来查找实际的view

7、DispaterServlet把返回的model传给view(视图渲染)

8、把view返回给请求者

4、Spring用到了哪些设计模式

1、工厂设计模式,Spring通过BeanFactoryApplicationContext 创建bean对象

2、代理设计模式, AOP功能的实现

3、单例设计模式,Spring 中的bean默认为单例

4、模版方法模式,Spring中jdbcTemplatehibernateTemplate等以template结尾的类,使用到了模版模式

5、包装器设计模式,连接多个数据库

6、观察者模式,Spring的事件驱动模型

7、适配器模式 Spring AOP中的增强或者通知,controller也用到了适配器模式

5、SpringBean的⽣命周期

SpringBean ⽣命周期简单概括为4个阶段:

1. 实例化,创建⼀个Bean对象

2. 填充属性,为属性赋值

3. 初始化

如果实现了xxxAware 接⼝,通过不同类型的Aware接⼝拿到Spring容器的资源,如果实现了BeanPostProcessor接⼝,则会回调该接⼝的postProcessBeforeInitialzation 和postProcessAfterInitialization ⽅法。如果配置了init-method ⽅法,则会执⾏init-method 配置的⽅法

4. 销毁

容器关闭后,如果Bean实现了DisposableBean 接⼝,则会回调该接⼝的destroy ⽅法。如果配置了destroy-method ⽅法,则会执⾏destroy-method 配置的⽅法

6、JDK和Cglib的区别:

JDK 动态代理主要是针对类实现了某个接⼝,AOP 则会使⽤ JDK 动态代理。他基于反射的机制实现,⽣成⼀个实现同样接⼝的⼀个代理类,然后通过重写⽅法的⽅式,实现对代码的增强。

⽽如果某个类没有实现接⼝,AOP 则会使⽤ CGLIB 代理。他的底层原理是基于 asm 第三⽅框架,通过修改字节码⽣成成成⼀个⼦类,然后重写⽗类的⽅法,实现对代码的增强。

 

Cglib

JDK

是否提供子类代理

是否提供接口代理

是(可强制)

区别

必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法

实现InvocationHandler 

使用Proxy.newProxyInstance产生代理对象

被代理的对象必须要实现接口

 

7、Spring AOP 和 AspectJ AOP 有什么区别?

Spring AOP 基于动态代理实现,属于运⾏时增强。

AspectJ 则属于编译时增强,主要有3种⽅式:

1. 编译时织⼊:指的是增强的代码和源代码我们都有,直接使⽤ AspectJ 编译器编译就⾏了,编译之后⽣成⼀个新的类,他也会作为⼀个正常的 Java 类装载到JVM。

2. 编译后织⼊:指的是代码已经被编译成 class ⽂件或者已经打成 jar 包,这时候要增强的话,就是编译后织⼊,⽐如你依赖了第三⽅的类库,⼜想对他增强的话,就可以通过这种⽅式

3. 加载时织⼊:指的是在 JVM 加载类的时候进⾏织⼊。

总结下来的话,就是 Spring AOP 只能在运⾏时织⼊,不需要单独编译,性能相⽐ AspectJ 编译织⼊的⽅

式慢,⽽ AspectJ 只⽀持编译前后和类加载时织⼊,性能更好,功能更加强⼤。

8、Springboot启动流程

1、执行注解SpringBootApplication,此注解包括三个注解(@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan)

2、初始化SpringApplication,加载各种资源,比如ApplicationContextInitializer,ApplicationListener,具体方式是通过SpringFactoriesLoader加载META-INF/spring.factories下的配置,比如如下配置

3、执行SpringApplication.run(),执行listeners.starting()启动监听器模块, 准备环境,根据不同的环境创建不同的Environment

2. 准备、加载上下⽂,为不同的环境选择不同的Spring Context,然后加载资源 加载所有配置了EnableAutoConfiguration的资源,配置Bean

3. 初始化,这个阶段刷新Spring Context,启动应⽤

4. 最后结束流程

https://www.cnblogs.com/theRhyme/p/11057233.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值