手写MVC初体验(一)

手写MVC初体验(一)

一级目录

二级目录

三级目录

根据咕泡学院的课程,加入自己的理解,开始了手撸轮子的第一天。仿照spring mvc的运行流程,实现一个简单的Spring MVC框架。

1. 知识点

spring mvc 是基于 spring 的 mvc 框架 ,这次实现初始化Ioc和DI操作Controller层Service层。视图处理这块内容以后再说,我们先通过responce.getWriter().write()来返回数据。

手写体验主要是为了增进对Spring和SpringMVC的理解,从中可以学习到注解的使用、反射的使用。

下边就是手写Spring MVC的整体流程

2 项目框架

文件结构如上

第一版就是先实现

  • @Autowired
  • @Controller
  • @Service
  • @RequestMapping
  • @RequestParam
  • DispatcherServlet
  • web.xml
  • application.properties

3. 注解的编写

import java.lang.annotation.*;

/**
 * @author gyh
 * @csdn https://blog.csdn.net/qq_40788718
 * @date 2020/6/14 22:56
 */
@Target({ElementType.FIELD})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
    String value() default "" ;
}
import java.lang.annotation.*;

/**
 * @author gyh
 * @csdn https://blog.csdn.net/qq_40788718
 * @date 2020/6/14 22:53
 */
@Target({ElementType.TYPE})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
    String value() default "" ;
}
import java.lang.annotation.*;

/**
 * @author gyh
 * @csdn https://blog.csdn.net/qq_40788718
 * @date 2020/6/14 22:54
 */
@Target({ElementType.TYPE})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
    String value() default "" ;
}
import java.lang.annotation.*;

/**
 * @author gyh
 * @csdn https://blog.csdn.net/qq_40788718
 * @date 2020/6/14 22:54
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD , ElementType.TYPE})
@Documented
public @interface RequestMapping {
    String value() default "" ;
}
import java.lang.annotation.*;

/**
 * @author gyh
 * @csdn https://blog.csdn.net/qq_40788718
 * @date 2020/6/14 22:55
 */
@Target({ElementType.PARAMETER})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestParam {
    String value() default "" ;
}

其实注解就是根据Spring提供的注解来创建的,不同的一点就是@Autowired中有value,也就是既可以根据type注入,也可以根据name注入

4. web.xml 配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <display-name>GuoYHang Web Application</display-name>

    <servlet>
        <servlet-name>mvc</servlet-name>
        <servlet-class>org.framework.spring.mvc.servlet.v1.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfiguration</param-name>
            <param-value>application.properties</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

5. application.properties配置

scanPackage=org.framework.demo

这里只有一个包名,也就是需要扫描的包

6. DispatcherServlet编写-init初始化

1. 继承HttpServlet

import org.framework.spring.annotation.Autowired;
import org.framework.spring.annotation.Component;
import org.framework.spring.mvc.annotation.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * @author gyh
 * @csdn https://blog.csdn.net/qq_40788718
 * @date 2020/6/14 23:00
 */

public class DispatcherServlet extends HttpServlet {

    private Properties properties = new Properties() ;
    private List<String> classNameList = new ArrayList<String>();
    private Map<String , Object> ioc = new HashMap<String, Object>() ;
    private Map<String , Method> handlerMapping = new HashMap<String, Method>() ;

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

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
        //6.找到HandlerMapping执行方法,并执行
        try {
            doDispatcher(req , resp) ;
        } catch (IOException e) {
            e.printStackTrace();
            try {
                resp.getWriter().write("500");
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1.加载配置文件
        doLoadConfig(config.getInitParameter("contextConfiguration")) ;
        //2.扫描包下的类
        doScanner(properties.getProperty("scanPackage")) ;
        //3.完成Ioc容器
        doInstance() ;
        //4.DI注入
        doAutowired() ;
        //5.初始化HandlerMapping
        doInitHandlerMapping() ;
    }
}

2. 配置读取

编写doLoadConfig方法获取web.xml中的contextConfiguration对应的value值,也就是我们在application.properties中写的数据。

scanPackage=org.framework.demo

	
	//配置
	private Properties properties = new Properties() ;	

	/**
     * 加载web.xml中的配置
     * @param contextConfiguration
     * @throws IllegalArgumentException
     */
    private void doLoadConfig(String contextConfiguration) throws IllegalArgumentException {

        if ("".equals(contextConfiguration)){
            throw new IllegalArgumentException("Web Application need value of contextConfiguration in the web.xml") ;
        }
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(contextConfiguration) ;
        try {
            //加载application.properties文件
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (null != inputStream){
                try {
                    //关闭输入流
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3. 扫描包下的类和接口

	private List<String> classNameList = new ArrayList<String>();

	/**
     * 扫描类
     * application.properties下的scanPackage属性
     * @param scanPackage
     */
    private void doScanner(String scanPackage) {
        if (isNull(scanPackage) || !isPackageRegular(scanPackage)){
            throw new IllegalArgumentException("scanPackage of application.properties is error") ;
        }

        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());
                continue ;
            }
            //非.class文件
            if (!file.getName().endsWith(".class")) {
                continue;
            }
            classNameList.add(scanPackage + "." + file.getName().replace(".class","")) ;
        }
    }

4. 完成Ioc的注入

	private Map<String , Object> ioc = new HashMap<String, Object>() ;

	/**
     * 初始化Ioc容器
     */
    private void doInstance() {
        if (classNameList.isEmpty()){
            return ;
        }
        try{
            for (String className : classNameList) {
                //通过反射创建实例对象
                Class<?> clazz = Class.forName(className) ;
                //value、类名首字母小写、接口名
                String beanName = "";
                Object instance = null ;
				
                if (clazz.isAnnotationPresent(Controller.class)){
                    //controller
                    beanName = toLowerFirstAlp(clazz.getSimpleName()) ;
                    instance = clazz.newInstance() ;
                    ioc.put(beanName , instance) ;
                }else if (clazz.isAnnotationPresent(Service.class)){
                    //service
                    //获取注解
                    Service service = clazz.getAnnotation(Service.class) ;
                    String serviceValue ;
                    beanName = "".equals(serviceValue = service.value().trim()) ?
                            toLowerFirstAlp(clazz.getSimpleName()) : serviceValue ;
                    instance = clazz.newInstance() ;
                    ioc.put(beanName , instance) ;
                    //接口对应
                    for (Class<?> inter : clazz.getInterfaces()){
                        beanName = inter.getSimpleName() ;
                        if (ioc.containsKey(beanName)){
                            throw new Exception(beanName + " is exist") ;
                        }
                        ioc.put(beanName , instance) ;
                    }
                    continue ;
                }
            }
        }catch (ClassNotFoundException e){
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

	/**
     * 将类名的首字母小写
     * @param simpleName
     * @return
     */
    private String toLowerFirstAlp(String simpleName) {
        // TODO: 2020/6/16
        char[] chars = simpleName.toCharArray() ;
        chars[0] += 32 ;
        String name = new String(chars) ;
        return name ;
    }

实际上的Ioc是Map存储的一个beanName和instance对应的列表

5. 进行依赖注入

	
	/**
     * 依赖注入
     */
    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(Autowired.class)){
                    continue ;
                }
                //自动注入
                Autowired autowired = field.getAnnotation(Autowired.class) ;
                String autowiredValue ;
                //1.autowired -> value
                //2.field -> type
                String beanName = "".equals(autowiredValue = autowired.value().trim())
                        ? toLowerFirstAlp(field.getType().getSimpleName()) : autowiredValue ;
                //private域的属性开通道
                field.setAccessible(true) ;
                try {
                    field.set(entry.getValue() , ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                    continue ;
                }
            }
        }
    }

依赖注入是从Ioc容器中获取instance来注入到依赖的instance中

6.初始化HandlerMapping

	private Map<String , Method> handlerMapping = new HashMap<String, Method>() ;
	
