手写MVC框架

1. MVC框架执行的大致原理

2. 注解开发

自定义controller @MyController
/**
 * 自定义controller注解
 */
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyController {
    String value() default "";
}
自定义Service @MyService
/**
 * 自定义service注解
 */
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyService {
    String value() default "";
}
自定义RequestMapping @MyRequestMapping
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
    String value() default "";
}
自定义Autowired @MyAutowired
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutowired {
    String value() default "";
}

3. 自定义DispatcherServlet

public class MyDispatcherServlet extends HttpServlet {

    // 读取properties文件
    private Properties properties = new Properties();
    // 存放所有class全限定类名
    private List<String> classNames = new ArrayList<>();
    // ioc 容器
    private Map<String, Object> iocMap = new HashMap<>();

    // 存放所有URL和method的映射
    List<Handler> handlerList = new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1 加载配置文件 springmvc.properties
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        try {
            doLoadConfig(contextConfigLocation);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 2 获取需要扫描的包下的所有class文件
        doScan(properties.getProperty("scanPackage"));


        // 3 初始化bean对象(实现ioc容器,基于注解)
        doInstance();

        // 4 实现依赖注入
        doAutoWired();

        // 5 构造一个HandlerMapping处理器映射器,将配置好的url和Method建立映射关系
        initHandlerMapping();

        System.out.println("mvc 初始化完成....");

        // 等待请求进入,处理请求
    }

