手动实现springmvc框架

一、实现思路

要实现的功能:

初始化时,将注解的类实例化到容器中进行管理,访问时可以根据url找到指定的方法进行调用,对于Autowired注解的属性,要将容器中管理的bean注入进来,实现springmvc的简易功能。

具体思路:

  1. 加载spring的配置文件
  2. 初始化用户设定的包下面的类(加了注解的)
  3. 把扫描到的类,通过反射机制实例化放到IOC容器中,beanName默认是首字母小写
  4. 属性注入,将@Autowired注解的参数注入实例
  5. 初始化HandlerMapping(将url和method对应上),通过url找到对应的处理器

实现中要注意的细节:

  • 类实例化时,统一bean的默认名字为类名首字母小写
  • 加载到spring容器过程中,bean名字重复时要抛出异常以及异常原因
  • Autowired注入时,如果容器中不存在该实例,按照Autowired注解的值是否忽略
  • 保证类加载到容器中时,只实例化一次
  • 建立方法与url的映射时,HandlerMapping中如果已存在该url,则抛出异常以及异常原因

项目结构目录如下:

手动实现springmvc框架_第1张图片

二、自定义注解

  • @Autowired注解:注入bean实例 
  • @Controller注解:标记bean为controller层 
  • @RequestMapping注解:映射请求地址
  • @RequestParam注解:参数绑定 
  • @Service注解:标记bean为Service层

这里我们先实现这些注解的功能,其余的可自行补充实现

@Autowired  :   required参数的作用为自动注入时bean不存在是否忽略,默认true,不忽略

package com.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.Documented;
import java.lang.annotation.RetentionPolicy;

/**
 * @Autowired注解:注入bean实例
 */
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {

    /**
     * Declares whether the annotated dependency is required.
     * 

Defaults to {@code true}. */ boolean required() default true; }

@Controller注解:标记bean为controller层 

package com.annotation;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.Documented;

/**
 * @Controller注解:标记bean为controller层
 */
@Target({ElementType.TYPE}) //注解可以在类上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
    String value() default "";
}

@RequestMapping注解:映射请求地址

package com.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @RequestMapping注解:映射请求地址
 */
@Target({ElementType.TYPE,ElementType.METHOD})  //注解可以在类或者方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
    String value();
}

@RequestParam注解:参数绑定 

package com.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @RequestParam注解:参数绑定
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
    String value();
}

@Service注解:标记bean为Service层

package com.annotation;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.Documented;

/**
 * @Service注解:标记bean为Service层
 */
@Target({ElementType.TYPE}) //注解可以在类上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
    String value() default "";
}

三、具体实现

