java面试

1:Spring中有哪些方式可以把Bean注入到IOC容器中?

答:1,使用xml的方式来声明Bean的定义Spring容器在启动的时候会加载并解析这个xml,把bean装在到IOC容器中。2,使用@CompontScan注解来扫描声明了@Controller,@Service,@Repository,@Component注解的类。3,使用@Configuration注解声明配置类,并使用@Bean注解来实现Bean的定义,这种方式其实是xml的配置放还是的一种演变。是Spring迈入到无配置化时代的里程碑。4,使用@Import注解,导入配置类和普通Bean。5,使用FactoryBean工厂bean,动态构建一个Bean实例,SpringCloud OpenFeign里面的动态代理实例就是是同FactoryBean来实现的。6,实现ImportBeanDefinitionRegistrar接口,可以动态注入Bean实例,这个在SpringBoot里面的启动注解用到。7,实现ImportSelector接口,动态批量注入配置类或者Bean对象,这个在Spring Boot里面的自动装配里面有用到。

2:Redis的缓存穿透:客户端请求的数据在数据库和缓存中都不存在,这样的缓存永远不会生效,都会打击到数据库中。解决方案①缓存空对象:数据库直接在redis中写空数据,缓存起来。

②布隆过滤

③:增强id的复杂度,避免被猜测id的风险

④:做好基础的数据格式校验

⑤:做好热点参数的限流

缓存雪崩:是同一时间内大量的缓存key失效或者redis宕机,导致大量的请求达到数据库,

解决方案: ①:给不同的key的TTL添加随机值 ②:利用redis集群提高服务的可用性 ③:给缓存业务添加降级限流策略  ④:给业务添加多级缓存。

缓存击穿:缓存击穿问题也叫热点key问题,就是一个被高并发访问并且缓存重建业务比较复杂的key突然失效,无数的请求会在瞬间给数据库带来巨大的冲击

常见的解决方案:①互斥锁 ②逻辑过期 

互斥锁:

线程一在查询缓存时没有命中,就会获取锁,进行缓存重建,其他的线程在查询时会获取锁失败,休眠后在重试这个步骤,实现了请求不到数据库,但因为互斥锁的原因导致效率很低。

        

逻辑过期:

       给缓存设置逻辑过期时间,数据一直存在,在线程一查询缓存时发现逻辑过期时间已经过期,就会获取互斥锁,但它会开启新线程,进行缓存重建,线程一返回旧数据,其他线程访问时发现互斥锁获取失败,就知道有线程在进行缓存重写,它直接返回旧数据。 

3。springboot的自动配置原理 

        遵循约定大约配置的规则,在boot程序启动后,起步依赖中的一些bean对象会自动的注入到ioc容器中

自动配置原理:在主启动类上添加了SpringBootApplication注解,这个注解组合了EnableAutoConfiguration注解,EnableAutoConfiguration注解又组合了import注解,导入了AutoConfigurationImportSelector类,实现selectImports方法,这个方法经过层层调用,最终会读取META-INF目录下的后缀名为imports的文件,boot2.7以前的版本读取的是spring.factories文件,读取到全类名了之后,会解析注册条件,@Conditional及其衍生注解把满足注册条件的Bean对象自动注入到ioc容器中

4。谈谈你对springMVC的理解

springMVC是属于spring frame生态的一个模块,它是在servlet的基础上构建的并且使用了MVC模式设计的一个mvc框架,是为了去简化传统的servlet+JSP模式下的WEB开发方式

主要体现在:1,把传统的MVC框架里面的Controller控制器做了拆分,分成了前端控制器DispatcherServlet和后端控制器Controller,2.把Model模型拆分成业务层Service和数据访问层Repository,3.在视图层,可以支持不同的视图,比如Freemark,velocity,JSP等等

