京東二面后整理的spring面試題
Q:ioc是什么,有什么用?
A:Ioc全稱Inversion of Control意思為控制反轉(zhuǎn),是程序設(shè)計的一種思想,或者目標。spring通過DI(依賴注入)來實現(xiàn)IOC,把對象的實例化過程的控制權(quán)給到了容器(spring容器);怎么理解呢?
用大白話來說就是以前一個對象產(chǎn)生的控制權(quán)全部是程序員自己控制的,比如產(chǎn)生時機、使用哪個構(gòu)造方法;但是如果反轉(zhuǎn)給容器則由容器決定時機和使用哪個構(gòu)造方法(spring源碼中有通過BeanPostProcessor來推斷構(gòu)造方法的)
Q:bean作用域有哪些,說一下各種使用場景?
A:bean的作用域比較多,可以參考spring官網(wǎng)文檔對bean的scope的定義
下面給出截圖

這里的生命周期常用的就是singleton和prototype,singleton指的是單例,prototype指的是原型,spring所有創(chuàng)建出來的bean都是單例的,比如controller和service、dao;但是有的時候我們需要bean是一個原型的,比如在類里面有全局變量。
Q:aop是什么,有哪些實現(xiàn)方式?
A:參考spring官方文檔:Aop叫做面向切面編程,在實際開發(fā)中我們的程序是一個自上而下的執(zhí)行流程,比如一個登陸邏輯,用戶發(fā)送HTTP請求,controller接受請求,封裝參數(shù),傳給service、繼而調(diào)用dao操作db然后返回。這整個邏輯當中會出現(xiàn)一些橫切性的問題,比如進入controller的時候我們需要記錄日志,比如調(diào)用dao的db操作時候需要進行事務(wù)操作,比如進入service的方法之前需要進行權(quán)限驗證等等。。。這種和主邏輯無關(guān)不影響程序執(zhí)行結(jié)果的問題稱之為橫切性問題,AOP的產(chǎn)生就是為了來解決這種橫切性問題。 Aop編程不關(guān)心主業(yè)務(wù)邏輯,只關(guān)心這些橫切性問題,比如他們的執(zhí)行時機,執(zhí)行的地方、執(zhí)行順序等等
Aop的實現(xiàn)方式可以從兩個方面來回答
第一Aop的編程風(fēng)格,spring提供兩種編程風(fēng)格,官網(wǎng)有解釋一種是xml,一種是基于aspectj的注解風(fēng)格來的
第二Aop的底層技術(shù)實現(xiàn)原理,spring里面提供jdk動態(tài)代理的技術(shù)和cglib的技術(shù)原理來實現(xiàn)AOP
Q:攔截器是什么,什么場景使用?
A:spring當中實現(xiàn)攔截的接口HandlerInterceptor,攔截器主要是讓請求進入controller之前進行攔截處理邏輯,主要用在和相關(guān)權(quán)限的業(yè)務(wù)上
Q:bean的各種作用域是怎么樣實現(xiàn)的?
A:工廠設(shè)計模式實現(xiàn)bean的作用域
Q:工具類中如何注入bean?具體使用場景?
A:通過實現(xiàn)Aware接口注入ApplicationContext對象,然后對外提供方位ApplicationContext對象的接口,工具類安裝繼而調(diào)用提供的接口獲取ApplicationContext的getBean方法就能獲取bean
使用場景分析:比如某個工具類A當中需要調(diào)用某個service B,但是A并不是bean,只是一個普通類,故而無法直接注入,需要用到上述方法
public static ApplicationContext applicationContext;供外部訪問、得到這個對象
Q:注入的bean存在多份的時候有哪些解決辦法?
A:@Quilifier可以解決,其實spring的@autowired已經(jīng)非常智能了,會先根據(jù)type找,如果找到多個,在根據(jù)名字找,但是如果名字沒有找到就會報錯,找到了就用這個bean。
Q:aop里面的cglib原理是什么?
A:ASM字節(jié)碼技術(shù),動態(tài)產(chǎn)生一個子類的類(該子類繼承了目標對象),然后實例該子類的對象,返回代理對象,完成代理
Q:aop切方法的方法的時候,哪些方法是切不了的?為什么?
A:最顯而易見的便是私有方法,因為AOP的底層是代理、不管是JDK還是cglib都是不能代理私有的。很簡單jdk是基于接口的,接口是沒有私有的,cglib是基于繼承,就是代理對象繼承了目標對象,假設(shè)你的目標對象里面有一個私有方法是無法繼承的,無法繼承也就無法代理
Q:同類調(diào)用為什么無法切?怎么樣解決(AOPContext)?
這個問題我看了很久無法看懂,估計面試官表達有問題,其實這個問題分場景,如果jdk動態(tài)代理是有問題的
參考JDK動態(tài)代理導(dǎo)致事務(wù)失效的原因(可以自行百度,當然你也看我這里對JDK原理的分析)
這里寫個例子分析一下
比如我有接口I
public interface I {?void f1();?void f2();}然后由實現(xiàn)類Apublic class A implements I {?@Override?public void f1() {?}?@Override?public void f2() {?}}
假設(shè)我現(xiàn)在產(chǎn)生一個代理對象(代理I接口),那么產(chǎn)生后的代理對象到底長什么樣呢?假設(shè)你對jdk動態(tài)代理的原理比較清楚就知道JDK是通過動態(tài)產(chǎn)生字節(jié)碼來產(chǎn)生代理對象的,那么我們直接把動態(tài)產(chǎn)生的字節(jié)碼拿出來看看就知道代理對象長什么樣子了。
public static void main(String[] args) throws IOException {//獲得到產(chǎn)生I接口的代理對象的字節(jié)碼?byte[] proxyClassFile = ProxyGenerator.generateProxyClass(?"xx", new Class[]{I.class});//下面代碼比較簡單,就是把字節(jié)碼寫到本地 ?File file = new File("d:\\\xx.class");?FileOutputStream fileOutputStream =new FileOutputStream(file);?fileOutputStream.write(proxyClassFile);?fileOutputStream.flush();?fileOutputStream.close();}
現(xiàn)在我們已經(jīng)在D盤有了一個class文件了,那么這個字節(jié)碼文件我們看不懂啊,其實很簡單,直接扔到idea當中(記得要扔到target下面,就是編譯輸出class的目錄,不能放到包下面),idea反編譯后的代碼如下:
//// Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//import com.spring.boot.I;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;public final class xx extends Proxy implements I {?private static Method m1;?private static Method m4;?private static Method m3;?private static Method m2;?private static Method m0;?public xx(InvocationHandler var1) throws {?super(var1);?}?public final boolean equals(Object var1) throws {?try {?return (Boolean)super.h.invoke(this, m1, new Object[]{var1});?} catch (RuntimeException | Error var3) {?throw var3;?} catch (Throwable var4) {?throw new UndeclaredThrowableException(var4);?}?}?public final void f2() throws {?try {?super.h.invoke(this, m4, (Object[])null);?} catch (RuntimeException | Error var2) {?throw var2;?} catch (Throwable var3) {?throw new UndeclaredThrowableException(var3);?}?}??public final void f1() throws {?try {?super.h.invoke(this, m3, (Object[])null);?} catch (RuntimeException | Error var2) {?throw var2;?} catch (Throwable var3) {?throw new UndeclaredThrowableException(var3);?}?}?public final String toString() throws {?try {?return (String)super.h.invoke(this, m2, (Object[])null);?} catch (RuntimeException | Error var2) {?throw var2;?} catch (Throwable var3) {?throw new UndeclaredThrowableException(var3);?}?}?public final int hashCode() throws {?try {?return (Integer)super.h.invoke(this, m0, (Object[])null);?} catch (RuntimeException | Error var2) {?throw var2;?} catch (Throwable var3) {?throw new UndeclaredThrowableException(var3);?}?}?static {?try {?m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));?m4 = Class.forName("com.spring.boot.I").getMethod("f2");?m3 = Class.forName("com.spring.boot.I").getMethod("f1");?m2 = Class.forName("java.lang.Object").getMethod("toString");?m0 = Class.forName("java.lang.Object").getMethod("hashCode");?} catch (NoSuchMethodException var2) {?throw new NoSuchMethodError(var2.getMessage());?} catch (ClassNotFoundException var3) {?throw new NoClassDefFoundError(var3.getMessage());?}?}}
比如我看f1方法去掉無用的代碼里面就一行代碼
super.h.invoke(this, m3, (Object[])null);
這里的super就是Proxy類,這里JDK動態(tài)代理已經(jīng)默認為你繼承了一個類,現(xiàn)在你知道為什么JDK只能用接口了吧?因為java不允許多繼承。
然后在Proxy類里面有一個h屬性,這個h屬性就是InvocatonHandler接口,是程序員提供的一個接口。在springAop的源碼中:

當然這里我先不討論springAop;說回上面的這個InvocatonHandler接口,里面有一個invoker方法,這個方法里面的邏輯是由程序員自己提供,那么我們的代理邏輯就是寫在這個方法里面,并且這個方法還負責(zé)調(diào)用目標方法的邏輯,怎么調(diào)用的呢?就是把目標對象傳給這個InvocatonHandler,然后反射調(diào)用,所以你在f1里面調(diào)用f2其實是調(diào)用目標對象的f2并不是代理對象的f2。故而會導(dǎo)致代理失效,也是上面說的不能切本類。
Q:有沒有用過BeanFactory?場景?
A:用的不多,曾經(jīng)通過BeanFactory動態(tài)往spring容器當中注入一個bean,什么意思呢?假設(shè)我一個bean最開始并不要求被spring實例化,在某個時機(某個條件成了、比如你獲取了一個別人傳給你的對象)需要自己實例化,實例化好之后通過BeanFactory添加到spring容器當中,代碼如下:
AnnotationConfigApplicationContext ac?= new AnnotationConfigApplicationContext(Appconfig.class);ac.getBeanFactory().registerSingleton("bname",你的對象);
Q:說說aop和ioc關(guān)系
A:其實這個題目本身意義不大,IOC和AOP都是編程目標和spring沒有關(guān)系,就算沒有spring也能實現(xiàn)AOP,比如大名鼎鼎的Aspectj技術(shù)也能實現(xiàn)Aop。如果硬是要說關(guān)系應(yīng)該說spring的IOC和springAop有什么關(guān)系。其實意義也不大,無非springAop當中的對象必須在IOC容器當中。比如Aspectj就可以脫離IOC單獨使用,但是springAop就不能脫離IOC
Q:說說DispatcherServlet做了什么
這個問題可復(fù)雜了,簡單說DispatcherServlet的init方法里面load了springmvc的配置信息,然后初始化了spring容器(調(diào)用了refresh方法),把controller的信息緩存了,比如映射信息;然后DispatcherServlet會攔截所有的請求,根據(jù)用戶的請求信息通過緩存的映射信息找到對應(yīng)的controller的對應(yīng)方法,然后反射調(diào)用(其實底層的源碼就是反射調(diào)用controller的方法),然后視圖裁決、解析等等工作,可以參考springMVC的工作原理去回答。