spring常见面试题

介绍一下spring bean的生命周期(面试有被问到)

1.加载配置转化成spring bean的定义。

2.使用jdk反射根据bean的定义创建bean的实例并封装成beanwrapper。

3.执行populateBean()属性填充方法。

4.执行initializeBean()方法进行bean的初始化,在初始化中,如果 Bean 实现了 BeanNameAware 接⼝,调⽤ setBeanName() ⽅法,传⼊Bean的名字。如果 Bean 实现了 BeanClassLoaderAware 接⼝,调⽤ setBeanClassLoader() ⽅法,传⼊ClassLoader 对象的实例。如果Bean实现了BeanFactoryAware接口调用setBeanFactory方法传入AbstractAutowireCapableBeanFactory对象实例信息。

如果有和加载这个 Bean的 Spring 容器相关的 BeanPostProcessor 对象,执⾏ postProcessBeforeInitialization() ⽅法

如果实现了InitializingBean接口,这执行afterPropertiesSet()方法。

如果 Bean 在配置⽂件中的定义包含 init-method 属性,执⾏指定的⽅法。

如果有和加载这个 Bean的 Spring 容器相关的 BeanPostProcessor 对象,执⾏ postProcessAfterInitialization() ⽅法

5.最后执行registerDisposableBeanIfNecessarybean的销毁逻辑,当要销毁 Bean 的时候,如果 Bean 实现了 DisposableBean 接⼝,执⾏ destroy() ⽅法。当要销毁 Bean 的时候,如果 Bean 在配置⽂件中的定义包含 destroy-method 属性,执⾏指定的⽅法。
image

面试的时候,面试官可能不想听你讲具体的细节,这个时候的回答方式。
五步法:创建bean的实例,设置bean的属性,初始化bean,使用bean,销毁bean。
七步法:加载bean的定义,创建bean的实例、依赖注入、属性填充、初始化实例、应用程序使用,注册销毁。

介绍一下spring的三级缓存

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    
    // ....
    /** 单例对象缓存:k:beanName, v: 实例。一级缓存 */
	/** Cache of singleton objects: bean name to bean instance. */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** 单例工厂缓存:k:beanName, v: ObjectFactory 三级缓存*/
	/** Cache of singleton factories: bean name to ObjectFactory. */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/** 早期单例对象缓存:k:beanName, v: 实例 */
	/** Cache of early singleton objects: bean name to bean instance. 二级缓存*/
	private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
    // ....
    
}

创建对象的时候先看看一级缓存中有没有,如果没有再走二级缓存,如果二级缓存中没有,再走三级缓存。

说一下spring怎样利用三级缓存解决循环依赖

1.也就是说第一次会创建出来a对象,但是a对象中的b属性值为null(半成品),然后会将key为a,value为lambda表达式放入到三级缓存中。

然后进行populateBean方法中进行属性填充,说白了就是开始创建B对象。

2.会创建出来b对象,但是b对象中的a属性值为null(半成品)。然后会将key为b,value为lambda表达式放入到三级缓存中。

然后进行populateBean方法中进行属性填充,说白了给b对象中的a属性进行复制。

3.这个时候会走到我们的lambda表达式中,我们之前创建好的半成品的a对象进行返回。同时将三级缓存中的a对象移除掉,将其放入到二级缓存中(说白了二级缓存中放的是一个半成品对象key:a,value:b属性为null)。

这个时候b对象就是一个完整的成品对象了。

4.将完整的成品的b对象放入到一级缓存中,同时将它从三级缓存中移除。

5.然后将半成品中的a对象的b属性进行赋值。这个时候a也成为了成品对象了。

6.最后将成品的a对象放到一级缓存中,将二级缓存中的a对象进行移除。

说一下aop动态代理的原理

AOP(Aspect-Oriented Programming,面向切面编程)能够将那些与业务无关,说白了,他是ioc的扩展,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理。

aop增强逻辑的执行时机是在initializeBean方法中,更确切一点说是在BeanPostProcessor的后置处理方法中执行。

其实底层为目标 bean 查找合适的通知器,并封装成一个排好顺序的List集合。遍历集合中的每个拦截器,并执行拦截器中对应的方法。

https://www.bilibili.com/video/BV1a84y1q7aX/?spm_id_from=333.337.search-card.all.click&vd_source=273847a809b909b44923e3af1a7ef0b1

参考博客:https://pdai.tech/md/interview/x-interview-2.html#_10-1-spring

@configuration注解接单介绍一下

@configuration注解底层其实一是个@Component注解。它主要是能够保证我们生成单例bean。如果不加@configuration注解生成的是普通bean,加上@configuration注解生成的是cglib的代理bean。比方说我们在@configuration注解中通过@Bean注解指定要生成的bean对象,如果我们的bean的类型相同,但是bean的名称不同,那么通过@configuration注解创建出来的是同一个bean对象。

@configuration注解会在beanfactorypostprocessor后置处理器执行的时候加载他。

底层执行原理:

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    this();
    register(componentClasses);
    refresh(); // 走这个方法
}

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

        // Prepare this context for refreshing.
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
            // Invoke factory processors registered as beans in the context.
            // 开始解析我们的@Configuration注解
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);
            beanPostProcess.end();

            // Initialize message source for this context.
            initMessageSource();

            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            onRefresh();

            // .......
        }
    }
}


protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        // 会执行我们的ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry()方法	
		PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

    // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
    // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
    if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
        beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
        beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
    }
}

// 底层会走到parse方法进行@Configuration注解的解析。

https://www.bilibili.com/video/BV1sh411A7K9/?spm_id_from=333.337.search-card.all.click&vd_source=273847a809b909b44923e3af1a7ef0b1

https://www.bilibili.com/video/BV1GP4y177Mr/?spm_id_from=333.337.top_right_bar_window_history.content.click&vd_source=273847a809b909b44923e3af1a7ef0b1

spring事务失效场景:

spring的声明式事务只会帮助我们回滚RuntimeException(也就是运行时异常)的异常。

底层源码其实为我们要执行的方法包了一层大大的try catch。

失效场景:

1.我们在代码中使用try catch 手动捕捉了异常,这样事务就不会回滚了。

2.我们抛出的异常不是RuntimeException的异常,事务也就不会回滚了。

当然,如果如果我们确实需要回滚非RuntimeException 的异常,这个时候我们可以指定@Transactional注解的rollbackfor属性,将其指定为我们要指定的回滚的异常。

3.访问修饰符出错,如果我们被@Transactional修饰的方法的访问修饰符使用的是private(非public)等等,这样会导致事务失效,因为spring 要求被代理方法必须是public的。

4.如果我们被@Transactional修饰的方法被final关键字进行了修饰,也是会导致事务失效的。spring源码关于事务的实现使用到了AOP,而AOP底层的实现是通过jdk或cglib动态代理实现的。

如果某个方法被final修饰了,那么在他的代理类中,spring就没有办法为你重写该方法了,也就没有办法为你实现代理了。

5.如果我们被@Transactional修饰的方法使用了static进行修饰那么也是没有办法进行代理的,原因同上。

6.我们在被@Transactional修饰的方法中调用了本类中的另外一个事务方法,这个时候事务也是会失效的。

原因就是你没有通过spring的方式进行调用,而是自己调用的,没有办法给你进行动态代理增强。

解决方法:我们可以为另一个事务方法再新创建一个service;也可以将本类通过@Autowired的形式再注入到本类中;当然也可以使用

((Userservice)AopContext.currentProxy()).query();来进行调用。

7.如果我们被@Transactional修饰的方法所在的类没有被@Controller、@Service、@Component、@Repository注解修饰,也就以为着,这个类不会被spring容器所接管。自然你的事务也不会生效。

8.比方说在多线程的场景中,一个事务方法调用了另外一个事务方法,但是在在调用另外一个事务方法的时候,你起了一个多线程。如果多线程中的这个方法出现了异常,那么你的第一个方法也是不会进行回滚的。因为两个事务获取到的数据库连接都不一样。

通过spring源码,我们能够知道spring的事务是通过数据库连接来实现的。

9.如果我们要操作的那张表使用的储存引擎是myisam的话,那么事务也是不生效的。

10.我们可以通过@Transactional注解的propagation属性来指定事务的传播行为,spring支持八种事务传播行为。@Transactional(propagation = Propagation.NEVER)这种类型的传播特性不支持事务,如果有事务则会抛异常。

11.如果我们自定义了异常,但是出现事务报错的那个异常又恰恰不是我们自定义的异常,那么事务也是没有办法进行回滚的。

针对异常问题:

在 spring 中为了支持编程式事务,专门提供了一个类:TransactionTemplate,在它的 execute 方法中,就实现了事务的功能。

相较于@Transactional注解声明式事务,我更建议大家使用基于TransactionTemplate的编程式事务。主要原因如下:

避免由于 spring aop 问题导致事务失效的问题。

能够更小粒度地控制事务的范围,更直观。

说一下springmvc的执行流程源码 (面试有被问到)

1、DispatcherServlet表示前置控制器,是整个SpringMVC的控制中心。用户发出请求,Dispatcherservlet接收
请求并拦截请求。
2、HandlerMapping为处理器映射。DispatcherServlet调用HandlerMapping,HandlerMapping根据请求url查找处理器执行链HandlerExecutionChain(里面包含handler和拦截器链)
Handler.
3、根据获取到的Handler匹配对应的HandlerAdapter。

4、执行拦截器的前置处理方法。

4、通过HandlerAdapter,去执行对应的Handler(也就是我们常说的Controller)。首先会通过参数解析器解析所有的参数,然后通过反射执行Handler,最后经过执行会返回一个ModelAndView对象。
5、执行拦截器的后置处理方法。
6、DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析。

7、DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)(你是jsp了还是freemarker了)。

8、将响应数据返回给客户端(也就是添加到response对象中)。

image

HandlerExecutionChain里面就包含了Handler和一个List的拦截器链。

springmvc的出现主要是用来取代servlet的。

因为每个handler的实现可能是不同的,这个时候我就有了一个HandlerAdapter来进行统一适配。

Spring bean的作用域

分为singleton,prototype,request,session,groupsession(也叫集群session,可以用来做单点登录)。

posted on 2024-09-21 15:56  ~码铃薯~  阅读(3)  评论(0编辑  收藏  举报

导航