首页 > 科技 > 手写SpringMVC

手写SpringMVC

环境描述

idea

java 8

1. POM文件

  4.0.0  org.feng  hand-springmvc  1.0-SNAPSHOT  war  hand-springmvc Maven Webapp    Example Domain      UTF-8    1.8    1.8                  junit      junit      4.11      test                  org.slf4j      slf4j-log4j12      1.7.25              log4j      log4j      1.2.17                  javax.servlet      javax.servlet-api      3.0.1            hand-springmvc          

2. log4j.properties

log4j.rootLogger=INFO, consolelog4j.appender.console=org.apache.log4j.ConsoleAppenderlog4j.appender.console.layout=org.apache.log4j.PatternLayoutlog4j.appender.console.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p %-60c %x - %m%n

3. 项目目录

核心内容是注解+servlet

注解类

org.feng.annotation.Autowired

package org.feng.annotation;import java.lang.annotation.*;/** * Created by Feng on 2019/12/16 17:58 * CurrentProject's name is hand-springmvc * Autowired 用在变量上 * @author Feng */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Autowired {    String value() default "";}

org.feng.annotation.Controller

package org.feng.annotation;import java.lang.annotation.*;/** * Created by Feng on 2019/12/16 18:01 * CurrentProject's name is hand-springmvc * Controller 用在类上 * @author Feng */@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Controller {    String value() default "";}

org.feng.annotation.RequestMapping

package org.feng.annotation;import java.lang.annotation.*;/** * Created by Feng on 2019/12/16 18:02 * CurrentProject's name is hand-springmvc * RequestMapping可以用在类、方法上 * @author Feng */@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface RequestMapping {    String value() default "";}

org.feng.annotation.RequestParam

package org.feng.annotation;import java.lang.annotation.*;/** * Created by Feng on 2019/12/16 18:06 * CurrentProject's name is hand-springmvc * RequestParam 用在参数上 * @author Feng */@Target(ElementType.PARAMETER)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface RequestParam {    String value() default "";}

org.feng.annotation.Service

package org.feng.annotation;import java.lang.annotation.*;/** * Created by Feng on 2019/12/16 18:05 * CurrentProject's name is hand-springmvc * Service 用在类上 * @author Feng */@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Service {    String value() default "";}

核心控制器