5。springboot前置知识:

        SpringBoot框架在设计之初,为了有更好的兼容性,在不同的运行阶段时期,提供了非常多的扩展点,可以让程序员根据自己的需求,在整个spring应用车光绪运行过程中执行程序员自定义的代码

        ①: ApplicationContextInitializer:ioc容器队形创建完成后执行,可以对上下文环境做一些操作,例如运行环境属性注册等         ~使用~:自定义列,实现ApplicationContextInitializer接口,在META-INF/spring.factories配置文件中配置自定义的类。

        ②:ApplicationListener: ~监听容器发布的事件,允许程序员执行自己的代码,完成事件驱动开发,它可以监听容器初始化完成,初始化失败事件,通常情况下可以使用监听器加载资源,开启定时任务等,        ~使用~:自定义类,实现ApplicationListener接口,在META-INF/spring.factories配置文件中配置自定义的类.

        ③:BeanFactory的作用:Bean容器的根接口,提供Bean对象的创建,配置,依赖注入等功能,BeanFactory常见的两个实现? ApplicationConfigServletServerApplicationContext,DefaultListableBeanFactory。

        ④:BeanDefinition  用于描述Bean,包括Bean的名称,Bean的属性,Bean的行为,实现的接口,添加的注解等等,Spring中,Bean在创建之前,都需要封装成对应的BeanDefinition,如何根据BeanDefinition进一步创建Bean对象。

        ⑤:BeanFactoryPostProcessor  ~Bean工厂后置处理器,当BeanFactory准备好了之后(Bean初始化之前),会调用该接口的postProcessBeanFactory方法,经常用于新增BeanDefinition。

        ⑥:Aware ~ 感知接口,spring提供的一种机制,通过实现该接口,重写方法,可以感知Spring应用程序执行过程中的一些变化,Spring会判断当前的Bean有没有实现Aware接口,如果实现了,会在特定的时机回调接口对应的方法  {(BeanNameAware:Bean名称的感知接口),(BeanClassLoaderAware:Bean类加载器的感知接口),(BeanFactoryAware:Bean工厂的感知接口)}

        ⑦:InitializingBean/DisposableBean

        ~初始化接口,当Bean被实例化好后,会回调里面的函数,经常用于做一些加载资源的工作

        ~销毁接口,当Bean被销毁之前,会回调里面的函数,经常用于做一些释放资源的工作

        ⑧:BeanPostProcessor ~ Bean的后置处理器,当Bean对象初始化之前以及初始化之后,会回调该接口对应的方法。

                AutowiredAnnotationBeanPostProcessor   用来完成依赖注入

                AbstractAutoProxyCreator    用来完成代理对象的创建

                AbstracAdvisingBeanPostProcessor   将AOP中的通知作用于特地特定的Bean上  。

6。springBoot的面试题:

        ①SpringBoot启动流程:

                ~ new SpringApplication()  

                        1.确认web应用的类型。

                        2.加载ApplicationContextInitializer

                        3.加载ApplicationListener

                        4.记录主启动器

                ~run()

                        1.准备环境对象Environment,用于加载系统属性等等.

                        2.打印Banner

                        3.实例化容器Context

                        4.准备容器,为容器设置Environment,BeanFactoryPostProcessor,并加载                                        主类的BeanDefinition。

                        5.刷新容器(创建Bean实例)

                        6.返回容器

        答案:在回答面试题时候采用结构化回答 可以采用总-分-总

        总:SpringBoot启动,其本质就是加载各种配置信息,如何初始化ioc容器后并返回

        分:在其启动的过程中会做这么几个事情:

                        首先,当我们在启动类执行SpringApplication.run这行代码后,在它的方法内其实会做两件事        1:创建SpringApplication对象;2:执行run方法

                        其次,在创建springApplication对象的时候,在它的构造方法内部主要做三件事。

                                  1.确认web应用类型,一般情况下是servlet类型,这种类型的应用,将来                                             会自动启动tomcat。

                                   2.  从spring.factories配置文件中,加载ApplicationContextInitializer                                                       和ApplicationListener。

                                   3.记录当前应用的的主启动类,以后做包扫描用

                        最后:对象创建好了之后会调用该对象的run方法,在run方法内部主要做这四件事

                                        1.准备好Environment对象,它里面会封装一些当前应用运行环境的参数,例如环境变量

                                        2.实例化容器,这仅仅是创建ApplicationContext对象。

                                        3.容器创建好了以后,会为容器做一些准备工作,比如为容器设置Environment,BeanFactoryPostProcessor后置处理器,并且加载主类对应的Definition。

                                        4.刷新容器,就是我们说的refresh,在这里是真正的创建Bean实例

                           总: 总结一下我刚说的,其实SpringBoot启动的时候核心就两步,创建SpringApplication对象,以及run方法的调用,在run方法中会真正的实例化容器,并且创建容器需要的Bean实例,最终返回容器

