手写一个山寨版的springmvc框架

首先贴出来一张从网上copy下来的springmvc工作原理图
在这里插入图片描述
分析:其实springmvc只不过是把servlet进行了封装和处理,下面就开始手写一个简易版的springmvc框架。另外,客户端发送一个请求到前端控制器DispatcherServlet,所谓的前端控制器只是封装后的一个servlet,这个servlet接收到请求后在初始化init()方法中要做下面几件事情。

  1. 扫描所有的类
  2. 反射实例化controller、service类,放入ioc容器
  3. 注入autowired处理
  4. 请求路径映射处理

一,环境准备

  1. eclipse
  2. servlet-api.jar
  3. tomcat

二,项目结构搭建

在写之前要把准备工作都做好,项目结构如下图
在这里插入图片描述
com.qianlong.zhujie包中要把springmvc框架中的常用注解都写进去,当然是我们的自定义注解了,山寨版的嘛!
DnAutowired注解

@Retention(RetentionPolicy.RUNTIME)//选择运行环境
@Target(ElementType.FIELD)//限定作用域,限制自定义的注解只能放在属性上面
public @interface DnAutowired {
    String value() default "";
}

DnController注解

@Retention(RetentionPolicy.RUNTIME)//选择运行环境
@Target(ElementType.TYPE)//限定作用域,限制自定义的注解只能放在类上面
public @interface DnController {
    String value() default "";
}

DnRequestMapping注解

@Retention(RetentionPolicy.RUNTIME)//选择运行环境
@Target({ElementType.TYPE,ElementType.METHOD})//限定作用域,放在类上面和方法上面
public @interface DnRequestMapping {
    String value() default "";
}

DnRequestParam注解

@Retention(RetentionPolicy.RUNTIME)//选择运行环境
@Target(ElementType.PARAMETER)//限定作用域,限制自定义的注解只能放在参数上面
public @interface DnRequestParam {
    String value() default "";
}

DnService注解

@Retention(RetentionPolicy.RUNTIME)//选择运行环境
@Target(ElementType.TYPE)//限定作用域,限制自定义的注解只能放在类上面
public @interface DnService {
    String value() default "";
}

三,简易版的前端控制器 DnDispatcherServlet

com.qianlong.servlet包里新建servlet文件名为DnDispatcherServlet并继承HttpServlet,也就是前端控制器。
然后在pom.xml文件中加入servlet配置,加入配置后,tomcat一启动就进到了这个servlet

<servlet>
        <servlet-name>DnDispathcherServlet</servlet-name>
        <servlet-class>com.qianlong.servlet.DnDispathcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup><!--tomcat已启动首先进入这个servlet-->
    </servlet>
    <servlet-mapping>
        <servlet-name>DnDispathcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

然后开始写DnDispatcherServlet的内容。
init()方法中写准备工作,在doPost()方法中处理客户端发来的请求。