在实现前端控制器之前,我们要配置一下web.xml,将需要加载的DispatcherServlet路径以及配置文件路径指定



    
        SpringMVC
        com.servlet.DispatcherServlet
        
            contextConfigLocation
            application.properties
        
        1
    
    
        SpringMVC
        /*
    

application.properties配置扫描路径:

scanPackage=com.web
csdnUrl=手写springmvc框架启动成功,更多技术文档请访问:https://blog.csdn.net/dwhdome

还需要一个servlet的pom依赖,我们引入一下:

    
        UTF-8
        1.8
    

    
        
            javax.servlet
            javax.servlet-api
            3.0.1
            provided
        
    

具体实现:

详细步骤都在代码中有详细说明,继承HttpServlet类,重写父类的init,get,post方法。在init中写我们的实现逻辑

package com.servlet;

import com.annotation.*;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import javax.servlet.ServletConfig;
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;

public class DispatcherServlet extends HttpServlet{
    //spring配置文件里的对象
    private Properties properties = new Properties();
    // handlerMapping对象,存储url和方法的映射关系
    private Map handlerMapping = new HashMap<>();
    //扫描路径下的类名列表
    private List classNames = new ArrayList<>();
    //IOC容器对象,保存注入的实例对象
    private Map ioc = new HashMap<>();
    //保存url和注入的bean
    private Map controllerMap  =new HashMap<>();

    //重写父类doGet方法
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);
    }

    //重写父类doPost方法
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            //处理请求
            doDispatch(this, req,resp);
        } catch (Exception e) {
            resp.getWriter().write("500!! Server Exception");
        }
    }


    //重写父类init方法
    @Override
    public void init(ServletConfig config) throws ServletException {
        //1、加载spring的配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        //2、初始化所有相关联的类,扫描用户设定的包下面所有的类
        doScanner(properties.getProperty("scanPackage"));

        //3、拿到扫描到的类,通过反射机制,实例化,并且放到ioc容器中beanName默认是首字母小写
        doInstance();

        //4.属性注入,将@Autowired注解的参数注入实例
        doAutowired();

        //5.初始化HandlerMapping(将url和method对应上),通过url找到对应的处理器
        initHandlerMapping();

        System.out.println(properties.getProperty("csdnUrl"));
    }

    private void doAutowired(){
        if (ioc.isEmpty()) {
            return;
        }
        //遍历所有被托管的对象
        for (Map.Entry entry : ioc.entrySet()) {
            //查找所有被Autowired注解的属性
            // getFields()获得某个类的所有的公共(public)的字段,包括父类;
            // getDeclaredFields()获得某个类的所有申明的字段,即包括public、private和proteced,但是不包括父类的申明字段。
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                //没加autowired的不需要注值
                if (!field.isAnnotationPresent(Autowired.class)) {
                    continue;
                }
                //默认bean名字为类名首字母小写
                String beanName  = toLowerFirstWord(field.getType().getSimpleName());
                //获取Autowired注解的值
                Autowired autowired = field.getAnnotation(Autowired.class);

                //将私有化的属性设为true,不然访问不到,用于下方属性注入
                field.setAccessible(true);

                //获取beanName的实例化对象
                Object beanClazz = ioc.get(beanName);
                try {
                    if (autowired.required()) {
                        //当required为true时,bean必须存在,否则报错
                        if(null == beanClazz){
                            throw new NullPointerException("无法找到:"+beanName+"的实例");
                        }
                        field.set(entry.getValue(), ioc.get(beanName));
                    } else {
                        //当required为false时,bean如果不存在,则跳过
                        if(null != beanClazz){
                            field.set(entry.getValue(), ioc.get(beanName));
                        }
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
        System.out.println("4:属性注入完成...");
    }

    private void initHandlerMapping() {
        if(ioc.isEmpty()){
            return;
        }
        try {
            for (Entry entry: ioc.entrySet()) {
                //获取ioc中注入的bean
                Class clazz = entry.getValue().getClass();
                if(!clazz.isAnnotationPresent(Controller.class)){
                    continue;
                }

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

                    url =(baseUrl+"/"+url).replaceAll("/+", "/");
                    if(handlerMapping.containsKey(url)){
                        throw new InstantiationException("当前请求路径映射‘"+url+"'已存在!");
                    }
                    handlerMapping.put(url,method);
                    controllerMap.put(url,entry.getValue());
                }
            }
            System.out.println("5: HandlerMapping初始化完成...");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void doInstance() {
        if (classNames.isEmpty()) {
            return;
        }
        for (String className : classNames) {
            try {
                //反射来实例化(加@Controller注解以及@Service需要实例化)
                Class clazz =Class.forName(className);
                if(clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(Service.class)){
                    String clazzName = toLowerFirstWord(clazz.getSimpleName());
                    if(ioc.containsKey(clazzName)){
                        throw new InstantiationException("当前bean‘"+clazzName+"'已存在!");
                    }
                    ioc.put(clazzName,clazz.newInstance());
                }else{
                    continue;
                }
            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }
        }
        System.out.println("3:类实例化完成...");
    }

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

    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);
                System.out.println("2:扫描到类"+className);
            }
        }
    }

    private void doLoadConfig(String contextConfigLocation) {
        //把web.xml中的contextConfigLocation对应value值的文件加载到流里面
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        InputStreamReader inputStreamReader = null;
        try {
            //字符流读取配置文件中文数据,防止乱码
            inputStreamReader = new InputStreamReader(resourceAsStream,"UTF-8");
            //用Properties文件加载文件里的内容
            properties.load(inputStreamReader);
            System.out.println("1:配置文件加载完成...");
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //关流
            if(null!=resourceAsStream){
                try {
                    resourceAsStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(null!=inputStreamReader){
                try {
                    inputStreamReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static void doDispatch(DispatcherServlet dispatcherServlet, HttpServletRequest req, HttpServletResponse resp) throws Exception{
        if(dispatcherServlet.handlerMapping.isEmpty()){
            return;
        }

        String url =req.getRequestURI(); //获取请求的uRL
        String contextPath = req.getContextPath();
        //去除URL中多余的"/"
        url=url.replace(contextPath, "").replaceAll("/+", "/");

        //如果访问的URL不存在,则直接返回404错误
        if(!dispatcherServlet.handlerMapping.containsKey(url)){
            resp.getWriter().write("404 NOT FOUND!");
            return;
        }

        Method method = dispatcherServlet.handlerMapping.get(url);
        //获取方法的参数列表
        Parameter methodParameters[] = method.getParameters();
        //获取请求的参数
        Map parameterMap = req.getParameterMap();

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

        for (int i = 0; i < methodParameters.length; i++) {
            if (ServletRequest.class.isAssignableFrom(methodParameters[i].getType())) { //绑定request
                paramValues[i] = req;
            } else if (ServletResponse.class.isAssignableFrom(methodParameters[i].getType())) {//绑定response
                paramValues[i] = resp;
            } else {// 请求参数,可类型自动转换,支持String,Integer,Float,Double
                // 参数绑定的名称,默认为方法形参名
                String bindingValue = methodParameters[i].getName();
                if (methodParameters[i].isAnnotationPresent(RequestParam.class)) {
                    //获取RequestParam注解绑定的参数名
                    bindingValue = methodParameters[i].getAnnotation(RequestParam.class).value();
                }
                // 从请求中获取参数的值
                String paramValue = req.getParameter(bindingValue);
                paramValues[i] = paramValue;
                //参数处理,根据参数类型自动转换
                if (paramValue != null) {
                    if (Integer.class.isAssignableFrom(methodParameters[i].getType())) {
                        paramValues[i] = Integer.parseInt(paramValue);
                    } else if (Float.class.isAssignableFrom(methodParameters[i].getType())) {
                        paramValues[i] = Float.parseFloat(paramValue);
                    } else if (Double.class.isAssignableFrom(methodParameters[i].getType())) {
                        paramValues[i] = Double.parseDouble(paramValue);
                    }
                }
            }
        }
        //利用反射机制来调用
        try {
            method.invoke(dispatcherServlet.controllerMap.get(url), paramValues);//第一个参数是method所对应的实例,在controllerMap中;第二个参数为方法参数
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

测试controller以及service

package com.web.controller;

import com.annotation.Autowired;
import com.annotation.Controller;
import com.annotation.RequestMapping;
import com.annotation.RequestParam;
import com.web.service.TestService;

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

@Controller
@RequestMapping("/Test")
public class TestController {

    @Autowired
    private TestService testService;

    //处理bean名字相同问题
    @RequestMapping("/Test")
    public void test1(HttpServletRequest request, HttpServletResponse response, @RequestParam("name")String name){
        try {
            response.setContentType("text/html; charset=utf-8");
            //response.setCharacterEncoding("UTF-8");
            response.getWriter().write( "controller method success! param:"+name);
            response.getWriter().write("
"); response.getWriter().write("serevice method success! param:"+this.testService.getName()); } catch (IOException e) { e.printStackTrace(); } } }
package com.web.service;

import com.annotation.Service;

@Service
public class TestService {

    public String getName(){
        return "胡歌";
    }
}

全部配置完成后启动项目。观察控制台:

手动实现springmvc框架_第2张图片

请求/Test/Test?name=丁文浩,

手动实现springmvc框架_第3张图片

得出结论,参数绑定正常,自动注入正常。、

代码链接:https://pan.baidu.com/s/1GsSa0k1kAccNJvyjJuAokA 提取码:1vae 
 

 

你可能感兴趣的:(spring,springmvc,Java)