手写springmvc实现其中几个注解,方便明白springmv原理

首先,这篇文章借鉴了网上项目为com.liugh.liughMVC的一个项目,不过其中的@MyRequestParam的作用却未实现,我这边加了一下,并对项目中加了很多解释

我的项目地址 https://gitee.com/xdsz/myMvc.git

1 新建一个普通的maven项目,pom文件里引入servlet依赖,

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

2 新建文件夹webapp,并创建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/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
   version="3.0">
   <servlet>
      <servlet-name>mymvc</servlet-name>
      <servlet-class>com.sj.myservlet.SjServlet</servlet-class>
      <init-param>
         <!--在init方法中会用到-->
         <param-name>contextConfig</param-name>
         <!--对应resources文件夹下配置文件的名字-->
         <param-value>application.properties</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
   </servlet>
   <servlet-mapping>
      <servlet-name>mymvc</servlet-name>
      <!--拦截所有-->
      <url-pattern>/*</url-pattern>
   </servlet-mapping>
</web-app>

3 在resources文件下创建application.properties,文件名称对应web.xml中配置的名称,内容如下

#项目启动时扫描的文件目录
scanPackage=com.sj.mycontroller

4 新建文件夹 

annotation:放注解的
mycontroller::放自己的控制类
myservlet:对注解的支持是在这个文件下的Sjservlet中

5 在annotation文件下创建注解接口

package com.sj.annotation;

import java.lang.annotation.*;

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

-------------------------------------------------------------------------------------------------------------------------

package com.sj.annotation;

import java.lang.annotation.*;

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

------------------------------------------------------------------------------------------------------------------------

package com.sj.annotation;

import java.lang.annotation.*;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SjRequesetParam {
    String value();//表示参数的别名,必须得填
}

----------------------------------------------------------------------------------------------------------------------------

package com.sj.annotation;

import java.lang.annotation.*;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
//这个是自己加着玩的,加在参数上的,可以给对应的参数拼接指定的字符串
public @interface SjAddStr {
    String value();//拼接添加字符
    int type() default 1;//类型 1是加在前面  其他是加在后面
}

6 在myservlet文件下创建Sjservlet,继承HttpServlet,重写其中的方法,类和方法级别的注解是在初始化的时候执行的,参数的注解处理是在dopost中处理的

package com.sj.myservlet;


import com.sj.annotation.SjAddStr;
import com.sj.annotation.SjController;
import com.sj.annotation.SjRequesetParam;
import com.sj.annotation.SjRequestMapping;

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.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.*;


    public class SjServlet extends HttpServlet {


    private Properties properties = new Properties();//配置文件中配置的信息

    private List<String> classNames = new ArrayList<>();//所有扫描到的类的名字

    private Map<String, Object> controllers = new HashMap<>();//类名 , 对象

    private Map<String, Method> handlerMapping = new HashMap<>();// key是url value是对应的方法

    private Map<String, Object> controllerMap  =new HashMap<>();// key是url  value是对应的对象



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

        //1.加载配置文件
        String configPath = config.getInitParameter("contextConfig");//得到配置文件的路径,对应web.xml中配置的名称
        doLoadConfig(configPath);

        //2.初始化所有相关联的类,扫描用户设定的包下面所有的类
        String packagePath =properties.getProperty("scanPackage");//得到配置文件配置的要扫描的包路径,对应配置文件的配置信息
        doScanner(packagePath);

        //3.拿到扫描到的类,通过反射机制,实例化,并且放到controllers中(k-v  beanName-bean) beanName默认是首字母小写
        doInstance();

        //4.初始化HandlerMapping(将url和method对应上)
        initHandlerMapping();


    }



    @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) {
            resp.getWriter().write("500!! Server Exception");
        }

    }

    //对参数注解的支持  逻辑是在这里实现的
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        if(handlerMapping.isEmpty()){
            return;
        }

        String url =req.getRequestURI();//得到访问路径
        String contextPath = req.getContextPath();

        //拼接url并把多个/替换成 初始化时往handlerMapping和controllerMap中放入的key
        url=url.replace(contextPath, "").replaceAll("/+", "/");

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

        Method method =this.handlerMapping.get(url);//得到url对应的方法


        //获取方法的参数列表

        Parameter[] paraNames=method.getParameters();


        //获取请求的参数
        Map<String, String[]> parameterMap = req.getParameterMap();

        //保存参数值
        Object [] paramValues= new Object[paraNames.length];


        //方法的参数列表
        for (int i = 0; i<paraNames.length; i++){
            Parameter parameter=paraNames[i];
            //根据参数名称,做某些处理
             String paratype = parameter.getType().getName();//参数类型
            String paraname = parameter.getName();//参数名称

            if (paratype.contains("HttpServletRequest")){
                //参数类型已明确,这边强转类型
                paramValues[i]=req;
                continue;
            }
            if (paratype.contains("HttpServletResponse")){
                paramValues[i]=resp;
                continue;
            }

            if(parameter.isAnnotationPresent(SjRequesetParam.class)){//如果改参数使用了SjRequesetParam注解
                SjRequesetParam annotation =  parameter.getAnnotation(SjRequesetParam.class);
                paraname=annotation.value();//替换为SjRequesetParam注解中设置的参数名称
            }
            String paramvalue="";
            if(paratype.equals("java.lang.String")){
                 paramvalue=Arrays.toString(parameterMap.get(paraname)).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");;

            }


            if(parameter.isAnnotationPresent(SjAddStr.class) && !"".equals(paramvalue)){
                SjAddStr annotation =  parameter.getAnnotation(SjAddStr.class);
                String addstr=annotation.value();
                int type=annotation.type();

                if(type==1){//判断拼接的类型,是加在前面还是后面的
                    paramvalue=paramvalue+addstr;
                }else{
                    paramvalue=addstr+paramvalue;
                }

            }

            paramValues[i]=paramvalue;
        }

        try {//从controllerMap得到url对应的实例对象,利用反射机制来调用
            method.invoke(this.controllerMap.get(url), paramValues);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


     //加载配置文件
    private void  doLoadConfig(String location){
        //把web.xml中的contextConfig对应value值的文件加载到留里面
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(location);
        try {
            //用Properties文件加载文件里的内容
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //关流
            if(null!=resourceAsStream){
                try {
                    resourceAsStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    //初始化所有相关联的类,扫描用户设定的包下面所有的类,使用了递归的写法读取所有的文件,渠道完整的类名方便使用反射
    private void doScanner(String packageName) {
        //把所有的.替换成/
        URL url  =this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.", "/"));
        File dir = new File(url.getFile());
        for (File file : dir.listFiles()) {
            if(file.isDirectory()){
                //递归读取包
                doScanner(packageName+"."+file.getName());
            }else{
                String className =packageName +"." +file.getName().replace(".class", "");
                classNames.add(className);
            }
        }
    }


//拿到扫描到的类,通过反射机制,实例化,并且放到controllers中(k-v  beanName-bean) beanName默认是首字母小写
    private void doInstance() {
        if (classNames.isEmpty()) {
            return;
        }
        for (String className : classNames) {
            try {
                //利用反射实例化类
                Class<?> clazz =Class.forName(className);
                if(clazz.isAnnotationPresent(SjController.class)){
                    String controllerName = clazz.getSimpleName();
                    Object obj= clazz.newInstance();
                    controllers.put(toLowerFirstWord(controllerName),obj);
                }else{
                    continue;
                }


            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }
        }
    }

        //初始化HandlerMapping(将url和method对应上)
    private void initHandlerMapping(){
        if(controllers.isEmpty()){
            return;
        }
        try {
            for (Map.Entry<String, Object> entry: controllers.entrySet()) {
                Class<? extends Object> clazz = entry.getValue().getClass();
                if(clazz.isAnnotationPresent(SjController.class)){

                    //拼url时,是controller头的url拼上方法上的url
                    String baseUrl ="";
                    if(clazz.isAnnotationPresent(SjRequestMapping.class)){
                        SjRequestMapping annotation = clazz.getAnnotation(SjRequestMapping.class);
                        baseUrl=annotation.value();
                    }
                    Method[] methods = clazz.getMethods();
                    for (Method method : methods) {
                        if(!method.isAnnotationPresent(SjRequestMapping.class)){
                            continue;
                        }
                        SjRequestMapping annotation = method.getAnnotation(SjRequestMapping.class);
                        String url = annotation.value();

                        url =(baseUrl+"/"+url).replaceAll("/+", "/");
                        //这里应该放置实例和method
                        handlerMapping.put(url,method);
                        controllerMap.put(url,clazz.newInstance());
                        System.out.println(url+","+method);
                    }

                }

            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    /**
     * 把字符串的首字母小写
     * @param name
     * @return
     */
    private String toLowerFirstWord(String name){
        char[] charArray = name.toCharArray();
        charArray[0] += 32;
        return String.valueOf(charArray);
    }
}

