1. 程式人生 > >【spring】手寫spring

【spring】手寫spring

前言

spring和之前將的tomcat差不多,都有一個servlet,但是有了分發的功能。最大的不同是需要把類例項化成物件放到ioc容器。

6大步驟

  • 1.載入配置檔案application.properties
public class HLPDidspatcherServlet extends HttpServlet{
    private  Properties p = new Properties();
    private List<String> classNames = new ArrayList<String>();
    private
Map<String,Object> ioc = new HashMap<String,Object>(); private Map<String,Method> handlerMapping = new HashMap<String,Method>(); @Override public void init(ServletConfig config) throws ServletException { String application = config.getInitParameter("contextConfigLocation"
); System.out.println("application = " + application); //1.載入配置檔案application.properties doLoadConfig(application); //2.掃描配置檔案中描述的相關的所有類 doScanner(p.getProperty("scanPackage")); //3.例項化所有掃描到的類,並放到ioc容器中(自己實現ioc容器) doInstance(); //4.依賴注入,di從ioc容器中找到加@autowired這個註解的屬性,並找到其在ioc容器中的例項,動態賦值
doAutowired(); //5.把在@controller中加了@requestMapping這個註解的方法和url構造成一個對應關係,一個map結構,key是URL;value是method initHandlerMapping(); //6.等待使用者請求,根據使用者請求的url去map中找對應的method // 呼叫doPost/doGet方法,通過反射機制動態呼叫該方法並執行 }}
 //1.載入配置檔案application.properties
    private void doLoadConfig(String location){
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(location);

        try {
            p.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null != is){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }
  • 2.掃描配置檔案中描述的類
//2.掃描配置檔案中描述的相關的所有類
    //遞迴掃描
    private  void doScanner(String packageName){
        URL url = this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.","/"));
        File dir = new File(url.getFile());
        for(File file : dir.listFiles()){
            if(file.isDirectory()){
                doScanner(packageName+"."+file.getName());
            }else{
                classNames.add(packageName + "." + file.getName().replaceAll(".class","").trim());
            }
        }
    }
  • 3.把這些類例項化放到ioc容器中
 //3.例項化所有掃描到的類,並放到ioc容器中(自己實現ioc容器)
    private  void doInstance(){
        try {
            for (String className : classNames) {
                Class<?> clazz = Class.forName(className);
                if(clazz.isAnnotationPresent(HLPController.class)){
                    String beanName = lowerFirst(clazz.getSimpleName());
                    ioc.put(beanName,clazz.newInstance());
                }else if(clazz.isAnnotationPresent(HLPService.class)){
                    //第一種形式,預設首字母小寫
                    //第二種形式,如果自己起了名字,優先用自己定義的名字去匹配
                    //第三種形式,利用介面本身作為key,把其對應類的例項作為值
                    HLPService service= clazz.getAnnotation(HLPService.class);
                    String beanName = service.value();

                    if(!"".equals(beanName.trim())){
                        ioc.put(beanName,clazz.newInstance());
                        continue;
                    }
                    Object instance = clazz.newInstance();

                    beanName=lowerFirst(clazz.getSimpleName());
                    ioc.put(beanName,instance);

                    Class<?>[] interfaces = clazz.getInterfaces();
                    for(Class<?> i : interfaces)
                    {
                       ioc.put(i.getName(),instance);
                    }
                }else{
                    continue;
                }
            }
        }catch (Exception e ){
            e.printStackTrace();
        }

    }

對所有加了@Controller的類都放到ioc容器了,其實就是一個map,key,value對;key是類名的首字母小寫,value是類的物件。
但是@service有一些不同,key可能是serviceImpl的類名首字母小寫,也可能是@service裡自定義的名字,還可能是service介面的類名

這一步就是IOC,物件的生命週期交給容器管理

  • 4.依賴注入,找到@autowired並把ioc容器中的例項動態注入
//4.依賴注入,di從ioc容器中找到加@autowired這個註解的屬性,並找到其在ioc容器中的例項,動態賦值
    private void  doAutowired(){
        if(ioc.isEmpty()){return;}

        for(Map.Entry<String,Object> entry: ioc.entrySet() ){
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for(Field field: fields){
                if(!field.isAnnotationPresent(HLPAutowired.class)){continue;}
                HLPAutowired autowired = field.getAnnotation(HLPAutowired.class);

                String beanName = autowired.value().trim();
                if("".equals(beanName)){
                    beanName=field.getType().getName();
                }
                //即使是private,也要強吻
                field.setAccessible(true);
                //開始賦值
                try {
                    field.set(entry.getValue(),ioc.get(beanName));
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }
    }

這一步是DI,在加了@autowired @requestMapping等註解的地方像為屬性賦值一樣 ,賦上類的例項。

這是一個和我之前想的不一樣的地方,注入的時候不是遍歷程式碼中的每個類的每個 方法,相反是遍歷ioc容器裡每一個key,value對的value對應的類上的註解。
之前ioc中已經有了key,value對,然後再建立一個key,value對,key是ioc容器中value也就是物件,value是註解比如@autowired中自定義的名字或者是類的型別。

  • 5.把加了@requestMapping的方法與url對應起來,key是url,value是method
//5.把在@controller中加了@requestMapping這個註解的方法和url構造成一個對應關係,一個map結構,key是URL;value是method
    private  void initHandlerMapping(){
        if(ioc.isEmpty()){return;}
        for(Map.Entry<String,Object> entry: ioc.entrySet()){
            Class<?> clazz = entry.getValue().getClass();
            if(!clazz.isAnnotationPresent(HLPController.class)){continue;}

            String url = "";
            if(clazz.isAnnotationPresent(HLPRequestMapping.class)){
                HLPRequestMapping requestMapping = clazz.getAnnotation(HLPRequestMapping.class);
                url = requestMapping.value();
            }
            Method[] methods = clazz.getMethods();
            for(Method method:methods){
                if(!method.isAnnotationPresent(HLPRequestMapping.class)){continue;}
                HLPRequestMapping hlpRequestMapping = method.getAnnotation(HLPRequestMapping.class);
                String mapping =("/"+url+"/"+hlpRequestMapping.value()).replaceAll("/+","/") ;
                handlerMapping.put(mapping,method);
            }
        }
    }
  • 6.等待使用者請求,根據使用者的url請求map中的method
 @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatcher(req,resp);
    }

    private void   doDispatcher(HttpServletRequest req, HttpServletResponse resp){
        if(handlerMapping.isEmpty()){return;}

        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replace(contextPath,"").replaceAll("/+","/");

        Method method = handlerMapping.get(url);
    }

一直HttpServletRequest如何獲得url?
分三步:
1.獲得 requestURL
String url = req.getRequestURI(); 這個包括http://應用名:埠號/程式名/類名/方法名;而只有類名/方法名是我們需要的,因為requestMapping組成的在ioc中的key,value對中key是這個。
2.獲得http://應用名:埠號/程式名
request.getContextPath();
3.把url中contextPath這部分替換成空,剩下的/+替換成一個的/
url.replace(contextPath,”“).replaceAll(“/+”,”/”);

總結圖

這裡寫圖片描述