SpringIOC簡單模擬,菜鳥篇
IOC是Spring兩大特徵之一,今天我們就來用最最最土的方式模擬下它。本文全是基礎,不涉及設計模式,適合初級程式設計師閱讀。
到底什麼是IOC、DI
IOC(控制反轉),不是什麼技術,而是一種設計思想。Ioc意味著,將你設計好的物件交給容器控制,而不是傳統的,在你的物件內部直接控制。
所以控制反轉就是說把建立物件的控制權進行轉移,以前建立物件的主動權和建立時機都是有自己控制的,而現在把這種權利交給第三方,比如IOC容器,它是一個專門用來建立物件的工廠,有了IOC容器後,把建立和查詢依賴物件的控制權交給了容器,由容器進行注入組合物件,所以物件與物件之間是鬆散耦合。
DI(依賴注入)
一種IoC的實現方式。即將依賴物件的建立和繫結工作轉移到第三者(呼叫方)
想象下Spring載入過程
假設有一個URL請求,http://demo/get/uid/1
當這個請求到達伺服器之後,首先獲取url,接著找到url所對應的處理方法,最後把url引數傳給這個方法,然後在呼叫下這個方法。
問題是:如何通過通過url找到這個方法的對映,攏共分幾步?
分六個步驟
第一步:自定義幾個註解
我們需要自定義幾個註解來標識我們bean物件:
1. @Controller 用來標識controller層。
2. @Service用來標識Sevice層。
3. @Qualifier 用來標識類中屬性。
4. @RequestMapping 用來標識方法。
這些註解中都有一個預設的value屬性。
第二步:收集bean
把整個工程檔案遍歷一遍,通過IO讀取所有java檔案,java的name放在一個list容器裡,就稱他為beanName集合吧。
第三步:註冊bean
遍歷beanName集合,獲取java檔案的類名,用反射的方法通過名字,獲取該類的Class,判斷Class是否包含Controller或Service註解,若包含,則通過反射的方式把當前Class生成bean物件,用Controller或Service註解中的value值做key來標識。放到一個map容器裡,就叫他instanceMap容器吧。
第四步:注入bean
遍歷instanceMap容器,instanceMap存的都是例項物件,我們遍歷每個物件,獲取當前物件內的所有變數fields,然後在遍歷這個fields,看當前field是否包含Qualifier註解,包含的話,就用Qualifier註解設定的value當做key去instanceMap容器裡找到bean例項物件,然後把這個例項物件設定到當前的field中。
第五步:收集RequestMapping方法
遍歷instanceMap容器,找到帶有Controller註解的類,獲取該類的所有方法methods,再遍methods方法,找到帶有RequestMapping註解的方法物件,用RequestMapping中的value加上Controller註解中的value拼接成url,當做key,把當前方法物件放到一個map容器,就把這個容器叫做methodMap容器吧。
第六步:實現url對映
當一個請求到達Servlet時,獲取這個請求的url,通過字串分割獲取url中的引數,把引數設定到request.Attribute中,刪除url中的引數,做key去methodMap獲取方法物件,再用反射的方法,將request,response做引數,呼叫這個方法,獲取返回值,這個返回值就是我們要返回給客戶端的頁面,在通過request轉發的方式,請頁面返回。
記住這六步,下面看程式碼。
Coding1:自定義幾個註解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
String value() ;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Service {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Qualifier {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequestMapping {
String value();
}
Coding2:收集bean
basePackages為配置掃描的包。
就像spring配置檔案中的<context:component-scan base-package="" />
public void scanClassName(String basePackages){
//獲取掃描註解所在的路徑
URL url =this.getClass().getResource("/"+replacePath(basePackages));
File files = new File(url.getFile());
String[] fileList = files.list();
for(String file: fileList){
File eachFile = new File(url.getFile()+file);
if(eachFile.isDirectory()){
scanClassName(basePackages+"."+file);
}else{
//將java類的名稱放在classList中
classlist.add(basePackages+"."+eachFile.getName());
}
}
}
Coding3:註冊bean
public void filterAndInstance() throws Exception {
for(String current:classlist){
//將.class字尾去掉,獲取檔名,用反射獲取class
Class clazz = Class.forName(current.replace(".class",""));
//判斷是否包含Controller註解
if(clazz.isAnnotationPresent(Controller.class)){
//建立物件
Object object = clazz.newInstance();
String key = ((Controller)clazz.getAnnotation(Controller.class)).value();
instanceMap.put(key,object);
}else if(clazz.isAnnotationPresent(Service.class)){
Object object = clazz.newInstance();
String key = ((Service)clazz.getAnnotation(Service.class)).value();
instanceMap.put(key,object);
}else{
continue;
}
}
}
Coding4:注入bean
public void springDi() throws IllegalAccessException {
for(Map.Entry entry: instanceMap.entrySet()){
Field[] fields = entry.getValue().getClass().getDeclaredFields();
for(Field field :fields){
if(field.isAnnotationPresent(Qualifier.class)){
field.setAccessible(true);
String key = field.getAnnotation(Qualifier.class).value();
field.set(entry.getValue(),instanceMap.get(key));
}
}
}
}
Coding5:收集RequestMapping方法
private void mvc() throws ServletException {
if(instanceMap == null){
throw new ServletException("instanceMap is null");
}
for(Map.Entry entry : instanceMap.entrySet()){
if(entry.getValue().getClass().isAnnotationPresent(Controller.class)){
String ctlUrl = entry.getValue().getClass().getAnnotation(Controller.class).value();
Method[] methods = entry.getValue().getClass().getMethods();
for(Method current: methods){
if (current.isAnnotationPresent(RequestMapping.class)){
String methodUrl = current.getAnnotation(RequestMapping.class).value();
methodMap.put("/"+ctlUrl+"/"+methodUrl,current);
}
}
}
}
}
Coding6:實現url對映
private void methodInvoke(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String url = req.getServletPath();
url = url.replace(".html","");
String className = url;
Method method = methodMap.get(className);
if(method==null){
req.getRequestDispatcher("/404.jsp").forward(req, resp);
}else {
Object o = instanceMap.get(className.split("/")[1]);
String forwod = null;
try {
forwod = (String) method.invoke(o, new Object[]{req, resp});
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
req.getRequestDispatcher("/" + forwod + ".jsp").forward(req, resp);
}
}