2 Star 4 Fork 1

yixiyun_tech / kuafu

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
README.md 23.04 KB
一键复制 编辑 原始数据 按行查看 历史
boolone 提交于 2021-09-19 22:18 . 更新一下说明

KuaFu MVC 开发框架

介绍

夸父 Java Web MVC开发框架,提供极简快速开发。

框架要求

  • JDK11 推荐AdoptOpenJDK
  • Mysql数据库 (目前Sql组件还没适配其他数据库,后面会继续完善)

安装教程

  1. 新建一个maven项目

  2. pom.xml中引入框架jar包

    <dependency>
     <groupId>tech.yixiyun.framework</groupId>
     <artifactId>kuafu-mvc</artifactId>
     <version>0.0.7</version>
     <type>jar</type>
    </dependency>
  3. pom.xml中还需要配置下build

    <build>
    		<resources>
    			<resource>
    				<directory>src/main/resources</directory>
    				<includes>
    					<include>**/*.*</include>
    				</includes>
    
    			</resource>
    		</resources>
    
    		<plugins>
    			<plugin>
    				<groupId>org.apache.maven.plugins</groupId>
    				<artifactId>maven-compiler-plugin</artifactId>
    				<version>3.8.1</version>
    				<configuration>
    					<source>你的jdk版本,不低于11</source>
    					<target>你的jdk版本,不低于11</target>
    					<encoding>UTF-8</encoding>
    					<compilerArgument>-parameters</compilerArgument>
    				</configuration>
    			</plugin>
    		
    		</plugins>
    	</build>
  4. 创建一个类,写一个main方法,启动项目

    public class Main {
        public static void main(String[] args) {
            TomcatStarter.start();
        }
    }

快速开始

  1. 创建一个Controller类

    import tech.yixiyun.framework.kuafu.controller.BaseController;
    import tech.yixiyun.framework.kuafu.view.View;
    
    /**
     * 演示Demo
     * @author Yixiyun
     * @version 1.0
     * @date 2021-05-16 15:00
     */
    public class DemoController extends BaseController {
    
    
        public View add(){
            return json("name", "zhangsan", "age", 14);
        }
    
    }
     重启下应用,访问 localhost:80/demo/add,就会看到浏览器显示一个json结构的数据了
  2. 按照正常的web项目结构,在webapp文件夹下,创建一个hello.jsp 文件

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
    	<title>Demo</title>
    </head>
    <body>
     Hello Kuafu
    </body>
    </html>
    
  3. 到DemoController中写一个新方法

     public View hello() {
          return jsp("hello");
      }
     重新下应用,访问 localhost:80/demo/hello,就会发现页面被打开了
  4. 框架会自动识别Controller组件,识别的依据就是类上或父类身上是否有@Controller注解。识别为Controller组件后,框架会继续分析它的方法,只要方法是public修饰的,并且返回类型是View的,框架就会自动生成路由映射

    路由对应的url生成规则是:

    1. 如果方法上有@Route注解,就以注解的值作为url
    2. 否则就以 “/基础路径 / 方法名” 作为url。基础路径由@Controller注解决定,BaseController类上有一个默认的@Controller("(a)(!Controller)") (这是一个特殊语法,更多说明请查看@Controller注解的注释),代表类名去掉"Controller"单词后,剩下字符串的第一个字符小写。所以DemoController因为继承了BaseController,它的基础路径就是 "demo"。它的 add方法映射的路由url 就是 "/demo/add",浏览器访问“/demo/add”就会由这个方法处理
  5. 在刚写的hello方法上加一个@Route("/")

    @Route("/")
    public View hello() {
        return jsp("hello");
    }
     现在重启下应用,访问 localhost:80/,就会发现,hello页面被打开了 (注意webapp目录下不要存在index.jsp、index.html等欢迎页)
  6. 在 DemoController 类上加一个 @Controller("abc")

    @Controller("abc")
    public class DemoController extends BaseController {
      ...
    }
     访问 localhost:80/abc/add,就会发现页面显示json数据了
  7. BaseController 提供了很多方便的的方法,例如

    • json(key,value, key,value...) 生成一个json结果的响应,json结果根据传入的键值对生成。
    • json(data) 由传入的一个对象生成json响应
    • jsonSuccess(data) 生成一个{state: SUCCESS, data: data}结构的json响应
    • jsonFail(msg) 生成一个 {state: ERROR, msg: msg}结构的json响应
    • jsp(path) 跳到一个jsp页面,只需要传入jsp在webapp文件夹中的相对路径,可以忽略 .jsp后缀
    • dispatch(path) 转向到一个地址
    • redirect(path) 重定向到一个地址
    • text(content) 返回一个普通文本响应
    • ftl(path) 跳到一个freemarker页面
    • setAttr(key,value) 向request中绑定数据
    • image(byte[]) 返回一个图片
    • download(filename, byte[]) 返回一个文件下载流
    • getRequest() 获取请求对象
    • getResponse() 获取响应对象
    • getSession() 获取session对象
  8. 写一个Service 业务类

    /**
     * 演示Service
     * @author Yixiyun
     * @version 1.0
     * @date 2021-05-15 13:22
     */
    public class DemoService extends BaseService {
    
    
        public void add() {
            LOGGER.info("执行了add方法");
        }
    
    }
  9. 任何类身上有@Service注解,或者父类身上有@Service注解,就会被识别为Service组件。

    注意这里我们使用了LOGGER 日志工具类,它基于log4j2实现,做了优化,无需每个类声明一个静态变量,都用它提供的静态方法记录即可。在框架的默认配置中,它只显示info以上级别的信息,并且输出记录到控制台以及日志文件中。后面会讲到如何修改配置

  10. 在DemoController中注入DemoService实例

    /**
     * 演示Demo
     * @author Yixiyun
     * @version 1.0
     * @date 2021-05-16 15:00
     
    @Controller("abc")
    */
    public class DemoController extends BaseController {
    
        
        private DemoService demoService;
        
        public View add() {
            //在这里调用一下demoService的方法
            demoService.add();
            return json("name", "zhangsan", "age", 14);
        }
    
        ...
    
    }
  11. 重新在浏览器访问下 demo/add,就会发现控制台打印了日志

  12. 只要是框架组件,就会自动识别为Bean,并自动注入,并默认以单例模式实例化。

    如果其他类想被纳入Bean管理,只需要类身上加上@Bean注解,注解可以控制实例化方式和是否懒加载。之后就可以通过BeanContext获取实例了。它也会被其他Bean类自动执行依赖注入。

  13. 接下来开始熟悉配置相关的,我们首先尝试修改一下tomcat的启动端口

    1. 在resources文件夹下新建一个名为 app-config.json 的文件

    2. 然后在文件中,按照 json 的写法写上

      {
        "system": {
          "server": {
            "port":8080
          }
        }
      }
    3. 重启应用,就会发现控制台提示

      ******** ^^^ 应用启动成功 ^^^ 启动地址:http://localhost:8080/ ********

  14. 对于一个项目来说,一般会有两个配置文件,一个是生产环境的,一个是开发环境的。刚才那个 app-config.json 正常来说对应的就是生产环境配置文件,当然你也可以两个环境下都用它。那么如果你想为开发环境另弄一个配置文件怎么办呢?只需要新建一个 app-config-dev.json 配置文件就行了,项目打包时,注意把它移除。一旦框架检测到有 app-config-dev.json,就会优先加载它,忽略 app-config.json. 接下来你我们尝试配置一个开发环境的配置文件,在这个配置里,我们干两个事,一个是tomcat端口改为9090,另一个是将日志的记录级别改为DEBUG,并且让日志不输出到文件,只在控制台打印。

    {
      "system": {
        "server": {
          "port":8080
        },
        "run": {
          "logger": {
            "configuration": { 
    					"loggers": {
    						"logger": [{
    							"name": "kuafuLogger",
    							"level": "debug",
    							"additivity": "false",
    							"AppenderRef": [
    
    								{"ref": "dev_console"}
    							]
    
    						}]
    					}
    				}
            
          }
        }
      }
    }
  15. 你可能会觉得这个日志的配置怎么这么麻烦,这个是log4j2 日志框架官方提供的 json 配置方式,框架本身提供的预配置 其实已经不需要怎么改了,如果就是有特别需要,请自行查阅 log4j2 官方文档进行自定义配置,但一定要注意 ** logger的name 一定不要改名字,除非你不想用框架提供的LOGGER工具类 **

  16. 回到配置文件上,你可能会有疑问,配置为什么要这么写,规则是什么?其实很简单,框架提供了一个预配置 default-config.json 文件,你只需要按照预配置的 json结构,在你的配置文件中去重写你想改变的配置项即可。框架会用 你的配置去覆盖 预配置。下面是default-config.json 的内容

    {
    	"system": {
    		//server相关配置
    		"server": {
    			"port": 80,
    			"contextPath": "/",
    			"connectionTimeout": "60000", //接收到请求后,等待处理的超时时间
    			"listen": "*", //如果服务器有多个网卡,而应用只需要监听某一个网卡的请求,就在这里配置上网卡的hostname或者ip。*代表服务器所有网卡
    			"compress": "off", //是否对文本进行压缩,三个值可用,on、off、force(强制)
    			"protocol": "HTTP/1.1", //暂时支持 HTTP/1.1 、AJP/1.3两种协议 HTTP/2目前未看到明显性能提升,且要求较多
    			"errorPages": { //错误页面,一般只对嵌入式服务器有效
    				"500": "/500.ftl", //发生500时显示的页面
    				"404": "/404.ftl" //发生404时显示的页面
    			}
    		},
    		//启动阶段
    		"boot": {
    
    			"tomcat": { //针对Tomcat内嵌服务器的启动阶段的一些配置
    				"scanJars": false,  //tomcat启动时是否扫描所有jar包,用于TLD和web-fragment扫描,这会增加启动时间
    				"uriEncoding": "UTF-8",
    				"minThread": 10, //tomcat处理请求的最少线程数
    				"maxThread": 200, //tomcat处理请求的并发线程数,超过的请求会被放入等待队列中
    				"maxWaitCount": 100 //最多可等待的请求数,超过后会拒绝请求
    
    			},
    			"preHandler": "" //前置处理器,在启动工作开始前执行的工作
    		},
    		//运行阶段
    		"run": {
    			//开发模式还是生产模式
    			"mode": "dev",
    			//是否启用热加载,需要配合jrebel使用
    			"hotdeploy": false,
    			//是否启用应用监控,用于实时查看服务器和应用的运行状态
    			"monitor": {
    				"enable": true
    			},
    			//请求相关
    			"request": {
    				//是否支持跨域
    				"cors": true,
    				//可以请求的资源路径
    				"resources": {
    					//只有匹配这些规则的请求,才会被框架处理,否则交由Servlet容器处理,语法同Filter的url-pattern语法
    					"include": ["/*"],
    					//在include的基础上,匹配这些规则的请求,将跳过拦截器处理,直接请求。语法同Filter的url-pattern语法
    					"exclude": ["*.ico"]
    				},
    				"session": {
    					"manager": "tech.yixiyun.framework.kuafu.controller.session.ServletSessionManager" //用于获取Session实例的类,需要实现ISessionManager接口
    				},
    				"upload": {
    					"singleMaxSize": 51200, //上传的单文件最大大小,单位K,默认50M
    					"totalMaxSize": 51200, //上传时单次请求最大大小,单位K,默认50M
    					"savePath": "/upload/{date:yyyyMMdd}/{uuid}.{suffix}", //上传文件的默认保存位置,以webroot位置为基准
    					"notAllowSuffix": [ //不允许上传的文件类型
    						//不允许上传的文件类型
    						".exe",
    						".bat",
    						".sh",
    						".dll",
    						".jsp",
    						".php",
    						".jar",
    						".class",
    						".js",
    						".asp",
    						".jspx",
    						".html",
    						".htm",
    						".shtml",
    						".ftl",
    						".py"
    					]
    				}
    			},
    			//模板相关
    			"template": {
    				"freemarker": { //freemarker模板,
    					"config": { //配置项参考文档:https://freemarker.apache.org/docs/api/freemarker/template/Configuration.html#setSetting-java.lang.String-java.lang.String-
    						"ContentType": "text/html; charset=UTF-8",
    						"TemplatePath": "[/,classpath:templates/]",
    						"locale": "zh_CN",
    						"template_exception_handler": "rethrow",
    						"date_format": "yyyy-MM-dd",
    						"time_format": "HH:mm",
    						"datetime_format": "yyyy-MM-dd HH:mm",
    						"template_update_delay": "2000",//单位毫秒,默认5000,开发环境设置低一点,生产环境建议设高
    						"default_encoding": "UTF-8",
    						"number_format": "0.###",
    						"incompatible_improvements": "2.3.30"
    
    					}
    				}
    			},
    			//响应相关
    			"response": {
    
    			},
    			//数据库相关
    			"db": {
    				//事务相关
    				"transaction": {
    					//自动开启事务的方法名前缀
    					"autoOpenPrefix": ["add","modify","del","update","do","save", "create", "alter", "insert"],
    					//默认的事务隔离级别,参考TransactionLevel枚举类中的定义
    					"defaultLevel": "REPEATABLE_READ",
    					//事务执行超时警告,一旦一个事务执行时间超过设定的毫秒值,就打印警告语句
    					"timeoutWarning": 1000
    				}
    			},
    			//日志相关
    			"logger": {
    				"default": "kuafuLogger", //默认使用的记录器
    				"configuration": { //配置参考log4j官网,链接http://logging.apache.org/log4j/2.x/manual/configuration.html#JSON
    					"status": "error",
    					"name": "kuafu",
    					"appenders": {
    						"appender": [
    							{
    								"type": "Console",
    								"name": "console",
    								"PatternLayout": {
    									"pattern": "%-d{MM-dd HH:mm:ss} [%p]-[%C{1}.%M()]: %m %n"
    								}
    							},
    							{ //开发环境用的打印,可以显示颜色
    								"type": "Console",
    								"name": "dev_console",
    								"PatternLayout": {
    									"pattern": "%-d{MM-dd HH:mm:ss} [%highlight{%-5level}{INFO=Magenta, TRACE=White, DEBUG=Blue}]-[%highlight{%C{1}.%M()}{INFO=Magenta, TRACE=White, DEBUG=Blue}]: %highlight{%m%n}{INFO=Magenta, TRACE=White, DEBUG=Blue}"
    								}
    							},
    							{
    								"type": "RollingFile",
    								"name": "file",
    								"fileName": "logs/run.log", //日志文件保存路径
    								"filePattern" : "logs/%d{MM-dd}_%i.log.gz", //分割后的命名规则
    								"PatternLayout": {
    									"pattern": "%-d{MM-dd HH:mm:ss} [%p]-[%C{1}.%M()]: %m %n"
    								},
    								"Policies": {
    									"CronTriggeringPolicy": {
    										"schedule": "0 0 0 * * ? *", //日志每天零点分割一次
    										"evaluateOnStartup": true
    									},
    									"SizeBasedTriggeringPolicy": { "size": "60M" } //单个日志文件超过60M也分割一次
    								},
    								"DefaultRolloverStrategy": {
    									"max": 10 //所有的日志文件最多保留10
    								}
    
    							}
    						]
    					},
    					"loggers": {
    						"logger": [{
    							"name": "kuafuLogger",
    							"level": "info",
    							"additivity": "false",
    							"AppenderRef": [ //默认会向这两个地方写入,上线后,可以不向console写入
    								{
    									"ref": "console"
    								},
    								{
    									"ref": "file"
    								}
    							]
    						}],
    						"root": {
    							"level": "info",
    							"AppenderRef": {
    								"ref": "console"
    							}
    						}
    					}
    				}
    			}
    		},
    		"stop": {
    			//系统停止运行阶段
    		}
    	}
    }
    
    
  17. 你应该会注意到,框架依赖的配置都写在了 system 配置项下,为了和你的项目相关的配置区分开,极力建议你,不要把你的 项目相关的配置写在 system 下,而应该另外建一个 键,例如 app 或者 project

    {
      "system": {
        //这里都是框架依赖的配置项
      },
      "app": {
        //这里写与你项目相关的配置,例如数据库配置、缓存配置等等
      }
    }
  18. 为了方便你从配置文件中读取配置,这里提供了一个AppConfig工具类,它可以根据 key (大部分配置项的key路径我们都在ConfigKey中定义了) 从 所有生效的配置文件中提取配置数据,并转成对应结构,例如

    List<String> prefixs = AppConfig.getAsStringList("system.run.db.autoOpenPrefix");
    Integer port = AppConfig.getAsInt("system.server.port");
    HashMap<String, String> errorMap = AppConfig.getAsStringMap(ConfigKey.SERVER_ERRORPAGES);
  19. 接下来我们创建一个实体类

    package tech.yixiyun.demo.user;
    
    import tech.yixiyun.framework.kuafu.domain.BaseDomain;
    
    import java.util.Date;
    
    /**
     * 用户实体类
     *
     * @author Yixiyun
     * @version 1.0
     * @date 2021-05-17 14:20
     */
    public class User extends BaseDomain {
        //唯一标识
        private Integer id;
        //姓名
        private String name;
        //爱好
        private String favors[];
        //性别,true代表男,false代表女
        private Boolean gender;
        //出生年月日
        private java.sql.Date birthday;
        //记录的创建时间
        private Date createTime;
        
        //getter setter 这里我就不写了,你一定要写上
    
    }
    
  20. 创建完,我们来看一下Controller方法如何自动解析和转换请求参数。回到 hello.jsp 中,我们构建一个表单。

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
    	<title>Demo</title>
    	<style>
    		form {
    			display: flex;
    			flex-direction: column;
    		}
    	</style>
    </head>
    <body>
    <form action="/demo/add" method="post">
    	<label >
    		姓名:
    		<input type="text" name="user.name" />
    	</label>
    	<label >
    		爱好:
    		<input type="checkbox" name="user.favors" value="游泳">游泳
    		<input type="checkbox" name="user.favors" value="健身">健身
    		<input type="checkbox" name="user.favors" value="写代码">写代码
    	</label>
    	<label >
    		性别:
    		<input type="radio" name="user.gender" value="1">
    		<input type="radio" name="user.gender" value="0">
    	</label>
    	<label >
    		出生年月:
    		<input type="date" name="user.birthday" />
    	</label>
    	<label >
    		创建时间:
    		<input type="datetime-local" name="user.createTime" />
    	</label>
    	<button type="submit">提交</button>
    </form>
    </body>
    </html>
    
  21. 可以看出,这个表单最终会提交到 demo/add 这个地址,所以回到 DemoController 的add方法,我们改动一下,把接收到的user对象返回给浏览器

    public class DemoController extends BaseController {
    
        private DemoService demoService;
    
        public View add(User user) {
            demoService.add();
            return json(user);
        }
    
        public View hello() {
            return jsp("hello");
        }
    
    }
  22. 现在可以尝试访问 /demo/hello 打开表单页,然后输入一些信息,点击提交看一下效果了,正常来说你应该能看到一个json结构的字符串,里面正是你提交的数据。如果失败了,请检查下pom.xml 中编译插件是否配置上了 -parameters

  23. 框架会自动解析请求参数,根据请求参数名和方法参数名进行匹对处理。同时,**框架会根据请求的Content-Type,自动判断该如何转化参数,所以即使使用 axios 等库,以application-json形式发起请求,无需任何注解,框架也可正常解析请求参数 **

  24. 接下来我们尝试把数据存储到数据库中,首先我们注册一个数据源,框架提供了阿里的Druid数据源支持,所以这里以注册Druid数据源为例:

    1. 在maven中添加druid依赖和mysql依赖

      <dependency> <!-- 数据库连接池 -->
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
          <version>${druid.version}</version>
      </dependency>
      <dependency><!-- mysql-connector-java -->
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>${mysql.version}</version>
      </dependency>
      		
    2. 在app-config-dev.json 配置文件中添加数据源配置

      {
          "app": {
              "datasource": { //数据源配置,注意值都是string类型,不能用其他类型,具体配置项参考 https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8
      			"name": "main", //数据源标识
      			"url": "jdbc:mysql://你的数据库连接?useSSL=false&autoReconnect=true&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai",
      			"username": "数据库用户名",
      			"password": "数据库密码",
      			"keepAlive": "true"
      		}
          }
      }
    3. 写一个IDataSourceProvider实现类

      public class DataSourceProvider implements IDataSourceProvider {
          @Override
          public DataSourceDefinition[] get() {
              DataSourceDefinition[] definitions = {new DruidDataSourceDefinition("app.datasource")};
              return definitions;
          }
      }
  25. 完成以上步骤,数据源就完成注册了。接着我们来根据Domain类,自动生成表结构,打开DemoController类,写一个方法

    /**
      * 根据Domain类生成或者更新表结构
      * @return
      */
    public View generateTable() {
        DbKit.createOrAlterAllSingleTable();
        return jsonSuccess();
    }

    然后我们重启系统,通过浏览器访问 /demo/generateTable,返回{state:"SUCCESS"} 就代表执行成功,打开数据库我们应该就可以看到创建成功的表了。

  26. 接着我们尝试向数据库添加一条数据,回到DemoService 中的add方法,我们将它改动一下:

    public void add(User user) {
        LOGGER.info("执行了add方法");
        insertOne(user);
    }

    然后改一下DemoController的add方法

    public View add(User user) {
        demoService.add(user);
        return json(user);
    }

    然后我们访问一下 /demo/hello,在页面填写一些数据,点击提交试试效果吧

Java
1
https://gitee.com/yixiyun-tech/kuafu.git
git@gitee.com:yixiyun-tech/kuafu.git
yixiyun-tech
kuafu
kuafu
master

搜索帮助