	private void doInitHandlerMapping() {
        if (ioc.isEmpty()){
            return ;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass() ;
            if (!clazz.isAnnotationPresent(Controller.class)){
                continue ;
            }
            //controller中标记在类上的requestMapping的value
            String baseUrl = clazz.isAnnotationPresent(RequestMapping.class)
                    ? clazz.getAnnotation(RequestMapping.class).value().trim() : "" ;
            Method[] methods = clazz.getMethods() ;
            for (Method method : methods) {
                if (!method.isAnnotationPresent(RequestMapping.class)){
                    continue ;
                }
                String url = "/" + baseUrl + "/" + method.getAnnotation(RequestMapping.class).value() ;
                url = url.replaceAll("/+" , "/") ;
                handlerMapping.put(url , method) ;
            }
        }
    }

7. DispatcherServlet编写-doPost/doGet

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

我们将doPost和doGet合并为同一个,在调用doGet时,调用doPost

	@Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
        //6.找到HandlerMapping执行方法
        try {
            doDispatcher(req , resp) ;
        } catch (IOException e) {
            e.printStackTrace();
            try {
                resp.getWriter().write("500");
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }
private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String requestURI = req.getRequestURI() ;
        String contextPath = req.getContextPath() ;
        String url = requestURI.replaceAll(contextPath , "").replaceAll("/+" , "/") ;

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

        Method method = handlerMapping.get(url);
        Map<String , String[]> paramMap = req.getParameterMap() ;
        Class<?>[] paramTypes = method.getParameterTypes() ;
        Object[] paramValues = new Object[paramTypes.length] ;
		//进行参数的匹配
        for (int i=0 ; i<paramTypes.length ; i++){
            Class paramType = paramTypes[i] ;
            if (paramType == HttpServletRequest.class){
                paramValues[i] = req ;
            }else if (paramType == HttpServletResponse.class){
                paramValues[i] = resp ;
            }else if (paramType == String.class){
                //获取参数上的注解列表
                Annotation[][] annotations = method.getParameterAnnotations();
                //遍历第i个参数上的注解,通过注解中的value,给传参列表赋值
                for (Annotation a : annotations[i]){
                    if ( a instanceof RequestParam){
                        String paramName = ((RequestParam) a).value() ;
                        if (!"".equals(paramName)){
                            String value = Arrays.toString(paramMap.get(paramName))
                                    .replaceAll("\\[|\\]" , "")
                                    .replaceAll("\\s",",");
                            paramValues[i] = value ;
                        }
                    }
                }
            }
        }
        String beanName = toLowerFirstAlp(method.getDeclaringClass().getSimpleName()) ;
        try {
            //invoke反射执行
            method.invoke(ioc.get(beanName) , paramValues) ;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

8. 启动

添加测试

import org.framework.demo.service.DemoService;
import org.framework.spring.annotation.Autowired;
import org.framework.spring.mvc.annotation.Controller;
import org.framework.spring.mvc.annotation.RequestMapping;
import org.framework.spring.mvc.annotation.RequestParam;

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

/**
 * @author gyh
 * @csdn https://blog.csdn.net/qq_40788718
 * @date 2020/6/17 9:49
 */

@Controller
@RequestMapping(value = "/demo")
public class DemoController {

    @Autowired
    private DemoService demoService ;

    @RequestMapping(value = "/say")
    public void say(HttpServletRequest request , HttpServletResponse response , @RequestParam("name")String name){
        System.out.println(name);
        String result = demoService.process(name) ;
        try {
            response.getWriter().write("<h1>"+result+"</h1>");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
import org.framework.spring.mvc.annotation.Service;

/**
 * @author gyh
 * @csdn https://blog.csdn.net/qq_40788718
 * @date 2020/6/17 23:21
 */

@Service
public class DemoService {

    public String process(String name) {
        return "service : i am "+ name +"" ;
    }
}

首先要install项目,然后配置tomcat

http://localhost:8888/demo/say?name=Java

        response.getWriter().write("<h1>"+result+"</h1>");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

}


```java
import org.framework.spring.mvc.annotation.Service;

/**
 * @author gyh
 * @csdn https://blog.csdn.net/qq_40788718
 * @date 2020/6/17 23:21
 */

@Service
public class DemoService {

    public String process(String name) {
        return "service : i am "+ name +"" ;
    }
}

首先要install项目,然后配置tomcat

http://localhost:8888/demo/say?name=Java

[外链图片转存中…(img-rHxkYjJr-1592666642228)]

第一版结束,继续改进,加入理解

猜你喜欢

转载自blog.csdn.net/qq_40788718/article/details/106879513