【spring】手写spring

前言

spring和以前将的tomcat差很少,都有一个servlet,可是有了分发的功能。最大的不一样是须要把类实例化成对象放到ioc容器。java

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接口的类名web

这一步就是IOC,对象的生命周期交给容器管理spring

  • 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等注解的地方像为属性赋值同样 ,赋上类的实例。tomcat

这是一个和我以前想的不同的地方,注入的时候不是遍历代码中的每一个类的每一个 方法,相反是遍历ioc容器里每个key,value对的value对应的类上的注解。
以前ioc中已经有了key,value对,而后再创建一个key,value对,key是ioc容器中value也就是对象,value是注解好比@autowired中自定义的名字或者是类的类型。app

  • 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(“/+”,”/”);ide

总结图

这里写图片描述