实现简单的注解型MVC框架 —— 低配SpringMVC

文章目录

    • 目标
    • 最终效果展示
    • 基本步骤
      • 1. 解析控制器类:
      • 2. 解析处理函数:
      • 3. 解析处理函数变量名:
      • 4. 监听TCP连接:
      • 5. 实现路由函数:
    • 知识点总结

目标

  • 与SpringMvc定义Controller类似效果

最终效果展示

主类

package org.example;import com.zcj.Accepter;
import com.zcj.annotation.SummerApplication;import java.io.IOException;//标注控制器类
@SummerApplication("org.example.MainController")
public class App
{public static void main( String[] args ) throws IOException {Accepter accepter = new Accepter();accepter.run(App.class);}
}

控制器类

package org.example;import com.zcj.annotation.Controller;
import com.zcj.annotation.Param;
import com.zcj.entity.Response;public class MainController {//标注处理的路径@Controller("/hello")public Response hello(@Param("name") String name){return new Response(200, Response.HTML, "<h1>Hello,"+ name + "</h1>");}
}

浏览器访问:
在这里插入图片描述

基本步骤

1. 解析控制器类:

利用反射解析@SummerApplication注解所包含的类,获得该类中的处理函数

2. 解析处理函数:

获取控制器类中包含@Controller的方法,建立处理函数映射表

3. 解析处理函数变量名:

获取处理函数中包含@Param的参数,建立处理函数变量名表用于获取请求中的参数

