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 属性,执⾏指定的⽅法。
面试的时候,面试官可能不想听你讲具体的细节,这个时候的回答方式。
五步法:创建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://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注解的解析。
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对象中)。
HandlerExecutionChain里面就包含了Handler和一个List
springmvc的出现主要是用来取代servlet的。
因为每个handler的实现可能是不同的,这个时候我就有了一个HandlerAdapter来进行统一适配。
Spring bean的作用域
分为singleton,prototype,request,session,groupsession(也叫集群session,可以用来做单点登录)。