②IOC的初始化流程:

       简单概述:AbstractApplicationContext.refresh();

                        1.准备beanFactory(DefaultListableBeanFactory)

                         .设置ClassLoader

                         .设置Environment

                        2.扫描要放入容器的Bean,得到对应的BeanDefinition(只扫描,并不创建)

                        3.注册BeanPosstProcessor

                        4.处理国际化

                        5.初始化事件多播器ApplicationEventMulticaster

                        6.启动tomact

                        7.绑定事件监听器和事件多播器

                        8.实例化非懒加载的单例Bean

                        9.扫尾工作,比如情况实例化时占用的缓存等

        回答:

                总:IOC容器的初始化,核心工作是在AbstractApplicationContext.refresh方法中完成的

                分:在refresh方法中主要做了这么几件事

                                1.:准备BeanFactory,在这一块需要给BeanFactory设置很多属性,比如类加载器,Environment。

                                 2.:执行BeanFactory后置处理器,这一阶段会扫描要放入容器中的bean信息,得到对应的BeanDefinition(注意,这里是扫描,不创建)

                                3.接下来是注册BeanPostProcessor,我们自定义的BeanPostProcessor就是在这一个阶段被加载的,将来Bean对象实例化好后,需要用到。

                                4.:启动tomcat

                                5.实例化容器中非懒加载的单例Bean,这里需要说的是,多例Bean和懒加载的Bean不会在这一阶段创建,将来用到的时候在创建。

                                6.当容器初始化完毕后,再做一些扫尾工作,比如清楚缓存等。

                总:简单总结一下,在IOC容器初始化的过程中首先得准备,并执行BeanFactory后置处理器,其次得注册Bean后置处理器,并启动tomcat,最后需要借助于BeanFactory完成Bean的实例化

③ Bean的生命周期
        ~创建对象

                ~实例化(构造方法)

                ~依赖注入

        ~初始化 : 执行Aware接口回调,执行BeanPostProcessor,postProcessBeforeInitization

                            执行InitializingBean回调(先执行@PostConstruct)

                               执行BeanPostProcessor,postProcessAfterInitialization

         ~使用对象

         ~销毁对象

回答:总:Bean的生命周期总的来说有4个阶段,分别由创建对象,初始化对象,使用对象以及销毁对象,而且这些工作大部分是交给Bean工厂的doCreateBean方法完成的

        分:首先,在创建对象阶段,先调用构造方法实例化对象,对象有了以后会填充该对象的内容,其实就是处理依赖注入,其次,对象创建完成后,需要做一些初始化的操作,1.执行Aware感知接口的回调方法,2.执行Bean后置处理器的postProcessBeforeInitialization方法,3.执行InitializingBean接口的回调,在这一步如果Bean中有标注了@OistConstruct注解的方法,先执行它,4.执行Bean后置处理器的postProcessAfterInitialization,把这些扩展点都执行完,Bean的初始化就完成了。接下来,在使用阶段就是程序员从容器中获取该Bean使用即可,最后,在容器销毁之前,会先销毁对象,此时会执行DisposableBean接口的回调,这一步如果Bean中有标注了@PreDestory接口的函数,会先执行它。

总:简单的总结一下,Bean的生命周期包含四个阶段,其中初始化对象和销毁对象我们程序员校验通过一些扩展点执行自己的代码

④:解决Spring中Bean的循环依赖

        总:Bean的循环依赖是指A依赖B,B依赖A,这样的依赖闭环问题,在Spring中,通过三个对象缓存区来解决循环依赖问题,这三个缓存区被定义到了DefaultSingletonBeanRegistry中,分别是singletonObjects用来存储创建完毕的Bean,earlySingletonObjects用来存储未完成依赖注入的Bean,还有SingletonFactories用来存储创建Bean的ObjectFactory。假如说现在A依赖B,B依赖A,整个Bean的创建过程是这样的:

分:首先,调用A的构造方法实例化A,当前A还没有处理依赖注入,暂且把它称为半成品,此时会把半成品封装到一个ObjectsFactory中,并存储到springFactories缓存区,

        接下来,要处理A的依赖注入,由于此时还没有B,所以得先实例化一个B,同样的,半成品B也会被封装到ObjectFactory中,并且存储到springFactory缓存区中,

        紧接着,要处理B的依赖注入了,此时会找到springFactories中A对应的ObjectFactory,嗲用它的getObject方法得到刚才实例化的半成品A(如果需要代理对象,则会自动创建代理对象,将来得到的就是代理对象),把得到的半成品A注入给B,并同时会把半成品A存入到earlySingletonObjects中,将来如果还有其他的类循环依赖了A,就可以直接从earlySingletonObjects中找到它了,那么此时springFactories中创建A的ObjectFactory也可以删除了,至此B的依赖注入处理完成后,B就创建完毕了,就可以把B的对象存入到singetonObjects中了,并且同时删除掉springFactories中创建B的ObjectFactory,B创建完毕后,就可以继续处理A的依赖注入问题了,把B注入给A,此时A也创建完毕了,就可以把A的对象存储到singletonObjects中,并且同时删除earlySingletonObjects中的半成品A,截此为止,A和B的对象全部创建完毕,并且存储到springFactory中,将来通过容器获取对象,都是从singletonObjects中获取,