7 试一下效果,创建TestController,在浏览器用get方式访问一下

http://localhost:8080/test?passram=a啊

package com.sj.mycontroller;

import com.sj.annotation.SjAddStr;
import com.sj.annotation.SjController;
import com.sj.annotation.SjRequesetParam;
import com.sj.annotation.SjRequestMapping;

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

@SjController
public class TestController {

    @SjRequestMapping(value="test" )
    public void sjtest(HttpServletRequest req, HttpServletResponse resp,@SjRequesetParam("passram")@SjAddStr(value = "str", type=2) String param){
       try {
           System.out.println(param);
           String result="处理过的参数为:"+param;
           OutputStream outputStream=resp.getOutputStream();//获取OutputStream输出流
           resp.setHeader("content-type", "text/html;charset=UTF-8");//通过设置响应头控制浏览器以UTF-8的编码显示
           byte[] dataByteArr=result.getBytes("UTF-8");//将字符转换成字节数组,指定以UTF-8编码进行转换
           outputStream.write(dataByteArr);//使用OutputStream流向客户端输出字节数组

       }catch (Exception e){

       }
    }
}

注意了,在获取参数名称时,需要编译的class文件中保留参数名称,我这边用的是idea,在配置文件中加上了-parameters如图,或者也可以在pom文件中引入依赖实现

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值