    /**
     * 配置url和Method的映射关系
     */
    private void initHandlerMapping() {
        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
            Object obj = entry.getValue();
            // 先查看当前类上是否含有@MyRequestMapping注解
            String baseMappingUrl = "";
            if (obj.getClass().isAnnotationPresent(MyRequestMapping.class)) {
                MyRequestMapping annotation = obj.getClass().getAnnotation(MyRequestMapping.class);
                baseMappingUrl = annotation.value();
            }
            // 获取当前类下的所有方法
            Method[] methods = obj.getClass().getMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                // 判断当前方法是否含有@MyRequestMapping注解
                if (method.isAnnotationPresent(MyRequestMapping.class)) {
                    // 获取MyRequestMapping的值
                    MyRequestMapping myRequestMapping = method.getAnnotation(MyRequestMapping.class);
                    //映射路径
                    String value = baseMappingUrl + myRequestMapping.value();

                    Handler handler = new Handler(Pattern.compile(value), obj, method);
                    // 获取方法的参数索引
                    Parameter[] parameters = method.getParameters();
                    for (int j = 0; j < parameters.length; j++) {
                        Parameter parameter = parameters[j];
                        if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
                            handler.getIndexMap().put(parameter.getType().getSimpleName(), j);
                        } else {
                            handler.getIndexMap().put(parameter.getName(), j);
                        }
                    }
                    handlerList.add(handler);
                }
            }
        }
    }

    /**
     * 依赖注入
     */
    private void doAutoWired() {
        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
            Object obj = entry.getValue();
            // 获取当前类下的所有属性
            Field[] declaredFields = obj.getClass().getDeclaredFields();
            for (int i = 0; i < declaredFields.length; i++) {
                Field declaredField = declaredFields[i];
                // 判断当前属性是否含有MyAutowired注解
                if (declaredField.isAnnotationPresent(MyAutowired.class)) {
                    MyAutowired myAutowired = declaredField.getAnnotation(MyAutowired.class);
                    // 查看当前注解是否含有自定义beanID
                    String value = myAutowired.value();
                    if (StringUtils.isBlank(value)) {
                        // 没有配置具体的bean id,那就需要根据当前字段类型注入(接口注入)  IDemoService
                        value = declaredField.getType().getName();
                    }

                    //暴力访问
                    declaredField.setAccessible(true);

                    try {
                        // 为属性赋值
                        declaredField.set(obj, iocMap.get(value));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * 初始化带有注解的bean
     * @throws Exception
     */
    private void doInstance() {
        if (classNames.size() == 0) return;
        try {
            for (int i = 0; i < classNames.size(); i++) {
                 String className = classNames.get(i);
                if (StringUtils.isNotBlank(className)) {
                    Class<?> aClass = Class.forName(className);
                    // 将带有注解的实例存放在IOC容器中
                    if (aClass.isAnnotationPresent(MyController.class)) {
                        // 获取注解的值  作为bean的id   controller不做处理  直接以类名首字母小写作为id
                        String simpleName = aClass.getSimpleName(); // DemoController
                        // 首字母小写
                        String id = doFirstLower(simpleName);  // demoController
                        Object instance = aClass.newInstance();
                        iocMap.put(id, instance);
                    } else if (aClass.isAnnotationPresent(MyService.class)) {
                        // 获取注解的值  作为bean的id
                        MyService myService = aClass.getAnnotation(MyService.class);
                        String value = myService.value();
                        if (StringUtils.isBlank(value)) {
                            value = doFirstLower(aClass.getSimpleName());
                        }
                        Object instance = aClass.newInstance();
                        iocMap.put(value, instance);
                        // service层往往是有接口的,面向接口开发,此时再以接口名为id,放入一份对象到ioc中,便于后期根据接口类型注入
                        Class<?>[] interfaces = aClass.getInterfaces();
                        for (int j = 0; j < interfaces.length; j++) {
                            Class<?> anInterface = interfaces[j];
                            // 按照接口的全限定类名存储
                            iocMap.put(anInterface.getName(), instance);
                        }
                    } else {
                        continue;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 首字母小写
     * @param simpleName
     * @return
     */
    private String doFirstLower(String simpleName) {
        char[] chars = simpleName.toCharArray();
        char aChar = chars[0];
        if (aChar >= 'A' && aChar <= 'Z') {
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }

    /**
     * 获取需要扫描的包下的所有class文件
     * @param basePackage
     */
    private void doScan(String basePackage) {
        String path = Thread.currentThread().getContextClassLoader().getResource("").getPath() + basePackage.replaceAll("\\.", "/");
        File file = new File(path);
        File[] files = file.listFiles();
        for (int i = 0; i < files.length; i++) {
            File file1 = files[i];
            // 如果是文件夹 递归
            if (file1.isDirectory()) {
                this.doScan(basePackage + "." + file1.getName());
            } else if (file1.getName().endsWith("class")) {
                //是.class文件   获取全限定类名
                String s = basePackage + "." + file1.getName().replace(".class", "");
                // 将所有class全限定类名存储   以便之后初始化
                classNames.add(s);
            }
        }

    }

    private void doLoadConfig(String contextConfigLocation) throws IOException {
        // 不处理classpath:
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);

        properties.load(resourceAsStream);
    }


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

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //TODO do something
        // 通过request中的URI获取handler
        Handler handler = getHandler(req);
        if (null == handler) {
            resp.getWriter().write("404");
            return;
        }

        // 获取当前方法
        Method method = handler.getMethod();
        // 获取方法的所有参数类型
        Class<?>[] parameterTypes = method.getParameterTypes();
        // 定义一个参数数组  执行方法时传递的参数   数组个数应与参数类型的长度一致
        Object[] params = new Object[parameterTypes.length];

        // 获取请求中的所有参数
        Map<String, String[]> parameterMap = req.getParameterMap();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            // 判断handler中参数名称列表中是否包含当前参数
            if (!handler.getIndexMap().containsKey(entry.getKey())) continue;
            // 拼接value
            String value = StringUtils.join(entry.getValue(), ",");
            // 获取当前参数在形参中的索引
            Integer index = handler.getIndexMap().get(entry.getKey());
            // 将value放入参数数组的对应位置
            params[index] = value;
        }
        // 将request放入参数数组的对应位置
        Integer requestIndex = handler.getIndexMap().get(HttpServletRequest.class.getSimpleName());
        if (requestIndex > -1) {
            params[requestIndex] = req;
        }
        // 将response放入参数数组的对应位置
        Integer responseIndex = handler.getIndexMap().get(HttpServletResponse.class.getSimpleName());
        if (responseIndex > -1) {
            params[responseIndex] = resp;
        }
        // 执行方法
        try {
            method.invoke(handler.getObject(), params);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Handler getHandler(HttpServletRequest req) {
        if (handlerList.size() == 0) return null;
        for (Handler handler : handlerList) {
            if (handler.getUrl().matcher(req.getRequestURI()).matches()) {
                return handler;
            }
        }
        return null;

    }

}

4. Handler

public class Handler {

    private Pattern url;

    // 执行Method.invoke(Object obj, args) 时需要
    private Object object;

    // 当前方法
    private Method method;

    // 参数索引
    private Map<String, Integer> indexMap;

    public Handler() {
    }

    public Handler(Pattern url, Object object, Method method) {
        this.url = url;
        this.object = object;
        this.method = method;
        this.indexMap = new HashMap<>();
    }

    public Pattern getUrl() {
        return url;
    }

    public void setUrl(Pattern url) {
        this.url = url;
    }

    public Object getObject() {
        return object;
    }

    public void setObject(Object object) {
        this.object = object;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public Map<String, Integer> getIndexMap() {
        return indexMap;
    }

    public void setIndexMap(Map<String, Integer> indexMap) {
        this.indexMap = indexMap;
    }
}

5. springmvc.properties

scanPackage=com.example

6. web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <servlet>
    <servlet-name>mvc</servlet-name>
    <servlet-class>com.example.servlet.MyDispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>springmvc.properties</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>mvc</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>

7. pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<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>com.example</groupId>
    <artifactId>springmvc</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>springmvc Maven Webapp</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <!-- scope必须设置为provided,否则不会识别自定义的MyDispatcherServlet -->
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!--编译插件定义编译细节-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                    <encoding>utf-8</encoding>
                    <!--
                      告诉编译器,编译的时候记录下形参的真实名称
                      否则  method.getParameters()
                      获取到的Parameter   getName()时获取到的不是参数的名称
                      而是 arg1 arg2
                    -->
                    <compilerArgs>
                        <arg>-parameters</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <port>8080</port>
                    <path>/</path>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>