手写SpringMVC
2019-12-19 16:08:19来源:博客园 阅读 ()
手写SpringMVC
环境描述
idea
java 8
1. POM文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.feng</groupId>
<artifactId>hand-springmvc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>hand-springmvc Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>Example Domain</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!--测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
</dependency>
</dependencies>
<build>
<finalName>hand-springmvc</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
</pluginManagement>
</build>
</project>
2. log4j.properties
log4j.rootLogger=INFO, console log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.layout=org.apache.log4j.PatternLayout log4j.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) @Documented public @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) @Documented public @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) @Documented public @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) @Documented public @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) @Documented public @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<br> * 核心控制器: * <ul>实现思路 * <li>先扫描基础包,获取 {@code class} 文件的路径;其实是为了获取完整类名</li> * <li>根据上边的完整类名以及判断是否有指定创建实例(通过有无注解和注解的类型)并保存实例到 {@code map} 中</li> * <li>依赖注入变量,从实例获得类对象,然后解析 {@code Field} 并赋值给标注了{@link Autowired}的{@code Field}</li> * <li>获取方法上的参数,通过{@link HttpServletRequest}获取</li> * </ul> * @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<String> classPathList = new ArrayList<>(); /** * IOC容器:存放对象;使用{@link ConcurrentHashMap}保证线程安全 */ private Map<String, Object> beans = new ConcurrentHashMap<>(16); /** * 存放方法映射:使用{@link ConcurrentHashMap}保证线程安全<br> * 用于存储方法<br> * {@code key = classpath + methodPath; value = method} */ private Map<String, Method> handlerMap = new ConcurrentHashMap<>(16); /** * 初始化数据: * <ul> * <li>扫描所有的类</li> * <li>创建实例并存储进 {@code beans}</li> * <li>依赖注入:使用 {@code Autowired}</li> * <li>拼接请求地址初始化、方法映射</li> * </ul> */ @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中<br> * 判断:是否被指定注解标注了 * <ul> * <li>首先判断是不是{@link org.feng.annotation.Service}注解的类,若是则判断有没有传入的{@code service}名称</li> * <li>其他情况,包括是{@link org.feng.annotation.Controller}的情况,全部使用小写类名为{@code key}</li> * </ul> */ 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); } /** * 先通过传入的包名拼接出文件路径: * <p> * {@code "org.feng".replace(".", "/");} * </p> * 递归扫描指定路径下的所有{@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 */ @Service public 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") @Controller public 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
运行结果:
————————————————
本人免费整理了Java高级资料,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G,需要自己领取。
传送门:https://mp.weixin.qq.com/s/osB-BOl6W-ZLTSttTkqMPQ
原文链接:https://www.cnblogs.com/yunxi520/p/12070373.html
如有疑问请与原作者联系
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
上一篇:java对象的实例化过程
下一篇:最简单的 Java内存模型 讲解
- springboot2配置JavaMelody与springMVC配置JavaMelody 2020-06-11
- 深入理解Mybatis(第一讲)——手写ORM框架(简易版Mybatis 2020-06-01
- 蚂蚁金服这套SpringMvc面试题你懂多少(面试必刷) 2020-05-27
- SpringMVC高级-拦截器如何正确运用?案例详解 2020-05-21
- 萌新学习SpringMVC 2020-05-20
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash