手写SpringMVC (一) 简要版,去除冗余复杂代码,手写Spring核心功能

版权声明:本文为博主原创文章,转载请注明出处 https://blog.csdn.net/yjy91913/article/details/79471460

github 地址 :https://github.com/yjy91913/jerry-mvcframework
只是闲来无事写的简化版,仅供大家理解SpringMvc的运作原理)
了解了springMVC的源码,写一个功能简单可以实现的springMVC,只是为了深入了解spring

先看一下项目结构
这里写图片描述
demo是测试类

第一步:pom文件

加入依赖 -servlet

<dependency>
     <groupId>javax.servlet</groupId>
     <artifactId>javax.servlet-api</artifactId>
     <version>3.0.1</version>
 </dependency>

第二步:配置web.xml

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
         http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         metadata-complete="true" version="3.0">
    <display-name>
        Jerry web application
    </display-name>
    <!--配置一个servlet,这个配置一个servlet就是springMVC中的DispatcherServlet-->
    <servlet>
        <servlet-name>jmvc</servlet-name>
        <servlet-class>com.jerry.mvcframework.servlet.JDispatcherServlet</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>jmvc</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

第三步:配置扫描包的位置

这一步简化了,只有读取扫描包的位置
resources 目录下建立一个配置文件application.properties
scanPackage=com.jerry.demo

第四步:配置所有的常用注解

这个只写一下常用的注解

@JAutowired

@Target({ElementType.FIELD})     //字段、枚举的常量
@Retention(RetentionPolicy.RUNTIME)     //注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented     //说明该注解将被包含在javadoc中
public @interface JAutowired {
    String value() default "";
}

@JController

@Target({ElementType.TYPE})     //接口、类、枚举、注解
@Retention(RetentionPolicy.RUNTIME)     //注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented     //说明该注解将被包含在javadoc中
public @interface JController {
    String value() default "";
}

@JRequestMapping

@Target({ElementType.METHOD,ElementType.TYPE})     //字段、枚举的常量    方法
@Retention(RetentionPolicy.RUNTIME)     //注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented     //说明该注解将被包含在javadoc中
public @interface JRequestMapping {
    String value() default "";
}

@JRequestParam

@Target({ElementType.PARAMETER})     //方法参数
@Retention(RetentionPolicy.RUNTIME)     //注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented     //说明该注解将被包含在javadoc中
public @interface JRequestParam {
    String value() default "";
}

@JService

@Target({ElementType.TYPE})     //接口、类、枚举、注解
@Retention(RetentionPolicy.RUNTIME)     //注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented     //说明该注解将被包含在javadoc中
public @interface JService {
    String value() default "";
}

第五步:创建JDispatcherServlet

我们所要的做的事情,就是实现这个类
原声Servlet主要方法我们要处理就两个 一个:init doPost doGet 后面两个其实是一个方法

第六步:编写init()方法

主要需要实现的功能都在注释里

    @Override
    public void init(ServletConfig config) throws ServletException {

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

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

        //3.初始化所有相关的类Class的实例,并且将其保存到IOC容器
        doInstance();

        //4.自动化的依赖注入
        doAutowired();

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

        System.out.println("Jerry MVC Framework is init");

    }

