Java面试题总结(SpringMVC相关)

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

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

Spring中循环依赖的场景:1、构造器的循环依赖 2、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 singletonObjects = new ConcurrentHashMap(256);

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

/** Cache of early singleton objects: bean name --> bean instance */
// 提前曝光的单例对象的cache
private final Map earlySingletonObjects = new HashMap(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功能

Java面试题总结(SpringMVC相关)_第1张图片

2、BeanFactory和ApplicationContext区别:

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

Spring容器提供的扩展方法:

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

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

2)BeanPostProcessor接口

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

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

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返回给请求者

Java面试题总结(SpringMVC相关)_第2张图片

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

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

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

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

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

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

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

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

5、Spring事务

1、编程式事务,在代码中编写 2、声明式事务,配置文件中配置 (基于xml的和基于注解的声明式事务)

隔离级别:

TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.

TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读

TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生

TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。

TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
事务的传播行为:

事务传播行为:propagation

1、支持当前事务的情况

required:如果当前存在事务,则加入该事务,如果没有事务,则创建一个新的事务(默认配置是这个)

supports:如果当前存在事务,则加入事务,如果当前没有事务,则以非事务的方式继续运行

mandatory(强制):如果当前存在事务,则接入该事务,如果当前没有事务,则抛出异常

2、不支持当前事务的情况

requires_new:创建一个新的事务,如果当前存在事务,则把当前事务挂起

not_supported:以非事务方式运行,如果当前存在事务,则把当前事务挂起

propagation_never:以非事务方式运行,如果当前存在任务,则抛出异常

3、其他情况

nested:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行,如果当前没有事务,则等价于required

适用情况:事务1方法里面有个方法定义了事务2,如果想要事务2方法异常时回滚,同时不影响事务1执行,则可以采用nested嵌套事务模式

rollbackfor=Exception.class 如果不配置rollbackfor,则只有在碰到RuntimeException或者error时才会回滚,,如果加上上述配置,则碰到异常时就会回滚

如果用的是aop代理,则@Transaction 只有应用到public方法才有效,并且会存在自调用问题,即只有被外部调用,目标方法才会由Spring生成的代理对象来管理,如果同一类中没有@Transaction的方法内部调用了有@Transaction的方法,则此事务会被忽略

解决办法就是由AspectJ取代aop代理

事务的原理:基于AOP,如果一个类或者public方法上标注了@Transactional,Spring容器会在启动的时候为其创建一个代理类,然后在调用此方法的时候,实际调用的是TransactionInterceptor的invoke方法,这个方法的作用就是在目标方法之前开启事务,在方法执行过程中如果遇到异常则回滚事务,调用完成后提交事务。

JDK和Cglib的区别:

 

Cglib

JDK

是否提供子类代理

是否提供接口代理

是(可强制)

区别

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

实现InvocationHandler 

使用Proxy.newProxyInstance产生代理对象

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

你可能感兴趣的:(java,Spring)