oxygen

Maven Central License

轻量级Java框架

介绍

一个轻量级Java框架

特性

快速开始

创建Maven项目

<!-- tomcat,jetty,undertow -->
<properties>
  <oxygen.server>-tomcat</oxygen.server>
</properties>

<!-- 使用内嵌容器启动 -->
<dependency>
    <groupId>vip.justlive</groupId>
    <artifactId>oxygen-web${oxygen.server}</artifactId>
    <version>${oxygen.version}</version>
</dependency>

Gradle

compile 'vip.justlive:oxygen-web-tomcat:$oxygenVersion'

不需要webapp项目框架,支持Servlet3.0

编写 main 函数写一个 Hello World

public static void main(String[] args) {
  Router.router().path("/").handler(ctx -> ctx.response().write("hello world"));
  Server.listen(8080);
}

用浏览器打开 http://localhost:8080 这样就可以看到 hello world 了!

内容详解

注册路由

硬编码方式

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);

注解方式

@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获取

Router.router().path("/").handler(ctx -> {
  String id = ctx.request().getParam("id");
  ctx.response().write(id);
});

使用注解获取

@Mapping(value = "/ajax", method = {HttpMethod.POST})
public Book find(@Param Long id, @Param("tp") String type) {
  // ...
  return new Book();
}

restful参数

使用RoutingContext获取

Router.router().path("/{id}").handler(ctx -> {
  String id = ctx.request().getPathVariable("id");
  ctx.response().write(id);
});

使用注解获取

@Mapping(value = "/{id}", method = {HttpMethod.POST})
public void ajax(@PathParam("id") Long id) {
  // ...
}

header参数

使用RoutingContext获取

Router.router().path("/").handler(ctx -> {
  String id = ctx.request().getHeader("id");
  ctx.response().write(id);
});

使用注解获取

@Mapping(value = "/", method = {HttpMethod.POST})
public void ajax(@HeaderParam("id") Long id) {
  // ...
}

cookie参数

使用RoutingContext获取

Router.router().path("/").handler(ctx -> {
  String id = ctx.request().getCookie("id");
  ctx.response().write(id);
});

使用注解获取

@Mapping(value = "/", method = {HttpMethod.POST})
public void ajax(@CookieParam("id") Long id) {
  // ...
}

参数转对象

实体类

@Data
public class Book {
  private String name;
  private String author;
}

使用RoutingContext转换

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);
});

使用注解获取

@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

自定义静态资源可使用下面代码

Router.staticRoute().prefix("/lib").location("classpath:lib");

也可以通过配置文件指定

web.static.prefix=/public
web.static.path=/public,/static,classpath:/META-INF/resources/webjars

上传文件

使用RoutingContext获取

Router.router().path("/").handler(ctx -> {
  MultipartItem file = ctx.request().getMultipartItem("file");
  // ...
});

使用注解获取

@Mapping("/")
public void upload(MultipartItem image, @MultipartParam("file1") MultipartItem file) {
  // 不使用注解则使用方法参数名作为请求参数名称
  // 使用注解指定请求参数名称
}

结果渲染

渲染json

// 使用RoutingContext返回
Router.router().path("/").handler(ctx -> {
  ctx.response().json(new Book("Java", "xxx"));
});

// 注解式
@Mapping("/")
public Book find() {
  // 直接返回对象,框架默认处理成json
  return new Book("Java", "xxx");
}

渲染文本

// 使用RoutingContext返回
Router.router().path("/").handler(ctx -> {
  ctx.response().text("hello world");
});

渲染html

// 使用RoutingContext返回
Router.router().path("/").handler(ctx -> {
  ctx.response().html("<html><body><span>hello world</span></body></html>");
});

渲染模板

内置支持了jspthymeleaf模板,默认对应resources下的WEB-INFtemplates目录

# 可通过下面配置进行更改模板目录
web.view.jsp.prefix=WEB-INF
web.view.thymeleaf.prefix=/templates

模板使用

// 使用RoutingContext
Router.router().path("/").handler(ctx -> {
  ctx.response().template("index.html");
});

Router.router().path("/").handler(ctx -> {
  Map<String, Object> attrs = new HashMap<>();
  // ...
  ctx.response().template("index.html", attrs);
});

// 注解式
@Mapping("/")
public Result index() {
  return Result.view("index.html");
}

@Mapping("/")
public Result index() {
  Map<String, Object> attrs = new HashMap<>();
  // ...
  return Result.view("index.html").addAttributes(attrs);
}

重定向

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

@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

@Mapping("/")
public void index(RoutingContext ctx) {
  ctx.response().setHeader("hello", "world");
}

写入session

@Mapping("/")
public void index(RoutingContext ctx) {
  ctx.request().getSession().put("key", "value");
}

拦截器

WebHook是拦截器接口,可以实现执行前、执行后和结束拦截处理

@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");
  }
}

异常处理

框架默认提供了一个异常处理器,如需自定义处理异常,可以像下面这样使用

@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);
    }
  }
}

部署项目

修改端口

编码指定

Server.listen(8080);

配置文件

server.port=8081

启动命令

java -jar -Dserver.port=8090 app.jar

运行项目

使用内嵌容器启动

启动类

public class Application {
  public static void main(String[] args) {
    Server.listen();
  }
}

通用打包方式

打成fat-jar:

使用外部容器(jetty、tomcat等)

无需web.xml配置,打包成war放入容器即可,实现机制可查看WebContainerInitializer

<!-- 解决默认打war包报错 webxml attribute is required -->
<properties>
  <failOnMissingWebXml>false</failOnMissingWebXml>
</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: [email protected]