第六步:编写doLoadConfig()方法

    private void doLoadConfig(String configLocation){
        //获取一个inputStream
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(configLocation);

        //加载配置文件
        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(null != inputStream) {

                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

第六步:编写doScanner()方法

这部是扫描我们指定的目录

    //初始化一个List,用于存放.class的地址
    private List<String> classNames = new ArrayList<String>();

    private void doScanner(String packageName){
        URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.", "/"));
        //获取所有的字节码文件
        File classDir = new File(url.getFile());

        for (File file : classDir.listFiles()) {
            //如果是文件夹递归执行
            if(file.isDirectory()){
                doScanner(packageName + "." + file.getName());
            } else {
                //输入是.class,加入list
                classNames.add(packageName + "." + file.getName().replace(".class",""));
            }
        }
    }

第七步:编写doInstance()方法

初始化所有相关的类Class的实例,并且将其保存到IOC容器

    //初始化ioc容器
    private Map<String,Object> ioc = new HashMap<String, Object>();

    //写一个讲第一个字母变成小写的方法
    private String lowerFirst(String str) {
        char[] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

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

        for (String className : classNames) {
            try {
                Class<?> clazz = Class.forName(className);
                //进行实例化
                //判断,是否有Controller等注解
                if(clazz.isAnnotationPresent(JController.class)){

                    String beanName = lowerFirst(clazz.getSimpleName());
                    ioc.put(beanName,clazz.newInstance());


                }else if (clazz.isAnnotationPresent(JService.class)){
                    JService jService = clazz.getAnnotation(JService.class);
                    String beanName = jService.value();
                    //1.默认采用类名首字母小写 beanId
                    //2.如果自定义名字,优先使用自己定义的名字
                    if("".equals(beanName.trim())){
                        beanName = lowerFirst(clazz.getSimpleName());
                    }

                    Object instance = clazz.newInstance();
                    ioc.put(beanName,instance);
                    //3.根据类型匹配,利用实现类的接口名字作为key,实现类的类做为value
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class<?> i : interfaces) {
                        ioc.put(lowerFirst(i.getSimpleName()),instance);
                    }
                }else {
                    continue;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

第七步:编写doAutowired()方法

自动化的依赖注入,扫描所有带autowired注解

private void doAutowired(){
        if(ioc.isEmpty()){
            return;
        }
        for (Map.Entry<String,Object> entry : ioc.entrySet()) {

            //在Spring中,没有隐私
            Field[] fields = entry.getValue().getClass().getDeclaredFields();


            for (Field field : fields) {
                //找autowired
                if(!field.isAnnotationPresent(JAutowired.class)){
                    continue;
                }

                JAutowired jAutowired = field.getAnnotation(JAutowired.class);
                String beanName = jAutowired.value().trim();

                if("".equals(beanName)){
                    beanName = lowerFirst(field.getType().getSimpleName());
                }
                //暴力反射
                field.setAccessible(true);
                try {
                    field.set(entry.getValue(),ioc.get(beanName));
                    System.out.println(entry.getValue()+" is autowired ,object is "+ioc.get(beanName));
                } catch (Exception e) {
                    e.printStackTrace();
                    continue;
                }
            }
        }
    }

第七步:初始化HandlerMapping

初始化一个HandlerMapping

private Map<String,Handler> handlerMapping = new HashMap<String, Handler>();

写一个handler类

    @Data
    @ToString
    private class Handler {
        protected Object controller;
        protected Method method;
        protected Pattern pattern;
        protected Map<String,Integer> paramIndexMapping;
    }

编写initHandlerMapping()方法

    private void initHandlerMapping() {

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

        for (Map.Entry<String,Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            //HandlerMapping 只认JController
            if(!clazz.isAnnotationPresent(JController.class)){
                continue;
            }
            String url = "";
            if(clazz.isAnnotationPresent(JRequestMapping.class)){
                JRequestMapping jRequestMapping = clazz.getAnnotation(JRequestMapping.class);
                url = jRequestMapping.value().trim();
            }
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                //如果没有JRequestMapping ,直接跳过
                if(!method.isAnnotationPresent(JRequestMapping.class)){
                    continue;
                }
                JRequestMapping jRequestMapping = method.getAnnotation(JRequestMapping.class);
                String murl = url + jRequestMapping.value().trim();

                Handler handler = new Handler();
                handler.setController(entry.getValue());
                handler.setMethod(method);
                handlerMapping.put(murl,handler);

                System.out.println("Mapping : "+ murl + "  " +handler);
            }
        }
    }

到这里springMVC初始化方法已经写完了,下面就是
doDispatch()方法了 ,下次在写

猜你喜欢

转载自blog.csdn.net/yjy91913/article/details/79471460