java开发java面试面试精选

文章总结(8)—JAVA体系

2021-08-23  本文已影响0人  小胖学编程

1. Spring

  1. Spring中BeanFactory和FactoryBean的区别
  1. 如何自己实现一个IOC容器

IOC容器实现的是服务的发现和注册,其作用和SPI相似。

IOC容器创建.png
  1. 配置文件扫描路径;
  2. 递归包扫描获取.class文件;
  3. 通过反射的方式,创建Object对象;
  4. 属性注入;
  5. 责任链的方式,执行各个BeanPostProcessor,生成代理对象;
  6. 将对象放入缓存池singletonObjects中;
扫描包得到beanDefinition对象.png bean加载流程.png

Spring 是如何管理事务的,事务管理机制?

Spring是如何管理事务的,事务关机机制?以及隔离级别?

Spring的事务管理机制包括:声明式事务和编程式事务。

事务传播行为

Spring基础篇(1)-事务

事务的传播(propagation [ˌprɒpə'ɡeɪʃn])行为是指:如果在开始当前事务之前,一个事务上下文已经存在,此时我们可以有多个选项指定事务性方法的执行行为。

  1. PROPAGATION_REQUIRED:[adj 必须的]默认传播行为,指的是若当前存在事务,则加入该事务;如果当前没有事务,则创建一个新事务。
  2. PROPAGATION_REQUIRES_NEW:[v 需要新的]需要创建一个新的,若当前有事务,则将当前事务挂起。
  3. PROPAGATION_SUPPORTS:[v 支持]当前存在事务,就在事务中运行;当前不存在事务,则不在事务中运行。
  4. PROPAGATION_NOT_SUPPORTED[v 不被支持]不运行在事务中,当前有事务,则挂掉当前事务。
  5. PROPAGATION_NEVER:[adv 绝不]不运行在事务中,如果当前有事务,则抛出异常。
  6. PROPAGATION_MANDARORY[[ˈmændətəri]强制的]`必须运行在事务中,如果当前方法没有事务,则抛出异常。
  7. PROPAGATION_NESTED[[nestɪd] 嵌套的]当前存在事务,则创建一个事务作为当前事务的嵌套事务运行,如果当前没有事务,则创建一个新的事务。

Exception事务回滚的异常

默认配置下,Spring只有在抛出运行时异常(RuntimeException及其子类)或者Error异常时才会回滚,但是可以配置rollbackFor=Exception.class将检查时异常进行回滚。

嵌套事务的回滚

【事务嵌套调用】事务A方法调用事务B方法;
【事务B出现异常】事务A捕获事务B的异常(其实事务A不想回滚);
【事务A回滚操作】最终事务A还是回滚了;

如果事务B失败不影响事务A,可以将事务B的传播行为设置为propagation=Propagation.REQUIRES_NEW

REQUIRES_NEW:当前存在事务,则将事务挂起。如果没有事务,则创建新事务。

Spring AOP的理解,各个术语,他们是怎么相互工作的?

SpringAOP联盟(1)—Advisor,Advice,Pointcut,Advised、ProxyConfig

image.png image.png

SpringAOP联盟(3)—代理工厂

Spring内部真正创建出代理对象是通过ProxyFactory。

AOP的原理

浅析Spring中AOP的实现原理——动态代理

Spring的AOP实现原理是通过动态代理实现的。而Spring的AOP使用了两种动态代理:分别是JDK动态代理和CGLib动态代理。

Spring默认的策略:如果目标类是接口,则使用JDK动态代理,否则使用Cglib来生成代理。

JDK动态代理:

JDK代理注意涉及到java.lang.reflect包下的两个类:Proxy和InvocationHandler。InvocationHandler是一个接口,通过实现该接口定义的横切逻辑,并通过反射机制调用目标类代码,动态将横切逻辑和业务逻辑编制在一起,Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。

CGlib可以在运行期扩展Java类与实现Java接口,本质上是通过字节码增强技术在运行期动态生成新的class。

JDK动态代理优缺点

优点:

  1. JDK动态代理是JDK原生的,不需要任何依赖既可使用;
  2. 通过反射机制生成代理类速度要比cglib操作字节码生成代理类速度更快;

缺点:

  1. 使用JDK动态代理,被代理的类必须实现接口;
  2. JDK动态代理执行代理方法时,需要通过反射机制回调,方法执行效率低;

CGlib动态代理优缺点

优点:

  1. CGlib代理类,无需实现接口;
  2. CGlib代理类执行代理方法时效率要高于JDK的动态代理;

缺点:

  1. CGlib代理类是操作字节码的方式,故生成代理类的速度要比JDK反射的速度要慢。
  2. CGlib代理类使用的是继承,也就意味着需要被代理的类是一个final类,无法使用CGlib代理。
  1. Spring的三级缓存
三级缓存.png

三级缓存的作用:

一级缓存:单例池,即常说的spring容器。

二级缓存:若允许循环依赖,将singletonFactory存入二级缓存。目的是延迟加载,真正发生循环依赖时对对象进行代理操作。

三级缓存:防止二级缓存多次处理,并且标示发生过循环依赖,以便后续spring自检。

  1. bean创建的流程
bean创建的流程.png

关键点:三级缓存中未获取到bean,然后登记Bean正在创建。

  1. 三级缓存的调用流程
三级缓存调用流程.png

关键点:有一个标识Bean是否在创建。这个标识决定了是去创建bean,还是去earlySingletonObjects(第三级缓存)中获取bean。

而这个标识是第一次在三级缓存中未获取到对象时,登记Bean正在创建。

  1. bean什么时候被AOP代理
二级缓存做的工作.png
  1. 为什么会出现自检异常

若循环依赖,提前会进行AOP代理,生成了earlySingletonObject代理对象。并且注入到Bean中。若正常流程下又获取到了新的代理对象,那么Spring便不知道以哪个代理版本为主。便会抛出自检异常。

  1. 创建BeanDefinition流程
BeanDefinition的创建.png

registry [ˈredʒɪstri] 乱着死捶

  1. bean生命周期回调的顺序
bean生命周期回调逻辑.jpeg
  1. SpringMVC原理
SpringMVC流程.png
组件 名称 作用
DispatcherServlet 前端控制器 接受请求,响应结果
HandlerMapping 处理器映射器 根据请求URL查找handler,获取HandlerExecutionChain
HandlerAdapter 处理器适配器 按照特定规则,去执行对应的handler
Handler 处理器(Controller) 接受用户请求,调用业务处理方法
ViewResolver 视图解析器 进行视图解析,将逻辑视图解析为物理视图
View 视图 将数据展示给用户的页面,例如JSP、freemarker

Handler的注册

Spring源码篇(1)—RequestMappingHandlerMapping(Handler的注册)
Spring源码篇(2)—RequestMappingInfo与RequestCondition(Handler—映射)
SpringBoot2.x—定制HandlerMapping映射规则

项目启动后,Spring可以获取到容器中所有Bean对象。而RequestMappingHandlerMapping将处理@RequestMapping标签完成handler的注册(存储到map中)。

  1. 根据@Controller和@RequestMapping标签筛选Bean。
  2. 根据@RequestMapping标签来筛选Bean中所有方法,将@RequestMapping标签解析为RequestMappingInfo对象。
  3. RequestMappingInfo和method对象进行注册。

如何定制HandlerMapping映射规则

Request不仅可以与@RequestMapping属性进行匹配(当然匹配规则是固定的),还可以使用自定义规则进行匹配。

在@RequestMapping类/方法上使用自定义注解。这样在解析含义@RequestMapping注解的类/方法时,用户便可以解析自定义注解的值,从而创建自定义的RequestCondition对象,选择出优先级最高的HandlerMethod对象。

handlingMapping的原理

根据上述描述,HandlerMapping实际上完成了两件工作:

  1. 根据请求获取到HandlerMethod,即定位到最优的Controller层的方法进行处理(即HandlerMethod);
  2. 将HandlerMethod与Interceptor封装为一个HandlerExecutionChain。
handlerMapping流程.png

Spring生命周期回调原理?

Spring提供了destroy [dɪˈstrɔɪ]方法用于生命周期的回调。其本质是使用ShutdownHook实现的。

JDK提供了Java.Runtime.addShutdownHook(Thread hook)方法,允许用户注册一个JVM关闭的钩子。这个钩子可以在以下几种场景被调用:

程序正常退出;
使用System.exit();
终端使用Ctrl+C触发的终端;
系统关闭;
使用kill pid命令干掉进程;
一般地发布系统会通过kill命令来停止服务。这个时候服务可以接收到关闭信号并执行钩子程序进行清理工作。

在使用ShutdownHook的时候,我们往往控制不了钩子的执行顺序。java.Runtime.addShutdownHook是对外公开的API接口。在前述场景里面,假若是独立注册钩子,在更复杂的项目里面是不是就没办法保证执行的顺序呢?曾在实际场景中遇到过这样的问题,从kafka队列消费消息,交给内部线程池去处理,我们自定义了线程池的拒绝策略为一直等待(为了保证消息确实处理),然后就会偶尔出现服务无法关闭的问题。原因正是线程池先被关闭,kafka队列却还在消费消息,导致消费线程一直在等待。

Java服务实现优雅的关闭:ShutdownHook/Signal回调

ShutdownHook的缺陷是无法控制钩子的执行顺序。

引申:除了shutdownHook外还有其他的方式实现服务关闭的回调吗?

Java同时提供了signal信号机制,我们的服务也可以接收到关闭信号。

使用Signal机制有以下原因:

ShutdownHook执行顺序无法保障,第三方组件也可能注册,导致业务自定义的退出流程依赖的资源会被提前关闭和清理;
Signal是非公开API,第三方组件基本很少使用,我们可以在内部托管服务关闭的执行顺序;
在完成清理工作后可以执行exit调用,保证资源清理不会影响ShutdownHook的退出清理逻辑;
这里核心的原因还是希望能完全保证服务关闭的顺序,避免出现问题。

引申:服务下线时,JDK线程池或者Spring线程池会shutdown()还是shutdownNow()去关闭线程池?

  1. JDK原始的线程池在服务下线的时候,不会调用shutdown()或者shutdownNow()相关API来销毁线程池;
  2. Spring的线程池在服务下线的时候,会调用destroy方法去调用shutdown()或者shutdownNow()方法关闭线程池。默认情况下Spring采用的是shutdownNow()关闭。

mybatis

mybatis的缓存

Mybatis一级缓存:sqlSession级别。

第一次发出查询sql语句,sql查询结果写入sqlSession的一级缓存中,缓存使用的数据结构是一个map。

key:MapperId+offset+limit+sql+入参
value:用户信息;

同一个sqlsession再次发出相同的sql,就从缓存中取出数据。如果两次中间出现commit操作(增改删),本sqlSession的一级缓存区域全部清空。

Mybatis二级缓存:mapper级别

二级缓存范围是Mapper级别(mapper是同一个命名空间),mapper以命名空间为单位创建缓存,结构是map。Mybatis的二级缓存通过CacheExecutor实现,CacheExecutor其实是Executor代理对象。所有的查询操作,在CacheExecutor中都会先匹配缓存是否存在。

image.png

JAVA基础

  1. 抽象类和接口的区别
  1. 性质不同

抽象类:是对具体对象的抽象;
接口:是一种行为规范;

  1. 其他特点

继承与实现:抽象类只能单继承,而接口可以多实现。

属性:抽象类中成员变量可以被不同的修饰符来修饰,而接口中的成员变量默认都是静态变量。

构造方法:抽象类中可以含有构造方法,构造方法的作用就是实例化成员变量。而接口中因为均是静态变量,所以没有构造方法。(注意构造方法作用就是实例化成员变量,new关键字才是创建对象)。

方法:抽象类中可以含有具体方法,而接口中只存在public的抽象方法。

JAVA基础篇(14)— 接口与抽象类的区别

  1. 抽象类中为什么存在构造方法(构造函数的作用)

构造函数的作用是类成员属性的初始化。

它和new关键字使用时,可以创建对象。

接口中无构造方法的原因在于接口中的属性均是静态常量。不需要进行初始化操作。

抽象类中存在构造函数便是因为抽象类中含有成员变量。便需要存在构造方法为其赋值。

只是抽象类的构造方法不能和new关键字一起使用,但是子类可以通过super()关键字来引用抽象父类的构造方法。

Java基础篇(11)— 抽象类中为什么存在构造方法

  1. 说一下你代码设计的思路
  1. 资源设施层(解耦):Repository层技术可能会发生一下的演变:单库单表->redis+DB查询->分库分表->拆分微服务RPC调用这么演变,技术类型可能经由:jdbc Template->mybatis->jooq等演变。所以基础设施层不能和domain层进行强依赖,传输对象不能和某个存储介质强绑定(例如DB的po对象)。所以我们需要在domain层来定义接口,由Repository层来进行实现。这样的话,就可以实现可插拔的替换底层存储介质的能力。
  2. 领域层(内聚):比较好的开发模式,是将某个功能全部内聚为一个领域服务。由这个领域服务对外提供所有的能力,这样就实现了某个功能的内聚性。
  3. 纵向抽取(开闭原则):回到某个领域服务中,此时domain层的代码可以实现纵向抽取。使用模板方法模式抽取大量公用逻辑,子类去实现个性化逻辑。然后通过枚举类来维护各个策略子类,通过入参路由到具体的子类来完成业务逻辑。
  4. 数据校验(内聚):数据校验分为两类,一类是无状态的校验(例如NPE校验和url)可以将逻辑写在DP中(以实现充血对象);一类是有状态的校验,必须借助其他类完成(例如校验id是否存在数据库中),可以将逻辑写在domain层;
  5. 防腐层(解耦):调用第三方接口时,为了不强依赖而破坏我们的代码,可以在中间加一层适配层,这一层的作用可以完成参数转换、结果缓存、兜底等逻辑。

大白话讲明白—DDD(领域驱动设计)如何从0到落地

记一次生产事故—JIT编译与CPU使用率飙升

记一次生产事故—JIT编译与CPU使用率飙升

为什么匿名内部类访问局部变量要使用final修饰

错误答案:局部变量如果没有用final修饰,他的生命周期和方法的生命周期是一样的,当方法弹栈,这个局部变量也会消失,那么如果局部内部类对象还没有马上消失想用这个局部变量,就没有了,如果用final修饰会在类加载的时候进入常量池,即使方法弹栈,常量池的常量还在,也可以继续使用。

分析:一个变量加上final难道可以延长生命周期吗?那么岂不是加上final便可以造成短暂的内存泄漏?实际上,传入的内部变量是匿名内部类的成员变量(通过构造函数传入)。

变量被回收不是因为方法被执行完毕,而是GC Root是否持有对象的引用,事实上变量作为局部内部类构造参数传入,仍然可达,故final并不是延长变量生命周期。

正确答案:为了保证内部类和外部类变量的一致性,在内部类中对变量的修改也不会影响到外部类的外部方法。

java内部类—匿名内部类访问局部变量要使用final修饰

String#intern方法作用

String类型的常量池比较特殊。它的主要使用方法有两种:

  1. 直接使用双引号声明出来的String对象会直接存储在常量池中。
  2. 如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中

intern方法可以极大的减少内存使用空间。但是不当是的使用intern方法,就会导致性能急剧下降(内存泄露)。
Jackson2.x中内存泄露的风险点—封装的intern逻辑

String#intern内存泄露的具体案例

Jackson2.9+JDK1.8反序列化的对象为Map<Long,String>,但是key为userId,不收敛。这些userId的字符串都会进入常量池,由于G1的bug,GC时没有被回收,导致内存持续泄露。

key一般是固定的,若使用intern来处理,那么会大大节约反序列化时的空间,但是Map<Long,String>中的key因为是userId,所以会将大量的数据放入常量池中,从而导致内存泄露。

其他

  1. agent是什么?

虚拟机级别的AOP功能,实现了对字节码增强的功能。

  1. 如何破坏单例模式

相关文章

Spring精华篇(1)— druid配置导致循环依赖(自检异常)

Spring源码解析:BeanFactory深入理解

项目实战—那些年常用的单例模式

上一篇下一篇

猜你喜欢

热点阅读