package com.qianlong.servlet;
import com.qianlong.controller.TestController;
import com.qianlong.zhujie.*;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
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.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DnDispathcherServlet extends HttpServlet {
    //扫描到的类的容器
    private List<String> classNames=new ArrayList<String>();
    //ioc容器,其实就是一个map集合
    private Map<String,Object> beans=new HashMap<String, Object>();
    //存放urlMapping的集合
    private Map<String,Object> urlMapping=new HashMap<String, Object>();

    @Override
    public void init() throws ServletException {
        //1,扫描类
        doScanPackage("com.qianlong");
        //2,反射实例化controller、service类,放入ioc容器
        doInstance();
        //3,注入autowired
        doAutowired();
        //4,请求路径映射
        doUrlMapping();
    }
    /**
     * 请求路径映射
     */
    private void doUrlMapping() {
        try{
            //另一种遍历map的方式
            for(Map.Entry<String,Object> entry:beans.entrySet()){
            	//从ioc容器beans中拿到实例化的controller类和service类的对象
                Object instance = entry.getValue();
                //实例化的对象得到类
                Class<?> clazz = instance.getClass();
                //如果该类被DnController注解
                if(clazz.isAnnotationPresent(DnController.class)){
                    //得到DnRequestMapping注解在方法上的映射值
                    DnRequestMapping classAnnotation=clazz.getAnnotation(DnRequestMapping.class);
                    String classPath = classAnnotation.value();
                    //得到该类中所有的方法
                    Method[] methods = clazz.getDeclaredMethods();
                    //遍历所有方法
                    for(Method method:methods){
                        //得到所有方法上面DnRequestMapping注解的映射值
                        DnRequestMapping methodAnnotation=method.getAnnotation(DnRequestMapping.class);
                        String methodPath = methodAnnotation.value();
                        //把映射拼接到一起放到urlMapping集合里
                        urlMapping.put(classPath+methodPath,method);
                    }
                }
            }
        }catch (Exception e){

        }
    }
    /**
     * 注入autowired
     */
    private void doAutowired() {
        try {
            //另一种遍历map的方式
            for(Map.Entry<String,Object> entry:beans.entrySet()){
            	//从ioc容器beans中拿到实例化的controller类和service类的对象
                Object instance = entry.getValue();
                //实例化的对象得到类
                Class<?> clazz = instance.getClass();
                //如果该类被DnController注解
                if(clazz.isAnnotationPresent(DnController.class)){
                    //得到该类中所有的属性
                    Field[] fields = clazz.getDeclaredFields();
                    for(Field field:fields){
                        //如果该属性被DnAutowired注解
                        if(field.isAnnotationPresent(DnAutowired.class)){
                            //拿到这个注解的值
                            DnAutowired annotation = field.getAnnotation(DnAutowired.class);
                            String key=annotation.value();
                            //私有属性强制控制
                            field.setAccessible(true);
                            //给属性赋值,set的两个参数(本属性属于哪个类,值)
                            field.set(instance,beans.get(key));
                        }
                    }
                }
            }
        }catch (Exception e){

        }
    }
    /**
     * 反射实例化controller、service类,放入ioc容器
     */
    private void doInstance(){
        try{
            for(String className:classNames){
                //拿到类的全路径名,去掉后缀名
                String cn = className.replace(".class", "");
                //得到类
                Class<?> clazz = Class.forName(cn);
                //实例化controller类和service类
                //如果该类被@DnController注解了
                if(clazz.isAnnotationPresent(DnController.class)){
                    //实例化(创建此 Class 对象所表示的类的一个新实例)
                    Object controllerInstance = clazz.newInstance();
                    //拿到@DnRequestMapping注解的值
                    DnRequestMapping annotation=clazz.getAnnotation(DnRequestMapping.class);
                    String key = annotation.value();
                    //放到ioc容器中
                    beans.put(key,controllerInstance);
                    //如果该类被@DnService注解了
                }else if(clazz.isAnnotationPresent(DnService.class)){
                    //实例化
                    Object serviceInstance = clazz.newInstance();
                    //拿到@DnService注解的值
                    DnService annotation=clazz.getAnnotation(DnService.class);
                    String key = annotation.value();
                    //放到ioc容器中
                    beans.put(key,serviceInstance);
                }

            }
        }catch (Exception e){

        }
    }
    /**
     * 扫描类
     * @param packages
     */
    private void doScanPackage(String packages) {
        //拿到包路径 ,path以'/'开头时,则是从项目的ClassPath根下获取资源,返回一个url
        URL url = this.getClass().getClassLoader().getResource("/" + packages.replaceAll("\\.", "/"));
        //获取此 URL 的文件名(这里是包名com.qianlong)
        String files = url.getFile();
        //创建流对象
        File fileAll=new File(files);
        //对该包下的文件判断
        for(File file:fileAll.listFiles()){
            if(file.isDirectory()){
                //递归扫描
                doScanPackage(packages+"."+file.getName());
            }else{
                //找到class类并且保存
                classNames.add(packages+"."+file.getName());//包名+全类路径名 com.qianlong.controller
            }
        }

    }
    /**
     * 接收前台请求,对请求进行处理
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       try{
           // 得到的路径是 /HandToSpringMvc/test/first
           String uri=request.getRequestURI();
           //  /HandToSpringMvc
           String context=request.getContextPath();
           //  /test/first
           String path = uri.replaceAll(context, "");
           //得到方法
           Method method = (Method) urlMapping.get(path);
           TestController instance=(TestController) beans.get("/"+path.split("/")[1]);//   /test
           //进行参数处理
           Object[] args=hand(request,response,method);
           /**
            * invoke调用方法,参数(该方法属于哪个类,参数)
            * jdkAPI解释:
            * 如果底层方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null。 
			* 如果底层方法所需的形参数为 0,则所提供的 args 数组长度可以为 0 或 null。 
            */
           method.invoke(instance,args);
       }catch (Exception e){

       }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    doPost(request,response);
    }

    /**
     * 获取参数的方法
     */
    public static Object[] hand(HttpServletRequest request,HttpServletResponse response,Method method){
        //拿到当前待执行的方法有哪些参数
        Class<?>[] paramClazzes = method.getParameterTypes();
        //根据参数的个数,new一个参数的数组,将方法里的全部参数赋值到args来
        Object[] args=new Object[paramClazzes.length];
        int index=0;
        for(Class<?> paramClazz:paramClazzes){
        	//isAssignableFrom:判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,如果是则返回 true,否则返回 false
            if(ServletRequest.class.isAssignableFrom(paramClazz)){
                args[index]=request;
            }
            //同上
            if(ServletResponse.class.isAssignableFrom(paramClazz)){
                args[index]=response;
            }
            //判断有没有RequestParam注解,需要获取参数值
            Annotation[] paramAns = method.getParameterAnnotations()[index];
            if(paramAns.length>0){
                for(Annotation annotation:paramAns){
                    if(DnRequestParam.class.isAssignableFrom(paramAns.getClass())){
                        DnRequestParam rp=(DnRequestParam)annotation;
                        args[index]=request.getParameter(rp.value());
                    }
                }
            }
            index++;
        }
        return args;
    }
}

