面试题1. Spring中bean的循环依赖怎么解决?
(一). 首先说一下什么是Spring的循环依赖:
其实就是在进行getBean的时候,A对象中去依赖B对象,而B对象又依赖C对象,但是对象C又去依赖A对象,结果就造成A、B、C三个对象都不能完成实例化,出现了循环依赖。就会出现死循环,最终导致内存溢出的错误。
(二).如何去解决Spring的循环依赖呢?
1.先知道什么是Spring的“三级缓存”:就是下面的三个大的Map对象,因为Spring中的循环依赖的理论基础其实是基于java中的值传递的,然后其实Spring中的单例对象的创建是分为三个步骤的:
createBeanInstance,其实第一步就是通过构造方法去进行实例化对象。但是这一步只是实例对象而已,并没有把对象的属性也给注入进去
然后这一步就是进行注入实例对象的属性,也就是从这步对spring xml中指定的property进行populate
最后一步其实是初始化XML中的init方法,来进行最终完成实例对象的创建。但是AfterPropertiesSet方法会发生循环依赖的步骤集中在第一步和第二步。
singletonObjects指单例对象的cache (一级缓存)
private final Map singletonObjects = new ConcurrentHashMap(256);
singletonFactories指单例对象工厂的cache(三级缓存)
private final Map> singletonFactories = new HashMap>(16);
earlySingletonObjects指提前曝光的单例对象的cache(二级缓存)
private final Map earlySingletonObjects = new HashMap(16);
2. 然后是怎么具体使用到这个三级缓存的呢,或者说三级缓存的思路?
附上核心代码
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
从一级缓存获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
从二级缓存获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
从三级缓存获取
ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);}
3. 总结一下为什么这么做就能解决Spring中的循环依赖问题。
面试题2. Spring中bean的加载过程?
首先从大的几个核心步骤来去说明,因为Spring中的具体加载过程和用到的类实在是太多了。
面试题3. Spring中bean的生命周期?
面试题4. 说一下Spring中的IOC核心思想和DI?
之前自己有总结过的一篇:https://blog.csdn.net/qq_36520235/article/details/79383238
面试题5. 说说Spring中的几种事务和隔离级别?
面试题6. 说一下SpringMVC中的拦截器和Servlet中的filter有什么区别?
首先最核心的一点他们的拦截侧重点是不同的,SpringMVC中的拦截器是依赖JDK的反射实现的,SpringMVC的拦截器主要是进行拦截请求,通过对Handler进行处理的时候进行拦截,先声明的拦截器中的preHandle方法会先执行,然而它的postHandle方法(他是介于处理完业务之后和返回结果之前)和afterCompletion方法却会后执行。并且Spring的拦截器是按照配置的先后顺序进行拦截的。
而Servlet的filter是基于函数回调实现的过滤器,Filter主要是针对URL地址做一个编码的事情、过滤掉没用的参数、安全校验(比较泛的,比如登录不登录之类)
面试题7. spring容器的bean什么时候被实例化?
(1)如果你使用BeanFactory作为Spring Bean的工厂类,则所有的bean都是在第一次使用该Bean的时候实例化
(2)如果你使用ApplicationContext作为Spring Bean的工厂类,则又分为以下几种情况:
如果bean的scope是singleton的,并且lazy-init为false(默认是false,所以可以不用设置),则 ApplicationContext启动的时候就实例化该Bean,并且将实例化的Bean放在一个map结构的缓存中,下次再使 用该 Bean的时候,直接从这个缓存中取
如果bean的scope是singleton的,并且lazy-init为true,则该Bean的实例化是在第一次使用该Bean的时候进 行实例化
如果bean的scope是prototype的,则该Bean的实例化是在第一次使用该Bean的时候进行实例化
面试题8.说一下Spring中AOP的底层是怎么实现的?,再说说一下动态代理和cglib区别?
Spring中AOP底层的实现其实是基于JDK的动态代理和cglib动态创建类进行动态代理来实现的:
1. 第一种基于JDK的动态代理的原理是:
需要用到的几个关键成员
InvocationHandler (你想要通过动态代理生成的对象都必须实现这个接口)
真实的需要代理的对象(帮你代理的对象)
Proxy对象(是JDK中java.lang.reflect包下的)
下面是具体如何动态利用这三个组件生成代理对象
(1)首先你的真是要代理的对象必须要实现InvocationHandler 这个接口,并且覆盖这个接口的invoke(Object proxyObject, Method method, Object[] args)方法,这个Invoker中方法的参数的proxyObject就是你要代理的真实目标对象,方法调用会被转发到该类的invoke()方法, method是真实对象中调用方法的Method类,Object[] args是真实对象中调用方法的参数
(2)然后通过Proxy类去调用newProxyInstance(classLoader, interfaces, handler)方法,classLoader是指真实代理对象的类加载器,interfaces是指真实代理对象需要实现的接口,还可以同时指定多个接口,handler方法调用的实际处理者(其实就是帮你代理的那个对象),代理对象的方法调用都会转发到这里,然后直接就能生成你想要的对象类了。
下面是Proxy调用newProxyInstance的方法源码
public static Object newProxyInstance(ClassLoader loader,
Class>[] interfaces,
InvocationHandler h) throws IllegalArgumentException {
//验证传入的InvocationHandler不能为空
Objects.requireNonNull(h);
//复制代理类实现的所有接口
final Class>[] intfs = interfaces.clone();
//获取安全管理器
final SecurityManager sm = System.getSecurityManager();
//进行一些权限检验
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
//该方法先从缓存获取代理类, 如果没有再去生成一个代理类
Class> cl = getProxyClass0(loader, intfs);
try {
//进行一些权限检验
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//获取参数类型是InvocationHandler.class的代理类构造器
final Constructor> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
//如果代理类是不可访问的, 就使用特权将它的构造器设置为可访问
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//传入InvocationHandler实例去构造一个代理类的实例
//所有代理类都继承自Proxy, 因此这里会调用Proxy的构造器将InvocationHandler引用传入
return cons.newInstance(new Object[]{h});
} catch (Exception e) {
//为了节省篇幅, 笔者统一用Exception捕获了所有异常
throw new InternalError(e.toString(), e);
}
}
大佬的博客借鉴一下; https://www.cnblogs.com/liuyun1995/p/8157098.html
面试题9. Spring的几种注入方式说一下?
(1)构造方法注入:
注意的点:
如果有多个构造参数,那么与构造方法参数列表参数的顺序无关
如果有多个构造方法且参数顺序不同,那么会按第一个构造方法进行注入
(2)set方法注入:
注意的点:
其实set方法的注入原理就是,spring会将name值的每个单词首字母转换成大写,然后再在前面拼接上"set"构成一个方法名,然后去对应的类中查找该方法,通过反射调用,实现注入
如果通过set方法注入属性,那么spring会通过默认的空参构造方法来实例化对象,所以如果在类中写了一个带有参数的构造方法,一定要把空参数的构造方法写上,否则spring没有办法实例化对象,导致报错。
(3)注解的方式注入:
@Resource:java的注解,默认以byName的方式去匹配与属性名相同的bean的id,如果没有找到就会以byType的方式查找,如果byType查找到多个的话,使用@Qualifier注解(spring注解)指定某个具体名称的bean
@Autowired:spring注解,默认是以byType的方式去匹配类型相同的bean,如果只匹配到一个,那么就直接注入该bean,无论要注入的 bean 的 name 是什么;如果匹配到多个,就会调用 DefaultListableBeanFactory 的 determineAutowireCandidate 方法来决定具体注入哪个bean。determineAutowireCandidate 方法的内容如下:
// candidateBeans 为上一步通过类型匹配到的多个bean,该 Map 中至少有两个元素。
protected String determineAutowireCandidate(Map candidateBeans, DependencyDescriptor descriptor) {
// requiredType 为匹配到的接口的类型
Class> requiredType = descriptor.getDependencyType();
// 1. 先找 Bean 上有@Primary 注解的,有则直接返回
String primaryCandidate = this.determinePrimaryCandidate(candidateBeans, requiredType);
if (primaryCandidate != null) {
return primaryCandidate;
} else {
// 2.再找 Bean 上有 @Order,@PriorityOrder 注解的,有则返回
String priorityCandidate = this.determineHighestPriorityCandidate(candidateBeans, requiredType);
if (priorityCandidate != null) {
return priorityCandidate;
} else {
Iterator var6 = candidateBeans.entrySet().iterator();
String candidateBeanName;
Object beanInstance;
do {
if (!var6.hasNext()) {
return null;
}
// 3. 再找 bean 的名称匹配的
Entry entry = (Entry)var6.next();
candidateBeanName = (String)entry.getKey();
beanInstance = entry.getValue();
} while(!this.resolvableDependencies.values().contains(beanInstance) && !this.matchesBeanName(candidateBeanName, descriptor.getDependencyName()));
return candidateBeanName;
}
}
}