手动实现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,则抛出异常以及异常原因

项目结构目录如下:

二、自定义注解

  • @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.
     * <p>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路径以及配置文件路径指定

<?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">
    <servlet>
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>com.servlet.DispatcherServlet</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>SpringMVC</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

application.properties配置扫描路径:

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

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

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

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

具体实现:

详细步骤都在代码中有详细说明,继承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<String, Method> handlerMapping = new HashMap<>();
    //扫描路径下的类名列表
    private List<String> classNames = new ArrayList<>();
    //IOC容器对象,保存注入的实例对象
    private Map<String, Object> ioc = new HashMap<>();
    //保存url和注入的bean
    private Map<String, Object> 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<String, Object> 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<String, Object> entry: ioc.entrySet()) {
                //获取ioc中注入的bean
                Class<? extends Object> 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<String, String[]> 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("<br//>");
            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 "胡歌";
    }
}

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

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

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

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

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值