浓缩就是精华,用300行最简洁的代码提炼 Spring 的基本设计思想1.0版本

最近在看spring源码,纸上得来终觉浅,觉知此事要躬行,先根据基本思路写一个mini版本,后续有时间再一点点丰富,慢慢深入理解,markdown一下。

先介绍下mini版本的spring基本思路:

自定义配置:

配置 application.properties 文件

为了解析方便,我们用 application.properties 来代替 application.xml 文件,具体配置内容如下:

scanPackage=com.dyx.demo

 配置 web.xml 文件

依赖于 web 容器的项目,都是从读取 web.xml 文件开始的。我们先配置好web.xml中的内容。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee"
	xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
	version="2.4">
	<display-name>dyx Web Application</display-name>
	<servlet>
		<servlet-name>dyxmvc</servlet-name>
		<servlet-class>com.dyx.mvcframework.v2.servlet.DYXDispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>application.properties</param-value>
		</init-param>

		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>dyxmvc</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>
</web-app>

自定义 Annotation :

@DYXService 注解:

package com.dyx.mvcframework.annotation;

import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DYXService {
    String value() default "";
}

@DYXAutowired 注解: 

package com.dyx.mvcframework.annotation;

import java.lang.annotation.*;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DYXAutowired {
    String value() default "";
}

@DYXController 注解:

package com.dyx.mvcframework.annotation;

import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DYXController {
    String value() default "";
}

@DYXRequestMapping 注解:

package com.dyx.mvcframework.annotation;

import java.lang.annotation.*;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DYXRequestMapping {
    String value() default "";
}

@DYXRequestParam 注解:

package com.dyx.mvcframework.annotation;

import java.lang.annotation.*;

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DYXRequestParam {
    String value() default "";
}

配置 Annotation :

配置业务实现类 DemoService:

 

package com.dyx.demo.service.impl;

import com.dyx.mvcframework.annotation.DYXService;
import com.dyx.demo.service.IDemoService;

/**
 * 核心业务逻辑
 */
@DYXService
public class DemoService implements IDemoService{

	public String get(String name) {
		return "My name is " + name;
	}

}

配置请求入口类 DemoAction: 

package com.dyx.demo.mvc.action;

import com.dyx.demo.service.IDemoService;
import com.dyx.mvcframework.annotation.DYXAutowired;
import com.dyx.mvcframework.annotation.DYXController;
import com.dyx.mvcframework.annotation.DYXRequestMapping;
import com.dyx.mvcframework.annotation.DYXRequestParam;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


//虽然,用法一样,但是没有功能
@DYXController
@DYXRequestMapping("/demo")
public class DemoAction {

  	@DYXAutowired
	private IDemoService demoService;

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


}

容器初始化 

基本框架如下:

    //初始化
   public void init(ServletConfig config) throws ServletException{
      //1.加载配置文件
      doLoadConfig(config.getInitParameter("contextConfigLocation"));

      //扫描相关类
      doScanner(contextConfig.getProperty("scanPackage"));
      //初始化扫描到的类,并且将他们放到ioc容器中
      doInstance();
      //完成依赖注入
      doAutowired();
      //初始化handlerMapping
      initHandlerMapping();
      System.out.println("spring init is end");
   }

声明全局的成员变量: 

    //保存application.properties配置文件中的内容
    private Properties contextConfig =new Properties();
    //保存扫描的所有的类名
    private List<String> classNames = new ArrayList<String>();
    //ioc容器 暂时不考虑ConcurrentHashMap
    private Map<String,Object> ioc = new HashMap<String,Object>();

    //保存url和Method的对应关系
    private List<Handler> handlerMapping = new ArrayList();

实现 doLoadConfig()方法加载配置文件: 

   //加载文件配置
   private void doLoadConfig(String contextConfigLocation){
       //直接从类路径下找到Spring主配置文件所在的路径
       //并且将其读取出来放到Properties对象中
       //相当于从文件中保存到了内存中
       InputStream fis = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
       try{
          contextConfig.load(fis);
       } catch (Exception e){
           e.printStackTrace();
       }finally{
           if(null != fis){
               try{
                   fis.close();
                   } catch (Exception e){
                 e.printStackTrace();
               }
           }

       }

   }

 实现 doScanner()方法扫描相关类:

//扫描相关的类
   private void doScanner(String scanPackage){
       //scanPackage = com.dyx.demo ,存储的是包路径
       //转换为文件路径,实际上就是把.替换为/就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);
          }

       }
   }

实现 doInstance()方法将扫描到的类放入到ICO容器之中

 