然后山寨版的springmvc框架就诞生了!

四,测试springmvc的性能

写一个TestService接口

public interface TestService {
		void say();
}

TestServiceImpl实现类

@DnService("haha")
public class TestServiceImpl implements TestService{
	@Override
	public void say() {
		System.out.println("手写springmvc框架实现");
	}
}

TestController

@DnController
@DnRequestMapping("/test")
public class TestController {
	
	@DnAutowired("haha")
    TestService testService;
	
    @DnRequestMapping("/first")
    public String query(HttpServletRequest request,HttpServletResponse response) throws IOException{
    	Person person=new Person();
       String s="我叫"+person.getName()+","+"住在"+person.getAddress();
       response.setHeader("content-type","text/html;charset=utf-8");
       response.getWriter().write(s);
       testService.say();
        return "";
    }
}

然后把项目添加到tomcat容器并运行
浏览器:
在这里插入图片描述
控制台:
在这里插入图片描述

五,springboot自定义日志注解

需求

加上自定义日志注解的方法执行时自动打印日志

步骤描述

1,随项目启动扫描所有被日志注解标注的方法

2,打印日志

注解类

@Retention(RetentionPolicy.RUNTIME)//选择运行环境
@Target({ElementType.TYPE, ElementType.METHOD})//限定作用域,放在类上面和方法上面
public @interface MyLog {
    String value() default "";
}

随项目启动执行的方法

@Component
public class MyLogger implements ApplicationRunner {

    //扫描到的类的容器
    private List<String> classNames=new ArrayList<String>();

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("#####################################");
        System.out.println("           初始化自定义日志注解           ");
        System.out.println("#####################################");

        scanPackage("com.ftx.myannotation");
        System.out.println(classNames);

        for(String className:classNames){
            //拿到类的全路径名,去掉后缀名
            String cn = className.replace(".class", "");
            Class<?> clazz = Class.forName(cn);
            Method[] methods = clazz.getDeclaredMethods();
            for(Method method:methods){
                if(method.isAnnotationPresent(MyLog.class)){
                    System.out.println(clazz.getName()+"类的"+method.getName()+"方法已被@MyLog注解标注");


                }
            }
        }

    }

    //扫描包
    public void scanPackage(String packageName){
        URL url = this.getClass().getClassLoader().getResource("" + packageName.replaceAll("\\.", "/"));
        System.out.println(url);
        //   file:/D:/IdeaProjects/springboot-myannotation/target/classes/com/ftx/myannotation
        String file = url.getFile();
        File files=new File(file);
        for(File file1:files.listFiles()){
            if(file1.isDirectory()){
                //递归扫描
                scanPackage(packageName+"."+file1.getName());
            }else{
                classNames.add(packageName+"."+file1.getName());
                //包名+类名 com.ftx.myannotation.controller.TestController
            }
        }
    }
}

测试方法

@RestController
public class TestContrller {

    @MyLog
    @RequestMapping("/test")
    public String test(){
        return "test";
    }
}

启动项目,后台打印

#####################################
           初始化自定义日志注解           
#####################################
com.ftx.myannotation.controller.TestContrller类的test方法已被@MyLog注解标注
posted @ 2020-03-08 17:20  你樊不樊  阅读(214)  评论(0编辑  收藏  举报