总:总结一句话,借助于DefaultSingletonBeanRegistry的三个缓存区可以解决缓存依赖问题

⑤SpringMVC的执行流程:

        获取HandlerExecutionChain ---》 获取HandlerAdapter --》执行拦截器的preHandle方法 --》

        执行HandlerAdapter(HandlerMethod) --》执行拦截器的postHandler方法  --》执行异常处理逻辑(全局异常处理器) --》 解析视图(使用response对象响应数据)  --》 渲染视图  --》执行拦截器的afterCompletion

回答:总:使用了SpringMVC后,所有的请求都需要经过DispatcherServlet前端控制器,该类提供了一个doDispatch方法,有关请求处理和结果响应的所有流程都在该方法中完成,

        分:首先,借助于HandlerMapping处理器映射器得到处理器执行链,里面封装了HandlerMethod代表目标Controller的方法,同时还通过一个集合记录要执行的拦截器,接下来是,会根据HadnlerMethod获取对应的HandlerAdapter处理器适配器,里面封装了参数解析器以及结果处理器分别解析浏览器提交的数据以及处理controller方法返回的结果,然后,执行拦截器的preHandle方法,接下来是核心,通过HandlerAdapter处理器适配器执行目标Controller的方法,在这个过程中会通过参数解析器和结果处理器分别解析浏览器提交的数据以及处理Controller方法返回的结果,然后,执行拦截器的postHandler方法,最后处理响应,在这个过程中如果有异常抛出,会执行异常的逻辑,这里还会窒息感全局异常处理器的逻辑,并通过视图解析器ViewResolve解析视图,在渲染视图,最后在执行拦截器的afterCompletion。

1.过滤器和拦截器有什么区别?

        答:1.运行的顺序不同,Filter(过滤器)是在servlet容器接收到请求之后,在servlet调用之前运行的,interceptor(拦截器)是在servlet被调用后,但是在响应被发送到客服端之前来运行的, 2,配置环境不同,过滤器实在web.xml文件中来配置的,拦截器是在spring的配置文件来配置的或者使用注解的方式,3.Filter依赖于servlet容器,Interceptor不依赖servlet容器,4.Filter只能对request和response进行操作,interceptor能对request和response,ModelAndView,handler,exception进行操作,相当于interceptor多了对SpringMVC生态组件的一个操作能力。 

2.如何理解 springBoot的Starter组件

        答:Starter组件是SpringBoot的四大核心功能特性之一,1.starter组件是以功能为维度,来维护对应jar包的版本依赖的,2.会把对应功能的所有jar包依赖全部导进来,避免开发者自己去引入依赖导致的一些麻烦,3.starter内部集成了一个自动装配的机制,也就是说程序依赖对应的starter组件以后,那么这个组件会自动集成到Spring生态里面,对于Bean的管理,也是基于自动装配来完成的 。

3.线程池中execute和submit的区别:

        execute不会去管理线程执行的一个状态,submit我们可以传入一个callable或者返回的是一个 futuretask  future我们可以去管理线程池里面线程的一个执行状态,submit的应用场景:我们线程 的任务完成后,我们再要去做一些任务,我们就可以需要使用submit

4.new String("abc")到底创建了几个对象

答:①:如果没有abc字符串常量,则创建两个对象分别是“abc”字符串常量,new String 实例化对象 ②:如果“abc”字符串常量存在则创建一个对象。就是从字符串常量池里去寻常有没有值相同的sting

5.如何解决hashmap的哈希冲突的?

答:hashmap的数据是由数组来进行存储的,默认长度位16,当要存入数据时,会根据key来计算出hash值,进行取模运算,得到要存储的位置,这个时候就可能会位置相同的现象(哈希冲突)。①采用链式寻址法 在位置冲突处添加链表,每有位置冲突时,就将key挂在链表尾部,当链表长度大于8,且数组大小大于64时,hashmap就会将当前单向链表变为红黑树,从而减少查询的时间。②再hash法  如果当hash函数出现冲突时,就用另一个hash来计算,例如布隆过滤器。

③开放寻址法 就是再放生冲突的位置,向后寻找空数组进行数据存储,这个在ThreadLocal中有体现。④:建立公共溢出区: 也就是把存在冲突的key统一存放在一个公共溢出区中存放

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值