/**
     * 初始化扫描到的类放入到ICO容器之中
     */
   private void doInstance(){
     //如果没有扫描的类则直接返回
     if(classNames.isEmpty()){return;}
     try{
         for (String className :classNames) {
           Class<?> clazz = Class.forName(className);
           //初始化加注解的类,这里只初始化@Controller和@Service
           if(clazz.isAnnotationPresent(DYXController.class)){
               Object instance = (Object) clazz.newInstance();
               //spring类首字母小写
               String beanName = toLowerFirstCase(clazz.getSimpleName());
               ioc.put(beanName,instance);
           } else if(clazz.isAnnotationPresent(DYXService.class)){
              //自定义的beanName
              DYXService service = clazz.getAnnotation(DYXService.class);
              String beanName = service.value();
              //默认首字母小写
              if("".equals(beanName.trim())){
                  beanName = toLowerFirstCase(clazz.getSimpleName());
              }
              Object instance = clazz.newInstance();
              ioc.put(beanName,instance);
              //根据类型自动赋值,采用投机取巧的方式
              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();
     }

   }

为了处理方便,自己实现了 toLowerFirstCase 方法,来实现类名首字母小写,具体代码如下: 

    /**
     *    如果类名本身是小写字母,确实会出问题
     *    传值也是自己传,类也都遵循了驼峰命名法
     *    默认传入的值,存在首字母小写的情况,也不可能出现非字母的情况
     *    为了简化程序逻辑,就不做其他判断了,大家了解就OK
     */
   private String toLowerFirstCase(String simpleName){
      char[] chars = simpleName.toCharArray();
      //Java中,对char做算学运算,实际上就是对ASCII码做算学运算,大写字母的ASCII码要小于小写字母的ASCII码,相差32
      chars[0] += 32;
      return  String.valueOf(chars);
   }

实现 doAutowired()方法自动依赖注入

 

/**
     *自动依赖注入
     */
   private void doAutowired(){
       //如果ioc容器为空则直接返回
      if(ioc.isEmpty()){return;}
      //循环处理
       for (Map.Entry<String,Object> entry:ioc.entrySet()) {
           //Declared 所有的,特定的 字段,包括private/protected/default
           //正常来说,普通的OOP编程只能拿到public的属性
           Field[] fields = entry.getValue().getClass().getDeclaredFields();
           for (Field field:fields) {
             //如果不是注入注解则跳出循环
             if(!field.isAnnotationPresent(DYXAutowired.class)){continue;}
               DYXAutowired autowired = field.getAnnotation(DYXAutowired.class);
               //如果用户没有自定义beanName,默认就根据类型注入(待补充对首字母小写的判断)
               String beanName = autowired.value().trim();
               //如果beanName 为空则用接口类型去ioc容器中获取
               if("".equals(beanName)){
                 beanName =field.getType().getName();
               }
               //如果是public以外的修饰符,只要加了@Autowired注解,都要强制赋值
               //反射中叫做暴力访问, 强吻
               field.setAccessible(true);
               //用反射机制,动态给字段赋值
               try {
                 field.set(entry.getValue(),ioc.get(beanName));
               }catch (IllegalAccessException e){
                   e.printStackTrace();
               }

           }

       }

   }

实现 initHandlerMapping()方法 ,初始化url和Method的一对一对应关系

    /**
     * 初始化url和Method的一对一对应关系
     */
    private void initHandlerMapping(){
        //如果ioc容器为空则直接返回
        if(ioc.isEmpty()){return;}
        //循环处理
        for (Map.Entry<String,Object> entry:ioc.entrySet()) {
          Class<?> clazz = entry.getValue().getClass();
          //不是controller则退出
          if(!clazz.isAnnotationPresent(DYXController.class)){continue;}
            //保存写在类上面的@DYXRequestMapping("/demo")
            String baseUrl = "";
            if(clazz.isAnnotationPresent(DYXRequestMapping.class)){
                DYXRequestMapping requestMapping = clazz.getAnnotation(DYXRequestMapping.class);
                baseUrl = requestMapping.value();
            }
           //默认获取所有的public方法
            for (Method method:clazz.getMethods()) {
             if(!method.isAnnotationPresent(DYXRequestMapping.class)){continue;}
                DYXRequestMapping requestMapping = method.getAnnotation(DYXRequestMapping.class);

                //正则
                String regex =("/"+baseUrl+"/"+requestMapping.value())
                        .replaceAll("/+","/");

                Pattern pattern = Pattern.compile(regex);
                this.handlerMapping.add(new Handler(pattern,entry.getValue(),method));
                System.out.println("Mapped:"+pattern+","+method);
            }
        }
    }

到这里位置初始化阶段就已经完成,接下实现运行阶段的逻辑

运行阶段 :

doPost/doGet 的代码

 @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{
      try{
          doDispatch(req,resp);
      }catch (Exception e){
          e.printStackTrace();
          resp.getWriter().write("500 Exception,Detail:"+Arrays.toString(e.getStackTrace()));
      }
    }

doPost()方法中,的具体逻辑在 doDispatch()方法中: 

其中为了支持正则添加getHandler()方法,为了支持强制类型转换加了convert()方法。


    //主调度器
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{
     Handler handler =getHandler(req);
     if(handler == null ){
         resp.getWriter().write("404 Not Found!!!");
         return;
     }
     //获取形参列表
        Class<?> [] paramTypes = handler.getParamTypes();

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

        Map<String,String[]> params = req.getParameterMap();
        for (Map.Entry<String, String[]> 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;
        }

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




    }

    //获取hander
    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 java.lang.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;
    }

 完整的pom文件如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>dyx-spring-1.0</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>


    <properties>
        <!-- dependency versions -->
        <servlet.api.version>2.4</servlet.api.version>
    </properties>


    <dependencies>

        <!-- requied start -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>${servlet.api.version}</version>
            <scope>provided</scope>
        </dependency>
        <!-- requied end -->

    </dependencies>

    <build>
        <finalName>${artifactId}</finalName>
        <resources>
            <resource>
                <directory>${basedir}/src/main/resources</directory>
                <includes>
                    <include>**/*</include>
                </includes>
            </resource>
            <resource>
                <directory>${basedir}/src/main/java</directory>
                <excludes>
                    <exclude>**/*.java</exclude>
                    <exclude>**/*.class</exclude>
                </excludes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                    <encoding>UTF-8</encoding>
                    <compilerArguments>
                        <verbose />
                        <bootclasspath>${java.home}/lib/rt.jar</bootclasspath>
                    </compilerArguments>
                </configuration>
            </plugin>

            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.5</version>
                <executions>
                    <execution>
                        <id>copy-resources</id>
                        <!-- here the phase you need -->
                        <phase>validate</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <encoding>UTF-8</encoding>
                            <outputDirectory>${basedir}/target/classes</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>src/main/resources</directory>
                                    <includes>
                                        <include>**/*.*</include>
                                    </includes>
                                    <filtering>true</filtering>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>maven-jetty-plugin</artifactId>
                <version>6.1.26</version>
                <configuration>
                    <webDefaultXml>src/main/resources/webdefault.xml</webDefaultXml>
                    <contextPath>/</contextPath>
                    <connectors>
                        <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
                            <port>8080</port>
                        </connector>
                    </connectors>
                    <scanIntervalSeconds>0</scanIntervalSeconds>
                    <scanTargetPatterns>
                        <scanTargetPattern>
                            <directory>src/main/webapp</directory>
                            <includes>
                                <include>**/*.xml</include>
                                <include>**/*.properties</include>
                            </includes>
                        </scanTargetPattern>
                    </scanTargetPatterns>
                    <systemProperties>
                        <systemProperty>
                            <name>
                                javax.xml.parsers.DocumentBuilderFactory
                            </name>
                            <value>
                                com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl
                            </value>
                        </systemProperty>
                        <systemProperty>
                            <name>
                                javax.xml.parsers.SAXParserFactory
                            </name>
                            <value>
                                com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl
                            </value>
                        </systemProperty>
                        <systemProperty>
                            <name>
                                javax.xml.transform.TransformerFactory
                            </name>
                            <value>
                                com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
                            </value>
                        </systemProperty>
                        <systemProperty>
                            <name>org.eclipse.jetty.util.URI.charset</name>
                            <value>UTF-8</value>
                        </systemProperty>
                    </systemProperties>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <archive>
                        <addMavenDescriptor>false</addMavenDescriptor>
                    </archive>
                    <webResources>
                        <resource>
                            <!-- this is relative to the pom.xml directory -->
                            <directory>src/main/resources/</directory>
                            <targetPath>WEB-INF/classes</targetPath>
                            <includes>
                                <include>**/*.*</include>
                            </includes>
                            <!-- <excludes>
                                <exclude>**/local</exclude>
                                <exclude>**/test</exclude>
                                <exclude>**/product</exclude>
                            </excludes> -->
                            <filtering>true</filtering>
                        </resource>
                        <resource>
                            <!-- this is relative to the pom.xml directory -->
                            <directory>src/main/resources</directory>
                            <targetPath>WEB-INF/classes</targetPath>
                            <filtering>true</filtering>
                        </resource>
                    </webResources>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.zeroturnaround</groupId>
                <artifactId>javarebel-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <id>generate-rebel-xml</id>
                        <phase>process-resources</phase>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
                <version>1.0.5</version>
            </plugin>

        </plugins>
        <pluginManagement>
            <plugins>
                <!--This plugin's configuration is used to store Eclipse m2e settings
                    only. It has no influence on the Maven build itself. -->
                <plugin>
                    <groupId>org.eclipse.m2e</groupId>
                    <artifactId>lifecycle-mapping</artifactId>
                    <version>1.0.0</version>
                    <configuration>
                        <lifecycleMappingMetadata>
                            <pluginExecutions>
                                <pluginExecution>
                                    <pluginExecutionFilter>
                                        <groupId>
                                            org.zeroturnaround
                                        </groupId>
                                        <artifactId>
                                            javarebel-maven-plugin
                                        </artifactId>
                                        <versionRange>
                                            [1.0.5,)
                                        </versionRange>
                                        <goals>
                                            <goal>generate</goal>
                                        </goals>
                                    </pluginExecutionFilter>
                                    <action>
                                        <ignore></ignore>
                                    </action>
                                </pluginExecution>
                            </pluginExecutions>
                        </lifecycleMappingMetadata>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

 运行效果演示:

 

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值