栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring 源码分析(一)—— 手写Spring初体验 V1

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring 源码分析(一)—— 手写Spring初体验 V1

简陋Spring运行流程

在手写spring之前我们可以从下图大致了解一个简陋版spring运行的流程。需要注意的是这个简陋版本没有包含AOP功能。

项目搭建

直接创建一个Maven项目,然后逐步添加代码。以下为我的工程目录。

Pom文件

包含 jetty插件,帮助我们直接运行程序。webdefault.xml为jetty所需要的文件。



    4.0.0

    org.example
    spring-source-1
    1.0-SNAPSHOT

    
        8
        8
        2.4
    

    

        
        
            javax.servlet
            servlet-api
            ${servlet.api.version}
            provided
        
        

    

    
        ${artifactId}
        
            
                ${basedir}/src/main/resources
                
                    ***.java
                    ***.*
                                    
                                    true
                                
                            
                        
                    
                
            
            
                org.mortbay.jetty
                maven-jetty-plugin
                6.1.26
                
                    src/main/resources/webdefault.xml
                    /
                    
                        
                            8081
                        
                    
                    0
                    
                        
                            src/main/webapp
                            
                                ***.properties
                            
                        
                    
                    
                        
                            
                                javax.xml.parsers.documentBuilderFactory
                            
                            
                                com.sun.org.apache.xerces.internal.jaxp.documentBuilderFactoryImpl
                            
                        
                        
                            
                                javax.xml.parsers.SAXParserFactory
                            
                            
                                com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl
                            
                        
                        
                            
                                javax.xml.transform.TransformerFactory
                            
                            
                                com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
                            
                        
                        
                            org.eclipse.jetty.util.URI.charset
                            UTF-8
                        
                    
                
            

            
                org.apache.maven.plugins
                maven-war-plugin
                2.2
                
                    
                        false
                    
                    
                        
                            
                            src/main/resources/
                            WEB-INF/classes
                            
                                **local
                                **/test
                                **/product
                             -->
                            true
                        
                        
                            
                            src/main/resources
                            WEB-INF/classes
                            true
                        
                    
                
            
            
                org.zeroturnaround
                javarebel-maven-plugin
                
                    
                        generate-rebel-xml
                        process-resources
                        
                            generate
                        
                    
                
                1.0.5
            

        
        
            
                
                
                    org.eclipse.m2e
                    lifecycle-mapping
                    1.0.0
                    
                        
                            
                                
                                    
                                        
                                            org.zeroturnaround
                                        
                                        
                                            javarebel-maven-plugin
                                        
                                        
                                            [1.0.5,)
                                        
                                        
                                            generate
                                        
                                    
                                    
                                        
                                    
                                
                            
                        
                    
                
            
        
    





















 

  
    Default web.xml file.  
    This file is applied to a Web application before it's own WEB_INF/web.xml file
  


  
  
  
  

  
    org.mortbay.jetty.webapp.NoTLDJarPattern
    start.jar|ant-.*.jar|dojo-.*.jar|jetty-.*.jar|jsp-api-.*.jar|junit-.*.jar|servlet-api-.*.jar|dnsns.jar|rt.jar|jsse.jar|tools.jar|sunpkcs11.jar|sunjce_provider.jar|xerces.*.jar
  
            


  
  
  
  
  
  
  
  
  
  
  
  
  
   
  
  
  
  
  
   
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
    default
    org.mortbay.jetty.servlet.DefaultServlet
    
      acceptRanges
      true
    
    
      dirAllowed
      true
    
    
      welcomeServlets
      false
    
    
      redirectWelcome
      false
    
    
      maxCacheSize
      256000000
    
    
      maxCachedFileSize
      10000000
    
    
      maxCachedFiles
      1000
    
    
      cacheType
      both
    
    
      gzip
      true
    
    
      useFileMappedBuffer
      false
      
    
    0
   

   default / 
  

  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
    jsp
    org.apache.jasper.servlet.JspServlet
    
        logVerbosityLevel
        DEBUG
    
    
        fork
        false
    
    
        xpoweredBy
        false
    
    
    0
  

   
    jsp 
    *.jsp 
    *.jspf
    *.jspx
    *.xsp
    *.JSP 
    *.JSPF
    *.JSPX
    *.XSP
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  



  
  
    30
  

  
  
  
  
  
  
  

  
  
    index.html
    index.htm
    index.jsp
  

  
  
    arISO-8859-6
    beISO-8859-5
    bgISO-8859-5
    caISO-8859-1
    csISO-8859-2
    daISO-8859-1
    deISO-8859-1
    elISO-8859-7
    enISO-8859-1
    esISO-8859-1
    etISO-8859-1
    fiISO-8859-1
    frISO-8859-1
    hrISO-8859-2
    huISO-8859-2
    isISO-8859-1
    itISO-8859-1
    iwISO-8859-8
    jaShift_JIS
    koEUC-KR     
    ltISO-8859-2
    lvISO-8859-2
    mkISO-8859-5
    nlISO-8859-1
    noISO-8859-1
    plISO-8859-2
    ptISO-8859-1
    roISO-8859-2
    ruISO-8859-5
    shISO-8859-5
    skISO-8859-2
    slISO-8859-2
    sqISO-8859-2
    srISO-8859-5
    svISO-8859-1
    trISO-8859-9
    ukISO-8859-5
    zhGB2312
    zh_TWBig5   
  
  
  
    
      Disable TRACE
      /
      TRACE
    
    
  
  

