# oxygen **Repository Path**: justlive1/oxygen ## Basic Information - **Project Name**: oxygen - **Description**: 一个轻量级Java框架,包含ioc、aop、config、cache、job、Jdbc、web等 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 135 - **Forks**: 38 - **Created**: 2018-09-13 - **Last Updated**: 2025-07-18 ## Categories & Tags **Categories**: utils, webframework **Tags**: None ## README # oxygen [![Maven Central](https://maven-badges.herokuapp.com/maven-central/vip.justlive/oxygen/badge.svg)](https://maven-badges.herokuapp.com/maven-central/vip.justlive/oxygen/) [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) 轻量级Java框架 ## 介绍 一个轻量级Java框架 - oxygen-core - 配置管理,支持${attrs.key:defaultValue}表达式获取配置 - 加解密管理,提供加解密服务内置基础加密实现,例如SHA-1、SHA-256、MD5 - 异常管理,提供异常包装,统一异常编码,便于国际化 - i18n国际化 - 资源文件加载,提供file,jar,classpath等文件加载 - 类扫描器 - 基于构造器的轻量级依赖注入 - 缓存 - 提供基于注解`Scheduled`的定时任务 - 可使用注解`Aspect`或直接实现`Interceptor`编写切面 - 部分工具类 - oxygen-jdbc - 小巧简单的jdbc实现,纯jdk实现,无第三方jar - 支持多数据源 - 基于sql进行crud,不提供类似Hibernate的链式方法(原因:sql作为数据库领域的DSL,已经很自然优雅,Less is more) - oxygen-web - 轻量级web框架支持注解声明和函数式编程 - 支持Servlet3.0 `ServletContainerInitializer` 自动加载,省略web.xml - 支持i18n动态切换 - 提供`WebHook`进行请求拦截处理 - 支持自定义全局异常处理 - 内置aio服务器 ## 特性 * 轻量级,使用简单 * 支持插件扩展 * 函数式编程 * 流式风格 ## 快速开始 创建`Maven`项目 ```xml -tomcat vip.justlive oxygen-web${oxygen.server} ${oxygen.version} ``` 或`Gradle` ``` compile 'vip.justlive:oxygen-web-tomcat:$oxygenVersion' ``` > 不需要`webapp`项目框架,支持Servlet3.0 编写 `main` 函数写一个 `Hello World` ```java public static void main(String[] args) { Router.router().path("/").handler(ctx -> ctx.response().write("hello world")); Server.listen(8080); } ``` 用浏览器打开 http://localhost:8080 这样就可以看到 `hello world` 了! ## 内容详解 - [**`注册路由`**](#注册路由) - [**`硬编码方式`**](#硬编码方式) - [**`注解方式`**](#注解方式) - [**`获取请求参数`**](#获取请求参数) - [**`表单参数或json请求参数`**](#表单参数或json请求参数) - [**`restful参数`**](#restful参数) - [**`header参数`**](#header参数) - [**`cookie参数`**](#cookie参数) - [**`参数转对象`**](#参数转对象) - [**`静态资源`**](#静态资源) - [**`上传文件`**](#上传文件) - [**`结果渲染`**](#结果渲染) - [**`渲染json`**](#渲染json) - [**`渲染文本`**](#渲染文本) - [**`渲染html`**](#渲染html) - [**`渲染模板`**](#渲染模板) - [**`重定向`**](#重定向) - [**`写入cookie`**](#写入cookie) - [**`添加header`**](#添加header) - [**`写入session`**](#写入session) - [**`拦截器`**](#拦截器) - [**`异常处理`**](#异常处理) - [**`部署项目`**](#部署项目) - [**`修改端口`**](#修改端口) - [**`运行项目`**](#运行项目) ### 注册路由 #### 硬编码方式 ```java Router.router().path("/").handler(ctx -> ctx.response().write("hello world")); Router.router().path("/get").method(HttpMethod.GET).handler(get); Router.router().path("/post").method(HttpMethod.POST).handler(post); ``` #### 注解方式 ```java @Router("/book") public class BookRouter { // 视图 @Mapping("/") public ViewResult index() { return Result.view("/book.html"); } // json @Mapping(value = "/ajax", method = {HttpMethod.POST}) public Book find(RoutingContext ctx) { // ... return new Book(); } } ``` ### 获取请求参数 #### 表单参数或json请求参数 项目将json请求参数与表单参数合并,使用相同的方法或注解获取 **使用RoutingContext获取** ```java Router.router().path("/").handler(ctx -> { String id = ctx.request().getParam("id"); ctx.response().write(id); }); ``` **使用注解获取** ```java @Mapping(value = "/ajax", method = {HttpMethod.POST}) public Book find(@Param Long id, @Param("tp") String type) { // ... return new Book(); } ``` #### restful参数 **使用RoutingContext获取** ```java Router.router().path("/{id}").handler(ctx -> { String id = ctx.request().getPathVariable("id"); ctx.response().write(id); }); ``` **使用注解获取** ```java @Mapping(value = "/{id}", method = {HttpMethod.POST}) public void ajax(@PathParam("id") Long id) { // ... } ``` #### header参数 **使用RoutingContext获取** ```java Router.router().path("/").handler(ctx -> { String id = ctx.request().getHeader("id"); ctx.response().write(id); }); ``` **使用注解获取** ```java @Mapping(value = "/", method = {HttpMethod.POST}) public void ajax(@HeaderParam("id") Long id) { // ... } ``` #### cookie参数 **使用RoutingContext获取** ```java Router.router().path("/").handler(ctx -> { String id = ctx.request().getCookie("id"); ctx.response().write(id); }); ``` **使用注解获取** ```java @Mapping(value = "/", method = {HttpMethod.POST}) public void ajax(@CookieParam("id") Long id) { // ... } ``` #### 参数转对象 **实体类** ```java @Data public class Book { private String name; private String author; } ``` **使用RoutingContext转换** ```java Router.router().path("/").handler(ctx -> { // 表单或json请求参数绑定 Book book = ctx.bindParam(Book.class); // cookie参数绑定 book = ctx.bindCookie(Book.class); // header参数绑定 book = ctx.bindHeader(Book.class); // restful参数绑定 book = ctx.bindPathVariables(Book.class); }); ``` **使用注解获取** ```java @Mapping(value = "/", method = {HttpMethod.POST}) public void ajax(@Param Book b1, @CookieParam Book b2, @HeaderParam Book b3, @PathParam Book b4) { // ... } ``` ### 静态资源 内置默认将`classpath`下`/public,/static`作为静态资源目录,支持`webjars`,映射到`/public` 自定义静态资源可使用下面代码 ```java Router.staticRoute().prefix("/lib").location("classpath:lib"); ``` 也可以通过配置文件指定 ```properties web.static.prefix=/public web.static.path=/public,/static,classpath:/META-INF/resources/webjars ``` ### 上传文件 **使用RoutingContext获取** ```java Router.router().path("/").handler(ctx -> { MultipartItem file = ctx.request().getMultipartItem("file"); // ... }); ``` **使用注解获取** ```java @Mapping("/") public void upload(MultipartItem image, @MultipartParam("file1") MultipartItem file) { // 不使用注解则使用方法参数名作为请求参数名称 // 使用注解指定请求参数名称 } ``` ### 结果渲染 #### 渲染json ```java // 使用RoutingContext返回 Router.router().path("/").handler(ctx -> { ctx.response().json(new Book("Java", "xxx")); }); // 注解式 @Mapping("/") public Book find() { // 直接返回对象,框架默认处理成json return new Book("Java", "xxx"); } ``` #### 渲染文本 ```java // 使用RoutingContext返回 Router.router().path("/").handler(ctx -> { ctx.response().text("hello world"); }); ``` #### 渲染html ```java // 使用RoutingContext返回 Router.router().path("/").handler(ctx -> { ctx.response().html("hello world"); }); ``` #### 渲染模板 内置支持了`jsp`和`thymeleaf`模板,默认对应`resources`下的`WEB-INF`和`templates`目录 ```properties # 可通过下面配置进行更改模板目录 web.view.jsp.prefix=WEB-INF web.view.thymeleaf.prefix=/templates ``` 模板使用 ```java // 使用RoutingContext Router.router().path("/").handler(ctx -> { ctx.response().template("index.html"); }); Router.router().path("/").handler(ctx -> { Map attrs = new HashMap<>(); // ... ctx.response().template("index.html", attrs); }); // 注解式 @Mapping("/") public Result index() { return Result.view("index.html"); } @Mapping("/") public Result index() { Map attrs = new HashMap<>(); // ... return Result.view("index.html").addAttributes(attrs); } ``` #### 重定向 ```java Router.router().path("/").handler(ctx -> { ctx.response().redirect("https://github.com/justlive1"); }); @Mapping("/a") public Result index() { // 内部地址 相对于根目录: /b // return Result.redirect("/b"); // 内部地址 相对于当前路径: /a/b // return Result.redirect("b"); // 协议地址 return Result.redirect("https://github.com/justlive1"); } ``` #### 写入cookie ```java @Mapping("/") public void index(RoutingContext ctx) { ctx.response().setCookie("hello", "world"); ctx.response().setCookie("java", "script", 100); ctx.response().setCookie("uid", "xxx", ".justlive.vip", "/", 3600, true); } ``` #### 添加header ```java @Mapping("/") public void index(RoutingContext ctx) { ctx.response().setHeader("hello", "world"); } ``` #### 写入session ```java @Mapping("/") public void index(RoutingContext ctx) { ctx.request().getSession().put("key", "value"); } ``` ### 拦截器 `WebHook`是拦截器接口,可以实现执行前、执行后和结束拦截处理 ```java @Slf4j @Bean public class LogWebHook implements WebHook { @Override public boolean before(RoutingContext ctx) { log.info("before"); return true; } @Override public void after(RoutingContext ctx) { log.info("after"); } @Override public void finished(RoutingContext ctx) { log.info("finished"); } } ``` ### 异常处理 框架默认提供了一个异常处理器,如需自定义处理异常,可以像下面这样使用 ```java @Bean public class CustomExceptionHandler extends ExceptionHandlerImpl { @Override public void handle(RoutingContext ctx, Exception e, int status) { if (e instanceof CustomException) { // do something } else { super.handle(ctx, e, status); } } } ``` ### 部署项目 #### 修改端口 **编码指定** ```java Server.listen(8080); ``` **配置文件** ```properties oxygen.server.port=8081 ``` **启动命令** ```bash java -jar -Doxygen.server.port=8090 app.jar ``` #### 运行项目 **使用内嵌容器启动** 启动类 ```java public class Application { public static void main(String[] args) { Server.listen(); } } ``` 通用打包方式 - `${mainClass}`为上面的启动类 - 会生成`lib`目录存放依赖`jar` ```xml org.apache.maven.plugins maven-compiler-plugin ${maven.compiler.source} ${maven.compiler.target} UTF-8 org.apache.maven.plugins maven-jar-plugin true lib/ ${mainClass} org.apache.maven.plugins maven-dependency-plugin copy package copy-dependencies ${project.build.directory}/lib ``` 打成`fat-jar`: - 使用springboot打包插件 ```xml org.springframework.boot spring-boot-maven-plugin 1.3.8.RELEASE package repackage ``` **使用外部容器(jetty、tomcat等)** 无需web.xml配置,打包成`war`放入容器即可,实现机制可查看`WebContainerInitializer` ```xml false ``` ### 外部化配置 框架可以通过使用配置文件进行修改默认属性 ```properties ##### 基础配置 # 配置覆盖地址,用户外部配置覆盖项目配置 例 file:/config/*.properties,classpath*:/config/*.properties,xx.properties oxygen.config.override.path= # 类扫描路径属性 oxygen.class.scan=vip.justlive # 临时文件根目录 oxygen.temp.dir=.oxygen # 缓存实现类,自定义缓存时使用 oxygen.cache.class= ##### web # embedded 启动端口 oxygen.server.port=8080 # context path oxygen.server.contextPath= # session失效时间,单位秒 oxygen.web.session.expired=3600 # 默认静态资源请求前缀 oxygen.web.static.prefix=/public # 默认静态资源目录 oxygen.web.static.path=/public,/static,classpath:/META-INF/resources/webjars # 静态资源缓存时间 oxygen.web.static.cache=3600 # jsp路径前缀 oxygen.web.view.jsp.prefix=WEB-INF # thymeleaf 路径前缀 oxygen.web.view.thymeleaf.prefix=/templates # thymeleaf 视图后缀 oxygen.web.view.thymeleaf.suffix=.html # freemarker 路径前缀 oxygen.web.view.freemarker.prefix=/templates # 内置简单视图处理 路径前缀 oxygen.web.view.simple.prefix=/templates # 内置简单视图处理 视图后缀 oxygen.web.view.simple.suffix=.htm # 是否开启模板缓存 oxygen.web.view.cache.enabled=true ##### 定时任务job # job线程名称格式 oxygen.job.threadNameFormat=jobs-%d # job核心线程池大小 oxygen.job.corePoolSize=10 ##### i18n国际化 # i18n配置文件地址 oxygen.i18n.path=classpath:message/*.properties # i18n默认语言 oxygen.i18n.language=zh # i18n默认国家 oxygen.i18n.country=CN # i18n参数key oxygen.i18n.param.key=locale # i18n Session key oxygen.i18n.session.key=I18N_SESSION_LOCALE ##### jetty # 虚拟主机 oxygen.server.jetty.virtualHosts= # 连接器在空闲状态持续该时间后(单位毫秒)关闭 oxygen.server.jetty.idleTimeout=30000 # 温和的停止一个连接器前等待的时间(毫秒) oxygen.server.jetty.stopTimeout=30000 # 等待处理的连接队列大小 oxygen.server.jetty.acceptQueueSize= # 允许Server socket被重绑定,即使在TIME_WAIT状态 oxygen.server.jetty.reuseAddress=true # 是否启用servlet3.0特性 oxygen.server.jetty.configurationDiscovered=true # 最大表单数据大小 oxygen.server.jetty.maxFormContentSize=256 * 1024 * 1024 # 最大表单键值对数量 oxygen.server.jetty.maxFormKeys=200 ##### tomcat # 最大请求队列数 oxygen.server.tomcat.acceptCount=100 # 最大连接数 oxygen.server.tomcat.maxConnections=5000 # 最大工作线程数 oxygen.server.tomcat.maxThreads=200 # 最小线程数 oxygen.server.tomcat.minSpareThreads=10 # 最大请求头数据大小 oxygen.server.tomcat.maxHttpHeaderSize=8 * 1024 # 最大表单数据大小 oxygen.server.tomcat.maxHttpPostSize=2 * 1024 * 1024 # 连接超时 oxygen.server.tomcat.connectionTimeout=20000 # URI解码编码 oxygen.server.tomcat.uriEncoding=utf-8 # 调用backgroundProcess延迟时间 oxygen.server.tomcat.backgroundProcessorDelay=10 # 是否启用访问日志 oxygen.server.tomcat.accessLogEnabled=false # 访问日志是否开启缓冲 oxygen.server.tomcat.accessLogBuffered=true # 是否设置请求属性,ip、host、protocol、port oxygen.server.tomcat.accessLogRequestAttributesEnabled=false # 每日日志格式 oxygen.server.tomcat.accessLogFileFormat=.yyyy-MM-dd # 日志格式 oxygen.server.tomcat.accessLogPattern=common ##### undertow # 主机名 oxygen.server.undertow.host=0.0.0.0 # io线程数 oxygen.server.undertow.ioThreads= # worker线程数 oxygen.server.undertow.workerThreads= # 是否开启gzip压缩 oxygen.server.undertow.gzipEnabled=false # gzip处理优先级 oxygen.server.undertow.gzipPriority=100 # 压缩级别,默认值 -1。 可配置 1 到 9。 1 拥有最快压缩速度,9 拥有最高压缩率 oxygen.server.undertow.gzipLevel=-1 # 触发压缩的最小内容长度 oxygen.server.undertow.gzipMinLength=1024 # url是否允许特殊字符 oxygen.server.undertow.allowUnescapedCharactersInUrl=true # 是否开启http2 oxygen.server.undertow.http2enabled=false #### aio # aio服务连接空闲超时时间,单位毫秒 oxygen.server.aio.idleTimeout=10000 # aio请求连接超时时间,负数为不超时 oxygen.server.aio.requestTimeout=-1 # 连接线程数 oxygen.server.aio.acceptThreads=100 # 连接线程最大等待数 oxygen.server.aio.acceptMaxWaiter=10000 # 工作线程数 oxygen.server.aio.workerThreads=200 # 工作线程最大等待数 oxygen.server.aio.workerMaxWaiter=1000000 # 是否hold住端口,true的话随主线程退出而退出,false的话则要主动退出 oxygen.server.aio.daemon=false ``` ## 联系信息 E-mail: qq11419041@163.com