package org.feng.servlet;import org.feng.annotation.*;import org.slf4j.Logger;import org.slf4j.LoggerFactory;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.InvocationTargetException;import java.lang.reflect.Method;import java.net.URL;import java.util.ArrayList;import java.util.List;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;/** * Created by Feng on 2019/12/16 17:58 * CurrentProject's name is hand-springmvc
* 核心控制器: *
    实现思路 *
  • 先扫描基础包,获取 {@code class} 文件的路径;其实是为了获取完整类名
  • *
  • 根据上边的完整类名以及判断是否有指定创建实例(通过有无注解和注解的类型)并保存实例到 {@code map} 中
  • *
  • 依赖注入变量,从实例获得类对象,然后解析 {@code Field} 并赋值给标注了{@link Autowired}的{@code Field}
  • *
  • 获取方法上的参数,通过{@link HttpServletRequest}获取
  • *
* @author Feng */public class DispatcherServlet extends HttpServlet { /** * 扫描包:基本的包,扫描该路径下的所有类 */ private static final String BASE_PACKAGE = "org.feng"; private static final String WAR_NAME = "/hand_springmvc"; /**日志*/ private static final Logger LOGGER = LoggerFactory.getLogger(DispatcherServlet.class); /** * 保存class文件的路径 */ private List classPathList = new ArrayList<>(); /** * IOC容器:存放对象;使用{@link ConcurrentHashMap}保证线程安全 */ private Map beans = new ConcurrentHashMap<>(16); /** * 存放方法映射:使用{@link ConcurrentHashMap}保证线程安全
* 用于存储方法
* {@code key = classpath + methodPath; value = method} */ private Map handlerMap = new ConcurrentHashMap<>(16); /** * 初始化数据: *
    *
  • 扫描所有的类
  • *
  • 创建实例并存储进 {@code beans}
  • *
  • 依赖注入:使用 {@code Autowired}
  • *
  • 拼接请求地址初始化、方法映射
  • *
*/ @Override public void init() { LOGGER.info("starting scan package into classpath list"); scanPackage(BASE_PACKAGE); LOGGER.info("scan package end"); LOGGER.info("classPathList:"+classPathList); LOGGER.info("starting create instance into bean map"); createInstance(); LOGGER.info("create instance end"); LOGGER.info("beans:" + beans); LOGGER.info("starting autowired field"); autowiredField(); LOGGER.info("autowired field end"); LOGGER.info("starting mapping to url"); urlMapping(); LOGGER.info("mapping to url end"); LOGGER.info("handler mapping:" + handlerMap); } /** * 方法映射 */ private void urlMapping() { beans.forEach((key, value) -> { Class clazz = value.getClass(); if(clazz.isAnnotationPresent(Controller.class)){ RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class); String classPath = requestMapping.value(); Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { if(method.isAnnotationPresent(RequestMapping.class)){ RequestMapping requestMapping1 = method.getAnnotation(RequestMapping.class); String methodPath = requestMapping1.value(); handlerMap.put(classPath + methodPath, method); } } } }); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) { this.doPost(req, resp); } /** * 解析调用方法后的返回值:当为String类型时,得到其是转发还是重定向; * @param invokeReturn invoke方法时得到的返回值 * @param req 请求对象 * @param resp 响应对象 */ private void forwardOrRedirect(Object invokeReturn, HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { final String forward = "forward:"; final String redirect = "redirect:"; // 当反向调用方法有返回值 if(invokeReturn != null){ // 返回值是字符串:解析字符串 if(invokeReturn.getClass() == String.class){ req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); String returnStr = invokeReturn.toString(); if(returnStr.startsWith(forward)){ returnStr = returnStr.substring(8); LOGGER.info(forward + returnStr); req.getRequestDispatcher(WAR_NAME + returnStr).forward(req, resp); } else if(returnStr.startsWith(redirect)){ returnStr = returnStr.substring(9); LOGGER.info(redirect + returnStr); resp.sendRedirect(WAR_NAME + returnStr); } else { LOGGER.info(redirect + returnStr); resp.sendRedirect(returnStr); } } } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) { // 请求的地址 String uri = req.getRequestURI(); uri = uri.replace(WAR_NAME, ""); int index = uri.indexOf("/", 1); String controllerUrl = uri.substring(0, index); Method method = handlerMap.get(uri); LOGGER.info("get method " + method); beans.forEach((key, value) -> { Class clazz = value.getClass(); if(clazz.isAnnotationPresent(Controller.class)){ RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class); String valueTemp = requestMapping.value(); if(controllerUrl.equals(valueTemp)){ try { LOGGER.info("invoking " + controllerUrl + "." + value); Object invokeReturn = method.invoke(value, getArgs(req, resp, method)); // 控制:转发或重定向 forwardOrRedirect(invokeReturn, req, resp); } catch (IllegalAccessException | InvocationTargetException e) { LOGGER.error("invoke error in " + controllerUrl); } catch (ServletException | IOException e) { e.printStackTrace(); } } } }); } /** * 解析标注有{@link RequestParam}的方法参数,并赋值 * @param req 请求对象 * @param resp 响应对象 * @param method 方法对象 * @return 赋值后的参数 */ private Object[] getArgs(HttpServletRequest req, HttpServletResponse resp, Method method) { // 拿到当前类待执行的方法参数 Class[] clazzParams = method.getParameterTypes(); // 定义存储参数的数组 Object[] args = new Object[clazzParams.length]; int argsIndex = 0; // 判定此 class 对象所表示的类或接口与指定的 class 参数所表示的类或接口是否相同 // 或是否是其超类或超接口 for (int index = 0; index < clazzParams.length; index++) { if(ServletRequest.class.isAssignableFrom(clazzParams[index])){ args[argsIndex ++] = req; } if(ServletResponse.class.isAssignableFrom(clazzParams[index])){ args[argsIndex ++] = resp; } Annotation[] annotations = method.getParameterAnnotations()[index]; if(annotations.length > 0){ for (Annotation annotation : annotations) { if(RequestParam.class.isAssignableFrom(annotation.getClass())){ RequestParam requestParam = (RequestParam) annotation; // 找到注解的名字 args[argsIndex ++] = req.getParameter(requestParam.value()); } } } } return args; } /** * 依赖注入:对带有{@link org.feng.annotation.Autowired}的属性赋值 */ private void autowiredField() { beans.forEach((key, value) ->{ Class clazz = value.getClass(); if(clazz.isAnnotationPresent(Controller.class)){ // 获取属性 Field[] declaredFields = clazz.getDeclaredFields(); for (Field declaredField : declaredFields) { if(!declaredField.isAnnotationPresent(Autowired.class)){ continue; } // 当存在 Autowired 标注的属性时 Autowired autowired = declaredField.getAnnotation(Autowired.class); String beanName; if("".equals(autowired.value())){ beanName = lowerFirstChar(declaredField.getType().getSimpleName()); } else { beanName = autowired.value(); } // 设置访问控制权限:原先是 private 不能访问 declaredField.setAccessible(true); // 自定义接口实现类:以Impl结尾,前边拼接接口名(首字母小写) if(beanName.endsWith("Impl")){ beanName = beanName.replace("Impl", ""); } if(beans.get(beanName) != null){ try { // 给声明的变量赋值:注入实例 declaredField.set(value, beans.get(beanName)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } }); } /** * 创建实例:遍历所有的{@code class}文件,创建需要创建的实例存储到beans中
* 判断:是否被指定注解标注了 *
    *
  • 首先判断是不是{@link org.feng.annotation.Service}注解的类,若是则判断有没有传入的{@code service}名称
  • *
  • 其他情况,包括是{@link org.feng.annotation.Controller}的情况,全部使用小写类名为{@code key}
  • *
*/ private void createInstance() { try { // 遍历所有的.class文件;将需要实例化的类创建实例 for (String classPath : classPathList) { Class clazz = Class.forName(classPath.replace(".class", "")); if(clazz.isAnnotationPresent(Service.class)){ Service service = clazz.getAnnotation(Service.class); String key = service.value(); // 当传入了注解中参数时 if(!"".equals(key)){ beans.put(key, clazz.newInstance()); LOGGER.info("created instance by " + classPath); } else { // 获取第一个接口的简单名称,首字母小写 beans.put(lowerFirstChar(clazz.getInterfaces()[0].getSimpleName()), clazz.newInstance()); LOGGER.info("created instance by " + classPath); } } else if(clazz.isAnnotationPresent(Controller.class)){ // 以类名小写首字母为key beans.put(lowerFirstChar(clazz.getSimpleName()), clazz.newInstance()); LOGGER.info("created instance by " + classPath); } } } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { LOGGER.error("error in createInstance", e); } } /** * 将类名中的首字母小写 * @param simpleName 类名(不含包名) */ private String lowerFirstChar(String simpleName) { char[] chars = simpleName.toCharArray(); chars[0] += 32; return new String(chars); } /** * 先通过传入的包名拼接出文件路径: *

* {@code "org.feng".replace(".", "/");} *

* 递归扫描指定路径下的所有{@code class}文件; * 存储{@code class}文件路径到集合中 * @param basePackage 扫描包的包名 */ private void scanPackage(String basePackage) { // 将包名转换为class文件路径 String resourceName = "/" + basePackage.replace(".", "/"); URL url = this.getClass().getClassLoader().getResource(resourceName); // 获取文件 assert url != null; String filename = url.getFile(); File file = new File(filename); // 获取所有文件 String[] files = file.list(); assert files != null; for (String path : files) { File fileTemp = new File(filename + path); // 当前如果是目录,递归扫描包 String packageName = basePackage + "." + path; if(fileTemp.isDirectory()){ scanPackage(packageName); } else { // 当扫描到文件(.class文件),增加到类路径集合 classPathList.add(packageName); LOGGER.info("scan " + packageName + " into classpath list"); } } }}


测试

org.feng.service.MyService

package org.feng.service;/** * Created by Feng on 2019/12/17 9:23 * CurrentProject's name is hand-springmvc * @author Feng */public interface MyService {    /**     * 说     * @return 字符串     */    String say();

org.feng.service.impl.MyServiceImpl

package org.feng.service.impl;import org.feng.annotation.Service;import org.feng.service.MyService;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * Created by Feng on 2019/12/17 9:25 * CurrentProject's name is hand-springmvc * @author Feng */@Servicepublic class MyServiceImpl implements MyService {    private static final Logger LOGGER = LoggerFactory.getLogger(MyServiceImpl.class);    public MyServiceImpl(){        LOGGER.info("no args constructor MyServiceImpl.class");    }    @Override    public String say() {        return "MyServiceImpl invoking say()";    }}

org.feng.controller.MyController

package org.feng.controller;import org.feng.annotation.Autowired;import org.feng.annotation.Controller;import org.feng.annotation.RequestMapping;import org.feng.annotation.RequestParam;import org.feng.service.MyService;/** * Created by Feng on 2019/12/17 9:27 * CurrentProject's name is hand-springmvc */@RequestMapping("/MyController")@Controllerpublic class MyController {    @Autowired    private MyService myService;    @RequestMapping("/say.do")    public String say(@RequestParam("name") String name, @RequestParam("info") String info){        System.out.println("name = " + name + ", info = " + info);        myService.say();        return "redirect:/index.jsp";    }}

运行

配置tomcat

运行结果:


————————————————

本文来自投稿,不代表本人立场,如若转载,请注明出处:http://www.souzhinan.com/kj/283827.html