相关配置文件

application

scanPackage=com.spring.test.demo

web.xml

web容器的项目都是从读取web.xml开始;其中DemoDispatchServlet是我们模拟spring的核心功能类。



    Web Application
    
        gpmvc
        com.spring.test.mvc.servlet.v1.DemoDispatchServlet
        
        
            contextConfigLocation
            application.properties
        

        1
    
    
        gpmvc
        /*
    

自定义注解

模拟spring的常用注解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface DemoAutowired {
    String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface DemoController {
    String value() default "";
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface DemoRequestMapping {
    String value() default "";
}
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface DemoRequestParam {
    String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface DemoService {
    String value() default "";
}

业务逻辑类

就是常规的spring操作,直接复制即可。

public interface IDemoService {
   
   String get(String name);
   
}
//我这边是类名和注解名重复了,可以自行替换
@com.spring.test.mvc.annotation.DemoService
public class DemoService implements IDemoService {

    public String get(String name) {
        return "My name is " + name + ",from service.";
    }

}
@DemoController
@DemoRequestMapping("/demo")
public class DemoAction {

    @DemoAutowired
    private IDemoService demoService;

    @DemoRequestMapping("/query")
    public void query(HttpServletRequest req, HttpServletResponse resp,
                      @DemoRequestParam("name") String name) {
//    String result = demoService.get(name);
        String result = "My name is " + name;
        try {
            resp.getWriter().write(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @DemoRequestMapping("/add")
    public void add(HttpServletRequest req, HttpServletResponse resp,
                    @DemoRequestParam("a") Integer a, @DemoRequestParam("b") Integer b) {
        try {
            resp.getWriter().write(a + "+" + b + "=" + (a + b));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @DemoRequestMapping("/sub")
    public void sub(HttpServletRequest req, HttpServletResponse resp,
                    @DemoRequestParam("a") Double a, @DemoRequestParam("b") Double b) {
        try {
            resp.getWriter().write(a + "-" + b + "=" + (a - b));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @DemoRequestMapping("/remove")
    public String remove(@DemoRequestParam("id") Integer id) {
        return "" + id;
    }

}
编写核心业务

当我们构建完项目之后,我们便可以开始编写我们的核心servlet了。

初始类

我们首先创建一个初始类,根据流程将大致框架搭好,然后开始编写代码。

public class DemoDispatchServlet extends HttpServlet {

    //保存用户篇配置好的配置文件
    private Properties contextConfig = new Properties();

    //缓存从包路径下扫描的全类名
    private List classNames = new ArrayList();

    //保存所有扫描的类的实例
    private Map ioc = new HashMap();

    //保存controller 中 URL和Method的对应关系
    private Map handlerMapping = new HashMap();

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

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //6.根据url委派给具体的调用方法
       
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1.加载配置文件
        //参数为web.xml中定义的键值名
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        //2.扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));

        //============IOC功能===============
        //3.初始IOC容器,将扫描到的类进行实例化,缓存到IOC容器中
        doInstance();

        //============DI功能===============
        //4.完成依赖注入
        doAutowired();

        //============MVC功能===============
        //5.初始化HandleMapping
        doInitHandleMapping();

        System.out.println("Spring framework is init.");

    }

    private void doInitHandleMapping() {
        
    }

    private void doAutowired() {

    }

    private void doInstance() {

    }

    // 扫描 classpath下符合包路劲规则所有的class文件
    private void doScanner(String scanPackage) {
       
    }

    //根据contextConfigLocation的路径去classpath下找到的对应的文件
    private void doLoadConfig(String contextConfigLocation) {
       
    }
}
加载配置文件

在初始化过程中,我们的第一步就是获取配置文件;

//根据contextConfigLocation的路径去classpath下找到的对应的文件
private void doLoadConfig(String contextConfigLocation) {
    InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);

    try {
        contextConfig.load(is);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (null != is) {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
扫描相关的类

扫描配置类中路径下的所有文件;首先需要注意配置类中的文件路径和我们正常的路径不同,需要将其 * . * 替换为/。

如果扫描到文件夹则继续往该路径下扫描;

如果不是class文件则跳过;

将符合规则的文件添加到集合中。

// 扫描 classpath下符合包路劲规则所有的class文件
private void doScanner(String scanPackage) {
    URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\.", "/"));
    File classPath = new File(url.getFile());

    for (File file : classPath.listFiles()) {
        if (file.isDirectory()) {
            doScanner(scanPackage + "." + file.getName());
        } else {
            // 不是class文件则跳过
            if (!file.getName().endsWith(".class")) {
                continue;
            }
            // 包名.类型  比如: com.demo.DemoAction
            String className = (scanPackage + "." + file.getName().replace(".class", ""));

            // 实例化 需要用到class.forName(className);
            classNames.add(className);
        }
    }
}
将扫描的类放入IOC容器中

扫描到文件之后,我们就需要将其实例化并存至我们的IOC容器中。

流程:

  • controller注解的类:

    1.判断这个类有没有使用controller注解

    2.将类名的首字母小写,通过位操作实现

    3.将该类实例化后添加至IOC容器中

  • service注解的类:

    1.判断这个类有没有使用service注解

    2.将类名的首字母小写,通过位操作实现

    3.获取别名,因为注解具有默认值,所以不需要进行判空操作

    4.实例化类并存入IOC容器中

    5.如果是接口则初始化他的实现类,多个实现类则报错提示。

  • 其他:跳过

private void doInstance() {
    if (classNames.isEmpty()) {
        return;
    }

    try {
        for (String className : classNames) {
            Class clazz = Class.forName(className);

            if (clazz.isAnnotationPresent(DemoController.class)) {
                String beanName = toLowerFirstCase(clazz.getSimpleName());
                Object instance = clazz.newInstance();
                ioc.put(beanName, instance);
            } else if (clazz.isAnnotationPresent(DemoService.class)) {
                // 1.默认类名首字母小写
                String beanName = toLowerFirstCase(clazz.getSimpleName());

                // 2.如果多个包下出现了相同的类名,优先使用别名(自定义命名)
                DemoService demoService = clazz.getAnnotation(DemoService.class);
                if (!"".equals(demoService.value())) {
                    beanName = demoService.value();
                }

                Object instance = clazz.newInstance();

                ioc.put(beanName, instance);

                // 3.如果是接口 ,只能初始化他的实现类
                for (Class i : clazz.getInterfaces()) {
                    if (ioc.containsKey(i.getName())) {
                        throw new Exception("The" + i.getName() + "is exists! please use aliens");
                    }
                    ioc.put(i.getName(), instance);
                }

            } else {
                continue;
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

}

private String toLowerFirstCase(String simpleName) {
    char[] chars = simpleName.toCharArray();
    chars[0] += 32;  //利用ascll码,大小写字母之间相差32
    return String.valueOf(chars);
}
依赖注入

1.遍历IOC容器中的每个实例

2.通过反射获取类中的所有字段

3.判断该字段有无使用auto wired注解

4.如果使用了则获取该注解的值,如果为默认值,则获取该字段的类型名使用

5.将IOC容器中对应该beanName的实例注入到该字段中

private void doAutowired() {

    if (ioc.isEmpty()) {
        return;
    }

    for (Map.Entry entry : ioc.entrySet()) {
        //忽略字段的修饰符
        for (Field field : entry.getValue().getClass().getDeclaredFields()) {
            if (!field.isAnnotationPresent(DemoAutowired.class)) {
                continue;
            }

            DemoAutowired autowired = field.getAnnotation(DemoAutowired.class);
            String beanName = autowired.value().trim();
            if ("".equals(beanName)) {
                beanName = field.getType().getName();
            }

            //强制访问
            field.setAccessible(true);
            try {
                // 相当于 demoAction.demoService = ioc.get("com.demo.service.IDemoService");
                field.set(entry.getValue(), ioc.get(beanName));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

    }
}
初始化HandlerMapping

该部分主要是对url路径及相对应的方法进行映射。

首先遍历IOC容器,没有使用controller的则跳过。

然后处理controller中添加的requestmapping,把其作为baseUrl。

然后开始处理类中的publi方法,如果没有使用requestMapping注解则跳过;

使用该注解的则对url地址进行相关处理后添加进Map中以供使用。

private void doInitHandleMapping() {
    if (ioc.isEmpty()) {
        return;
    }

    for (Map.Entry entry : ioc.entrySet()) {
        Class clazz = entry.getValue().getClass();

        if (!clazz.isAnnotationPresent(DemoController.class)) {
            continue;
        }

        //处理controller类添加地址注解
        String baseUrl = "";
        if (clazz.isAnnotationPresent(DemoRequestMapping.class)) {
            DemoRequestMapping requestMapping = clazz.getAnnotation(DemoRequestMapping.class);
            baseUrl = requestMapping.value();
        }

        //只迭代public方法
        for (Method method : clazz.getMethods()) {
            if (!method.isAnnotationPresent(DemoRequestMapping.class)) {
                continue;
            }

            DemoRequestMapping requestMapping = method.getAnnotation(DemoRequestMapping.class);
            // /demo/query 使用正则表达处理 多个斜杠问题
            String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
            handlerMapping.put(url, method);
            System.out.println("Mapped : " + url + "--->" + method);

        }
    }
}
根据url委派给具体的调用方法

这一步就是我们通过地址访问后触发的方法。

1.首先获取URL地址;这边是将地址中的根地址去除,方便根据映射找到对应的方法。

2.首先判断映射关系中是否包含该路径

3.然后将形参的位置和参数名字建立映射关系,缓存下来

4.根据参数位置匹配参数名字,从url中取到参数名字对应的值

5.以上两步都需要对request及response两个类进行相关的处理

6.使用反射调用相关方法

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

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //6.根据url委派给具体的调用方法
    try {
        doDispatcher(req, resp);
    } catch (Exception e) {
        e.printStackTrace();
        resp.getWriter().write("500 Exception,Detail: " + Arrays.toString(e.getStackTrace()));
    }
}

private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    String url = req.getRequestURI();
    String contextPath = req.getContextPath();
    url = url.replaceAll(contextPath, "").replaceAll("/+", "/");

    if (!this.handlerMapping.containsKey(url)) {
        resp.getWriter().write("404 Not Found!");
        return;
    }

    Method method = this.handlerMapping.get(url);

    // 1.先把形参的位置和参数名字建立映射关系,缓存下来
    Map paramIndexMapping = new HashMap();
    // 多个参数, 每个参数后面有多个值
    Annotation[][] pa = method.getParameterAnnotations();
    for (int i = 0; i < pa.length; i++) {
        for (Annotation annotation : pa[i]) {
            if (annotation instanceof DemoRequestParam) {
                String paramName = ((DemoRequestParam) annotation).value();
                if (!"".equals(paramName.trim())) {
                    paramIndexMapping.put(paramName, i);
                }
            }
        }
    }

    Class[] paramTypes = method.getParameterTypes();
    for (int i = 0; i < paramTypes.length; i++) {
        Class type = paramTypes[i];
        if (type == HttpServletRequest.class || type == HttpServletResponse.class) {
            paramIndexMapping.put(type.getName(), i);
        }
    }

    // 2.根据参数位置匹配参数名字,从url中取到参数名字对应的值
    Object[] paramValues = new Object[paramTypes.length];

    //http://localhost/demo/query?name=Tom&name=Tomcat&name=Mic
    Map params = req.getParameterMap();
    for (Map.Entry param : params.entrySet()) {
        String value = Arrays.toString(param.getValue())
                .replaceAll("\[|\]", "")
                .replaceAll("\s", "");

        if (!paramIndexMapping.containsKey(param.getKey())) {
            continue;
        }

        int index = paramIndexMapping.get(param.getKey());

        //设计到类型强制转换
        paramValues[index] = value;
    }

    if(paramIndexMapping.containsKey(HttpServletRequest.class.getName())){
        int index = paramIndexMapping.get(HttpServletRequest.class.getName());
        paramValues[index] = req;
    }

    if(paramIndexMapping.containsKey(HttpServletResponse.class.getName())){
        int index = paramIndexMapping.get(HttpServletResponse.class.getName());
        paramValues[index] = resp;
    }


    String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
    // 3.组成动态参数列表 ,传给反射调用。
    method.invoke(ioc.get(beanName), paramValues);
    //method.invoke(ioc.get(beanName), new Object[]{req, resp, params.get("name")[0]});
}
运行测试

这边是使用的jeety直接启动的项目。

按照如下方式运行即可。

打开浏览器访问控制器中的路径即可发现成功。

优化

以上便是一个简陋版Spring的编写过程,基本功能已经实现,但代码的优雅程度还不人意,在处理URL及方法映射的时候还可以继续优化。以下直接放优化后的全部代码,有相关注解可以自行查看。

public class DemoDispatcherServlet extends HttpServlet {

    //保存application.properties配置文件中的内容
    private Properties contextConfig = new Properties();

    //保存扫描的所有的类名
    private List classNames = new ArrayList();

    //为了简化程序,暂时不考虑ConcurrentHashMap
    private Map ioc = new HashMap();

    //保存url和Method的对应关系
//    private Map handlerMapping = new HashMap();

    //用Map的话,key,只能是url
    //Handler 本身的功能就是把url和method对应关系,已经具备了Map的功能
    //根据设计原则:单一职责,最少知道原则,帮助我们更好的理解
    private List handlerMapping = new ArrayList();

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

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

        //6、调用,运行阶段
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500 Exection,Detail : " + Arrays.toString(e.getStackTrace()));
        }


    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {

        Handler handler = getHandler(req);
        if (handler == null) {
//        if(!this.handlerMapping.containsKey(url)){
            resp.getWriter().write("404 Not Found!!!");
            return;
        }

        //获得方法的形参列表
        Class[] paramTypes = handler.getParamTypes();

        Object[] paramValues = new Object[paramTypes.length];

        Map params = req.getParameterMap();
        for (Map.Entry parm : params.entrySet()) {
            String value = Arrays.toString(parm.getValue()).replaceAll("\[|\]", "")
                    .replaceAll("\s", ",");

            if (!handler.paramIndexMapping.containsKey(parm.getKey())) {
                continue;
            }

            int index = handler.paramIndexMapping.get(parm.getKey());
            paramValues[index] = convert(paramTypes[index], value);
        }

        if (handler.paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
            int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[reqIndex] = req;
        }

        if (handler.paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
            int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[respIndex] = resp;
        }

        Object returnValue = handler.method.invoke(handler.controller, paramValues);
        if (returnValue == null || returnValue instanceof Void) {
            return;
        }
        resp.getWriter().write(returnValue.toString());
    }

    private Handler getHandler(HttpServletRequest req) {
        if (handlerMapping.isEmpty()) {
            return null;
        }
        //绝对路径
        String url = req.getRequestURI();
        //处理成相对路径
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");


        for (Handler handler : this.handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(url);
            if (!matcher.matches()) {
                continue;
            }
            return handler;
        }
        return null;
    }

    //url传过来的参数都是String类型的,HTTP是基于字符串协议
    //只需要把String转换为任意类型就好
    private Object convert(Class type, String value) {
        //如果是int
        if (Integer.class == type) {
            return Integer.valueOf(value);
        } else if (Double.class == type) {
            return Double.valueOf(value);
        }
        //如果还有double或者其他类型,继续加if
        //这时候,我们应该想到策略模式了
        //在这里暂时不实现,希望小伙伴自己来实现
        return value;
    }


    //初始化阶段
    @Override
    public void init(ServletConfig config) throws ServletException {

        //1、加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        //2、扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));

        //3、初始化扫描到的类,并且将它们放入到ICO容器之中
        doInstance();

        //4、完成依赖注入
        doAutowired();

        //5、初始化HandlerMapping
        initHandlerMapping();

        System.out.println("Spring framework is init.");

    }


    //初始化url和Method的一对一对应关系
    private void initHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }

        for (Map.Entry entry : ioc.entrySet()) {
            Class clazz = entry.getValue().getClass();

            if (!clazz.isAnnotationPresent(DemoController.class)) {
                continue;
            }


            //保存写在类上面的@GPRequestMapping("/demo")
            String baseUrl = "";
            if (clazz.isAnnotationPresent(DemoRequestMapping.class)) {
                DemoRequestMapping requestMapping = clazz.getAnnotation(DemoRequestMapping.class);
                baseUrl = requestMapping.value();
            }

            //默认获取所有的public方法
            for (Method method : clazz.getMethods()) {
                if (!method.isAnnotationPresent(DemoRequestMapping.class)) {
                    continue;
                }

                DemoRequestMapping requestMapping = method.getAnnotation(DemoRequestMapping.class);
                //优化
                // //demo///query
                String regex = ("/" + baseUrl + "/" + requestMapping.value())
                        .replaceAll("/+", "/");
                Pattern pattern = Pattern.compile(regex);
                this.handlerMapping.add(new Handler(pattern, entry.getValue(), method));
//                handlerMapping.put(url,method);
                System.out.println("Mapped :" + pattern + "," + method);

            }


        }


    }

    //自动依赖注入
    private void doAutowired() {
        if (ioc.isEmpty()) {
            return;
        }

        for (Map.Entry entry : ioc.entrySet()) {
            //Declared 所有的,特定的 字段,包括private/protected/default
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                if (!field.isAnnotationPresent(DemoAutowired.class)) {
                    continue;
                }
                DemoAutowired autowired = field.getAnnotation(DemoAutowired.class);

                //如果用户没有自定义beanName,默认就根据类型注入
                //这个地方省去了对类名首字母小写的情况的判断
                String beanName = autowired.value().trim();
                if ("".equals(beanName)) {
                    //获得接口的类型,作为key待会拿这个key到ioc容器中去取值
                    beanName = field.getType().getName();
                }

                field.setAccessible(true);

                try {
                    //用反射机制,动态给字段赋值
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }


            }

        }


    }

    private void doInstance() {
        //初始化,为DI做准备
        if (classNames.isEmpty()) {
            return;
        }

        try {
            for (String className : classNames) {
                Class clazz = Class.forName(className);

                if (clazz.isAnnotationPresent(DemoController.class)) {
                    Object instance = clazz.newInstance();
                    //Spring默认类名首字母小写
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName, instance);
                } else if (clazz.isAnnotationPresent(DemoService.class)) {
                    //1、自定义的beanName
                    DemoService service = clazz.getAnnotation(DemoService.class);
                    String beanName = service.value();
                    //2、默认类名首字母小写
                    if ("".equals(beanName.trim())) {
                        beanName = toLowerFirstCase(clazz.getSimpleName());
                    }

                    Object instance = clazz.newInstance();
                    ioc.put(beanName, instance);
                    //3、根据类型自动赋值,投机取巧的方式
                    for (Class i : clazz.getInterfaces()) {
                        if (ioc.containsKey(i.getName())) {
                            throw new Exception("The “" + i.getName() + "” is exists!!");
                        }
                        //把接口的类型直接当成key了
                        ioc.put(i.getName(), instance);
                    }
                } else {
                    continue;
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }


    //扫描出相关的类
    private void doScanner(String scanPackage) {
        //转换为文件路径,实际上就是把.替换为/就OK了
        //classpath
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\.", "/"));
        File classPath = new File(url.getFile());
        for (File file : classPath.listFiles()) {
            if (file.isDirectory()) {
                doScanner(scanPackage + "." + file.getName());
            } else {
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                String className = (scanPackage + "." + file.getName().replace(".class", ""));
                classNames.add(className);
            }
        }
    }


    //加载配置文件
    private void doLoadConfig(String contextConfigLocation) {
        InputStream fis = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            contextConfig.load(fis);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != fis) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    //保存一个url和一个Method的关系
    public class Handler {
        //必须把url放到HandlerMapping才好理解
        private Pattern pattern;  //正则
        private Method method;
        private Object controller;
        private Class[] paramTypes;

        public Pattern getPattern() {
            return pattern;
        }

        public Method getMethod() {
            return method;
        }

        public Object getController() {
            return controller;
        }

        public Class[] getParamTypes() {
            return paramTypes;
        }

        //形参列表
        //参数的名字作为key,参数的顺序,位置作为值
        private Map paramIndexMapping;

        public Handler(Pattern pattern, Object controller, Method method) {
            this.pattern = pattern;
            this.method = method;
            this.controller = controller;

            paramTypes = method.getParameterTypes();

            paramIndexMapping = new HashMap();
            putParamIndexMapping(method);
        }

        private void putParamIndexMapping(Method method) {

            //提取方法中加了注解的参数
            //把方法上的注解拿到,得到的是一个二维数组
            //因为一个参数可以有多个注解,而一个方法又有多个参数
            Annotation[][] pa = method.getParameterAnnotations();
            for (int i = 0; i < pa.length; i++) {
                for (Annotation a : pa[i]) {
                    if (a instanceof DemoRequestParam) {
                        String paramName = ((DemoRequestParam) a).value();
                        if (!"".equals(paramName.trim())) {
                            paramIndexMapping.put(paramName, i);
                        }
                    }
                }
            }

            //提取方法中的request和response参数
            Class[] paramsTypes = method.getParameterTypes();
            for (int i = 0; i < paramsTypes.length; i++) {
                Class type = paramsTypes[i];
                if (type == HttpServletRequest.class ||
                        type == HttpServletResponse.class) {
                    paramIndexMapping.put(type.getName(), i);
                }
            }

        }
    }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/327811.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号