/*** 解析控制器类中的处理函数* @param clazz 控制器类的Class* @throws ClassNotFoundException* @throws IllegalAccessException* @throws InstantiationException*/private void analysisController(Class clazz) throws ClassNotFoundException, IllegalAccessException, InstantiationException {System.out.println("解析控制器类.....");if (!clazz.isAnnotationPresent(SummerApplication.class)) {System.out.println("ERROR: 未指定控制器类");return;}SummerApplication summerApplication = (SummerApplication) clazz.getAnnotation(SummerApplication.class);String classPack = summerApplication.value();// Class.forName 可以得到Class对象,并且如果这个类没有被加载过它将会初始化这个类。Class controllerClazz = Class.forName(classPack);controllers = (T) controllerClazz.newInstance();System.out.println("控制器类:" + controllerClazz);System.out.println("\n*************************************************************************************************************************" +"\n*  路径\t\t|参数\t\t\t\t| 处理函数");//反射解析控制器类Method[] methods = controllerClazz.getMethods();for (Method method : methods) {if (!method.isAnnotationPresent(Controller.class)) continue;Controller annotation = method.getAnnotation(Controller.class);//将函数加入映射表controllerMap.put(annotation.value(), method);Parameter[] parameterList = method.getParameters();List<String> paramNameList = null;if (parameterList.length > 0) {//获取参数变量名paramNameList = new ArrayList<>();for (Parameter param : parameterList) {//判断是否出现 @Paramif (!param.isAnnotationPresent(Param.class)) continue;Param paramAnnotation = param.getAnnotation(Param.class);paramNameList.add(paramAnnotation.value());}//处理函数变量名映射表paramNameMap.put(annotation.value(), paramNameList);}System.out.println("*  " + annotation.value() + " | " + paramNameList + " | " + method);}System.out.println("*************************************************************************************************************************\n");System.out.println("解析控制器类完成");}

4. 监听TCP连接:

获取请求的方法(POST,GET),请求的路径,请求参数等

/*** 启动连接监听* @param clazz 启动类*/public void run(Class clazz) {//解析控制器类try {analysisController(clazz);} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {System.out.println("ERROR: 控制器类不存在, 请检查 @SummerApplication 注解的类名");return;}//启动监听连接System.out.println("==============================================\n\n等待远程连接,端口号为:" + serverSocket.getLocalPort() + "\n\n==============================================\n");while (true) {try {Socket client = serverSocket.accept();synchronized (System.out) {System.out.println("==============================================\n连接的远程主机地址:" + client.getRemoteSocketAddress() + "\n==============================================\n");}executorService.submit(() -> {//处理请求handlerRequest(client);});} catch (IOException ioException) {System.out.println(ioException);//关闭线程池executorService.shutdown();}}}

请求解析函数

/*** 此函数用于解析请求并分配请求到相应的处理函数* @param client 连接请求的Socket*/private void handlerRequest(Socket client) {try (InputStream input = client.getInputStream()) {try (OutputStream output = client.getOutputStream()) {try (PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8)))) {try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {//请求头String requestHeader;int contentLength = 0;//请求的类型String requestType = null;//请求的路径String requestPath = null;//请求参数String requestParams = null;//参数类型String contentType = null;while ((requestHeader = reader.readLine()) != null && !requestHeader.isEmpty()) {synchronized (System.out) {System.out.println(requestHeader);}//获得请求的类型,路径if (requestHeader.startsWith("POST") || requestHeader.startsWith("GET")) {String[] line = requestHeader.split(" ");requestType = line[0];int end = line[1].indexOf("?");if (end == -1) requestPath = line[1];else requestPath = line[1].substring(0, end);}//获得参数类型if (requestHeader.startsWith("Content-Type")) {String[] line = requestHeader.split(": ");contentType = line[1];}//获得Get参数if (requestHeader.startsWith("GET")) {int begin = requestHeader.indexOf("?") + 1;if (begin == 0) {requestParams = "";} else {int end = requestHeader.indexOf("HTTP/") - 1;//GET的参数requestParams = requestHeader.substring(begin, end);}}//获取POST请求内容长度if (requestHeader.startsWith("Content-Length")) {int begin = requestHeader.indexOf("Content-Lengh:") + "Content-Length:".length() + 1;String postParamterLength = requestHeader.substring(begin).trim();contentLength = Integer.valueOf(postParamterLength);}}//获取POST请求参数字符串StringBuffer sb = new StringBuffer();if (contentLength > 0) {for (int i = 0; i < contentLength; i++) {sb.append((char) reader.read());}requestParams = sb.toString();}//将字符串转UTF-8requestParams = URLDecoder.decode(requestParams, "UTF-8");System.out.println(requestParams);synchronized (System.out) {System.out.println("请求类型:" + requestType);System.out.println("请求路径:" + requestPath);System.out.println("请求参数类型:" + contentType);System.out.println("请求参数:" + requestParams);}//发送回复Response resp = null;if (requestType.equals("POST") && !contentType.equals("application/x-www-form-urlencoded")) {//只接受 application/x-www-form-urlencoded 的参数resp = new Response(400, Response.HTML, "<h1>400<h4>POST请求的参数格式必须为 application/x-www-form-urlencoded</h4></h1>");} else {//将请求分配到相应的处理函数resp = dispatch(requestPath, requestParams);}writer.write(resp.toString());writer.flush();client.close();synchronized (System.out) {System.out.println("\n=============================================\nWARING:" + client.getRemoteSocketAddress() + " is disconnected.\n=============================================\n");}}}} catch (InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {client.close();System.out.println("ERROR: 分配请求失败");e.printStackTrace();}} catch (IOException e) {try {client.close();} catch (IOException ignored) {}synchronized (System.out) {System.out.println("\n=============================================\nWARING:" + client.getRemoteSocketAddress() + " is disconnected.\n=============================================\n");}}}

5. 实现路由函数:

利用处理函数变量名表获取参数,并将参数转为处理函数所需类型,利用处理函数映射表将请求转发到对应的处理函数,返回结果

/*** @param requestPath   请求的路径* @param requestParams 请求的参数字符串* @return Response http回复类* @throws NoSuchMethodException* @throws IllegalAccessException* @throws InvocationTargetException* @throws InstantiationException*/private Response dispatch(String requestPath, String requestParams) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Response resp = null;if (!controllerMap.containsKey(requestPath)) {//处理函数不存在return new Response(404, Response.HTML, "<h1>404<h4>请求的资源不存在</h4></h1>");}//调用处理函数Method method = controllerMap.get(requestPath);//获取方法的参数类型Class<?>[] paramterTypes = method.getParameterTypes();if (paramterTypes.length == 0) {//不需要参数//invoke()第一个参数为调用方法的实例return (Response) controllerMap.get(requestPath).invoke(controllers);}//检验参数//解析参数字符传LinkedHashMap<String, String> paramMap = Params.toMap(requestParams);if (paramMap == null) {//没有所需参数return new Response(400, Response.HTML, "<h1>参数错误,请检查</h1>");}//获得方法的参数变量名表List<String> paramNameList = paramNameMap.get(requestPath);//方法的参数Object[] params = new Object[paramterTypes.length];for (int i = 0; paramMap != null && i < paramterTypes.length; i++) {//参数的类型Class paramType = paramterTypes[i];if (allowType.contains(paramType) && !paramMap.containsKey(paramNameList.get(i))) {//参数缺漏return new Response(400, Response.HTML, "<h1 style=\"color:red;\">参数错误, 缺少:" + paramNameList.get(i) + "[ " + paramterTypes[i].getName() + " ]</h1>");}//将String 转为 所需的类型,只支持基本类型或自定义类(不包括集合)String param = paramMap.get(paramNameList.get(i));//将基本类型转为包装类型if (paramType.equals(int.class)) {paramType = Integer.class;} else if (paramType.equals(boolean.class)) {paramType = Boolean.class;}if (paramType.equals(String.class)) {params[i] = param;} else if (paramType.equals(Integer.class) || paramType.equals(Boolean.class)) {//将参数转为所需类型Object instance = paramType.getConstructor(String.class).newInstance(param);params[i] = instance;} else {//自定义类Object o = Params.toObject(requestParams, paramType);params[i] = o;}}//调用控制器return (Response) controllerMap.get(requestPath).invoke(controllers, params);}

知识点总结

  • 将字符串转UTF-8的方法
String utf8Str = URLDecoder.decode(str, "UTF-8");
  • 向反射获取的函数中传入任意数量且不同类型的参数的方法

    前提知识:JAVA中的引用类型都继承于Object,通过向上转型,可以用Object类型引用各种引用类型

    第一步:将基本类型转为包装类型
    第二步:将所有参数放入Object数组中
    第三步:调用相应的函数

//...转换步骤省略,可参照 基本步骤的5. dispatch函数
//len为参数的数量
Object[] parmas = new Object[len]
//....放入参数到 params 数组
method.invoke(instance, params)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/330230.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

转:聊聊开发中幂等性问题(*)

【README】 这是一篇非常棒的&#xff0c; 讲解幂等性问题的post&#xff0c; 感谢原文作者&#xff1b; 转自&#xff1a; https://juejin.cn/post/6844903815552958477 幂等 (idempotence) 的概念 幂等的数学概念 幂等是源于一种数学概念。其主要有两个定义 如果在一元运…

面象对象设计6大原则之六:迪米特原则

转载自 面象对象设计6大原则之六&#xff1a;迪米特原则迪米特原则&#xff08;LOD&#xff09;&#xff0c;The Law Of Demeter&#xff0c;也称为最少知识原则定义一个对象应该对其他对象有最少的了解。也就是说一个类耦合和调用一个类应该知道的最少&#xff0c;它只关心被耦…

转-Apache kafka 工作原理介绍

转自&#xff1a; https://developer.ibm.com/zh/articles/os-cn-kafka/ 消息队列 消息队列技术是分布式应用间交换信息的一种技术。消息队列可驻留在内存或磁盘上, 队列存储消息直到它们被应用程序读走。通过消息队列&#xff0c;应用程序可独立地执行–它们不需要知道彼此的…

Java内存区域(运行时数据区域)和内存模型(JMM)

原文作者&#xff1a;czwbig 原文&#xff1a;https://www.cnblogs.com/czwbig/p/11127124.html Java 内存区域和内存模型是不一样的东西&#xff0c;内存区域是指 Jvm 运行时将数据分区域存储&#xff0c;强调对内存空间的划分。 而内存模型&#xff08;Java Memory Model&am…

kafka 学习 非常详细的经典教程

转自&#xff1a; https://blog.csdn.net/tangdong3415/article/details/53432166 一、基本概念 介绍 Kafka是一个分布式的、可分区的、可复制的消息系统。它提供了普通消息系统的功能&#xff0c;但具有自己独特的设计。 这个独特的设计是什么样的呢&#xff1f; 首先让我们看…

一个多线程死锁案例,如何避免及解决死锁问题

转载自 一个多线程死锁案例&#xff0c;如何避免及解决死锁问题 多线程死锁在java程序员笔试的时候时有遇见&#xff0c;死锁概念在之前的文章有介绍&#xff0c;大家应该也都明白它的概念&#xff0c;不清楚的去翻看历史文章吧。 下面是一个多线程死锁的例子 输出 thread1 get…

Thread打印值的含义

打印当前线程&#xff1a; log.warn("当前线程&#xff1a;"Thread.currentThread()");输出结果&#xff1a; 输出结果各部分的含义&#xff1a; Thread[ 线程名称, 线程优先级, 线程所属线程组 ]

WEB攻击手段及防御第1篇-XSS

转载自 WEB攻击手段及防御第1篇&#xff0d;XSS 概念 XSS全称为Cross Site Script&#xff0c;即跨站点脚本攻击&#xff0c;XSS攻击是最为普遍且中招率最多的web攻击方式&#xff0c;一般攻击者通过在网页恶意植入攻击脚本来篡改网页&#xff0c;在用户浏览网页时就能执行恶意…

转:权限管理——用户认证和用户授权

转自&#xff1a; https://blog.csdn.net/xdd19910505/article/details/51926540 因为做了权限的项目经理&#xff0c;so&#xff0c;恶补一下一个权限框架&#xff1a;shiro。其实作为框架首要目标是易于使用和理解。安全有时候是很复杂的&#xff0c;甚至是痛苦的&#xff0…

Spring websocket 使用@Autowired 出现null

问题 在spring websocket 中使用Autowired 出现空指针异常 原因 spring管理的都是单例&#xff08;singleton&#xff09;&#xff0c;和 websocket &#xff08;多对象&#xff09;相冲突。websocket在客户端每建立一个链接就会创建一个新的对象&#xff0c;这个对象没有任何…

WEB攻击手段及防御第2篇-SQL注入

转载自 WEB攻击手段及防御第2篇&#xff0d;SQL注入 概念 SQL注入即通过WEB表单域插入非法SQL命令&#xff0c;当服务器端构造SQL时采用拼接形式&#xff0c;非法SQL与正常SQL一并构造并在数据库中执行。 简单的SQL注入的例子&#xff1a; 例1&#xff1a;test123456 or 11; …

WEB攻击手段及防御第3篇-CSRF

转载自 WEB攻击手段及防御第3篇&#xff0d;CSRF 概念 CSRF全称即Cross Site Request forgery&#xff0c;跨站点请求伪造&#xff0c;攻击者通过跨站点进行伪造用户的请求进行合法的非法操作&#xff0c;其攻击手法是通过窃取用户cookie或服务器session获取用户身份&#xff0…

Mybatis报错:nested exception is org.apache.ibatis.binding.BindingException: Parameter ‘XXX‘ not found

问题 使用Mybatis过程中报错 nested exception is org.apache.ibatis.binding.BindingException: Parameter XXX not found原因 mapper.xml映射没有得到传入的参数&#xff0c;当 SQL语句中只有一个参数时&#xff0c;mybatis可以正确传参&#xff0c;而如果由多个参数&…

java作为kafka生产者实验及Expiring超时问题解决

【README】 java作为生产者&#xff0c;centos 作为消费者&#xff1b; 【1】生产者代码 -- pom.xml <!-- 依赖 --> <dependencies><dependency><groupId>org.apache.kafka</groupId><artifactId>kafka-clients</artifactId><…

WEB攻击手段及防御-扩展篇

转载自 WEB攻击手段及防御&#xff0d;扩展篇 之前的文章介绍了常见的XSS攻击、SQL注入、CSRF攻击等攻击方式和防御手段&#xff0c;没有看的去翻看之前的文章&#xff0c;这些都是针对代码或系统本身发生的攻击&#xff0c;另外还有一些攻击方式发生在网络层或者潜在的攻击漏洞…

java客户端作为kafka生产者测试

【README】 1、本文主要对 java客户端作为kafka 生产者进行测试&#xff0c; 消费者由 centos的kafka命令行线程扮演&#xff1b; 2、消息发送&#xff1a; kafka的生产者采用异步发送消息的方式&#xff0c;在消息发送过程中&#xff0c;涉及到2个线程——main线程和sender…

Redis学习之缓存穿透、缓存击穿和缓存雪崩详解

目录缓存穿透解决方案缓存空对象布隆过滤器缓存击穿解决方案对访问数据库的操作加锁提前缓存热点数据&#xff0c;设置热点数据永不过期缓存雪崩解决方案Redis高可用限流降级数据预热设置合理的过期时间参考缓存穿透 指的是对某个一定不存在的数据进行请求&#xff0c;该请求将…

Redis高可用:主从复制及哨兵模式

目录主从复制作用复制原理使用的方式哨兵模式主从切换过程Redis Sentinel的配置文件参考主从复制 主从复制&#xff0c;是指将一台Redis服务器的数据&#xff0c;复制到其他的Redis服务器。前者称为主节点(master)&#xff0c;后者称为从节点(slave)&#xff1b;数据的复制是单…

java客户端作为kafka消费者测试

【README】 本文主要对 java客户端作为kafka 消费者进行测试&#xff0c; 生产者由 kafka客户端扮演&#xff1b; 【1】普通消费者 设置消费者组&#xff1b; 重置消费者的offset&#xff0c; 即每次都从最头开始消费&#xff08;默认仅保持7天内数据&#xff09; &#xf…

spring bean初始化及销毁你必须要掌握的回调方法。

转载自 spring bean初始化及销毁你必须要掌握的回调方法。 spring bean在初始化和销毁的时候我们可以触发一些自定义的回调操作。 初始化的时候实现的方法 1、通过java提供的PostConstruct注解&#xff1b; 2、通过实现spring提供的InitializingBean接口&#xff0c;并重写其a…