jersey+jetty+jdk8实现restful接口
Posted tower888
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jersey+jetty+jdk8实现restful接口相关的知识,希望对你有一定的参考价值。
jersey+jetty+jdk8实现restful接口, 嵌入式, 无需tomcat部署, 比springboot轻量.
支持统一异常处理;
支持Date日期序列化, 响应时自动将对象中的日期字段转为自定义格式的时间格式字符串 返回给客户端
目录结构:
build.gradle:
import org.apache.tools.ant.filters.FixCrLfFilter;
import org.apache.tools.ant.filters.ReplaceTokens;
apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'eclipse'
project.group = 'com.soft'
project.archivesBaseName = 'jersey-jetty'
project.version = ''
tasks.withType(JavaCompile) {
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
options.encoding="UTF-8"
}
javadoc { options.encoding = "UTF-8" }
repositories {
mavenLocal()
maven{ url "http://maven.aliyun.com/nexus/content/groups/public" }
}
dependencies {
compile 'cn.hutool:hutool-all:5.3.5'
compile 'org.glassfish.jersey.containers:jersey-container-jetty-http:2.34'
compile 'org.glassfish.jersey.containers:jersey-container-servlet-core:2.34'
compile 'org.glassfish.jersey.inject:jersey-hk2:2.34'
compile 'org.glassfish.jersey.media:jersey-media-json-jackson:2.34'
//compile 'org.eclipse.jetty:jetty-server:9.4.31.v20200723'
//compile 'org.eclipse.jetty:jetty-servlet:9.4.31.v20200723'
//compile 'org.eclipse.jetty:jetty-util:9.4.31.v20200723'
compile 'org.eclipse.jetty:jetty-server:9.4.41.v20210516'
compile 'org.eclipse.jetty:jetty-servlet:9.4.41.v20210516'
compile 'org.eclipse.jetty:jetty-util:9.4.41.v20210516'
compile 'org.slf4j:slf4j-api:2.0.0-alpha1'
runtime 'org.slf4j:jcl-over-slf4j:2.0.0-alpha1'
runtime 'ch.qos.logback:logback-classic:1.3.0-alpha5'
compile group: 'org.mybatis', name: 'mybatis', version: '3.5.4'
compile group: 'com.zaxxer', name: 'HikariCP', version: '3.4.5'
runtime 'com.oracle:ojdbc6:11.2.0.3'
compileOnly 'org.projectlombok:lombok:1.18.12'
annotationProcessor 'org.projectlombok:lombok:1.18.12'
}
task libs(type: Copy) {
from configurations.runtime
into "$buildDir/libs"
}
task makeJar(type:org.gradle.api.tasks.bundling.Jar) {
from('build/classes/java/main')
exclude('*.properties', '*.xml','*.setting')
}
processResources(){
filter(ReplaceTokens,tokens: loadEnv());
filter ReplaceTokens, tokens: [
"version": version
]
filter(FixCrLfFilter,
eol: FixCrLfFilter.CrLf.newInstance('lf'),
tab: FixCrLfFilter.AddAsisRemove.newInstance('asis'),
tablength: 4,
eof: FixCrLfFilter.AddAsisRemove.newInstance('remove'),
fixlast: true)
}
task processShell(type: Copy) {
description = " copy shell scripts to buildDir/shell"
from 'src/main/sh'
into "$buildDir/sh"
filter(ReplaceTokens,tokens: loadEnv());
filter(FixCrLfFilter,
eol: FixCrLfFilter.CrLf.newInstance('lf'),
tab: FixCrLfFilter.AddAsisRemove.newInstance('asis'),
tablength: 4,
eof: FixCrLfFilter.AddAsisRemove.newInstance('remove'),
fixlast: true)
}
test {
systemProperties 'property': 'value'
}
task deploy(type: Copy) {
description = ' deploy all the binary and config files to destination(prefix) '
def destFold=file("$prefix")
into(destFold)
from("$buildDir/libs"){
into("lib")
}
from("$buildDir/sh"){
into("sh")
}
from("$buildDir/resources/main"){
into("conf")
}
doFirst{
logger.lifecycle("deploy files to : $destFold")
}
doLast{
logger.lifecycle("deploy success!")
}
}
task preDeploy(){
description = ' pre actions for task deploy'
def logsdir=file("$prefix/log")
if(!logsdir.exists()){
logsdir.mkdir();
}
def libs=fileTree("$prefix/lib")
libs.each {jar ->
jar.delete()
}
def configs=fileTree("$prefix/conf")
configs.each {conffile ->
conffile.delete()
}
}
libs.dependsOn(makeJar)
build.dependsOn(['libs','processShell','processResources'])
deploy.dependsOn preDeploy
deploy.dependsOn build
env/config.groovy:
prefix='/output'
environments {
dev {
sh{
WORK_HOME='/home/xxx/newitf/jersey-jetty';
PNAME='jersey-jetty';
LANG='zh_CN.utf8';
JAVA_HOME='/home/xxx/app/jdk/jdk8';
}
}
}
JettyStart
package com.soft;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.soft.server.JettyServer;
public class JettyStart {
private static final Logger log = LoggerFactory.getLogger(JettyStart.class);
public static void main(String... args) {
new JettyStart().start();
}
private JettyServer server;
public JettyStart() {
server = new JettyServer();
}
public void start() {
try {
server.start();
server.join();
} catch (Exception e) {
log.error("", e);
} finally {
server.destroy();
}
}
}
JerseyStart (此类与JettyStart 2个都可以启动服务)
package com.soft;
import java.net.URI;
import org.glassfish.jersey.jetty.JettyHttpContainerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JerseyStart {
private static final Logger log = LoggerFactory.getLogger(JerseyStart.class);
public static void main(String[] args) {
try {
// uri只有第一个路径段将用作上下文路径,其余部分将被忽略
JettyHttpContainerFactory.createServer(URI.create("http://localhost:8080/"), new RestApplication());
} catch (Exception e) {
log.error("", e);
}
}
}
JettyServer
package com.soft.server;
import com.soft.RestApplication;
import com.soft.config.ServerConf;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.glassfish.jersey.servlet.ServletContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JettyServer {
private static final Logger log = LoggerFactory.getLogger(JettyServer.class);
private static final int port = ServerConf.VO.getPort();
private static final String contextPath = ServerConf.VO.getContextPath();
private static final String resourceBase = ServerConf.VO.getResourceBase();
private static final int maxThreads = ServerConf.VO.getMaxThreads();
private static final String ipWhiteList = ServerConf.VO.getIpWhiteList();
private Server server;
public JettyServer() {
}
public void start() throws Exception {
server = new Server(createThreadPool());
server.addConnector(createConnector());
server.setHandler(createHandlers());
server.setStopAtShutdown(true);
server.start();
}
public void join() throws InterruptedException {
server.join();
}
public void destroy() {
server.destroy();
}
private ThreadPool createThreadPool() {
QueuedThreadPool threadPool = new QueuedThreadPool();
threadPool.setName("embed-jetty-http");
threadPool.setMinThreads(1);
threadPool.setMaxThreads(maxThreads);
return threadPool;
}
private ServerConnector createConnector() {
ServerConnector connector = new ServerConnector(server);
connector.setPort(port);
log.info("App start at port: {}", port);
return connector;
}
private HandlerCollection createHandlers() {
ServletContextHandler handler = new ServletContextHandler(ServletContextHandler.SESSIONS);
handler.setContextPath(contextPath);
handler.setResourceBase(resourceBase);
ServletHolder holder = handler.addServlet(ServletContainer.class, "/*");
holder.setInitOrder(1);
holder.setInitParameter("javax.ws.rs.Application", RestApplication.class.getName());
HandlerCollection handlerCollection = new HandlerCollection();
handlerCollection.setHandlers(new Handler[]{handler});
return handlerCollection;
}
// private static InetAccessHandler getFireWallHandler() {
// InetAccessHandler ipHandler = new InetAccessHandler();
// if (StrUtil.isBlank(ipWhiteList)) {// 空,不配置,则不校验
// return ipHandler;
// }
//
// String[] ipList = ipWhiteList.split(",");
// for (String ip : ipList) {
// ipHandler.include(ip);
// }
// return ipHandler;
// }
}
UserController:
package com.soft.controller;
import java.util.Date;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.soft.po.User;
import com.soft.util.Result;
@Path("/abc")
public class UserController {
private static final Logger log = LoggerFactory.getLogger(UserController.class);
@GET
@Path("/hello")
@Produces(MediaType.TEXT_PLAIN)
public String hi() {
return "hello jersey";
}
@GET
@Path("/{username}/{age}")
@Produces(MediaType.APPLICATION_JSON)
public Result findByKey(@PathParam("username") String username, @PathParam("age") int age) {
log.info("接收到请求" + username);
User user = new User();
user.setUsername(username);
user.setAge(age);
user.setStatus(20);
user.setModdate(new Date());
return Result.ok(user);
}
}
Result:
package com.soft.util;
import com.soft.exception.ErrorType;
import lombok.Data;
@Data
public class Result {
private String code;
private String msg;
private Object body;
public Result() {
}
public Result(ErrorType type) {
this.code = type.getCode();
this.msg = type.getMsg();
}
public Result(String code, String msg) {
this.code = code;
this.msg = msg;
}
public Result(String code, String msg, Object body) {
this.code = code;
this.msg = msg;
this.body = body;
}
public static Result gen(String code, String msg, Object data) {
return new Result(code, msg, data);
}
public static Result gen(ErrorType type, Object data) {
return new Result(type.getCode(), type.getMsg(), data);
}
public static Result ok() {
return gen(ErrorType.Succ, null);
}
public static Result ok(Object data) {
return gen(ErrorType.Succ, data);
}
public static Result fail(String msg) {
return new Result(ErrorType.Fail.getCode(), msg, null);
}
}
ErrorType
package com.soft.exception;
public enum ErrorType {
Succ("0","操作成功"),
Fail("1","操作失败"),
ReqParamMissing("0001","请求参数缺少"),
DataNotExist("1000","数据不存在"),
DataExist("1001","数据已存在"),
InvalidQuery("1002","无效的查询"),
InvalidMod("1003","无效的修改"),
SysErr("9999","系统错误");
private String code;
private String msg;
ErrorType(String code,String msg) {
this.code=code;
this.msg = msg;
}
public String getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
ServerConf
package com.soft.config;
import cn.hutool.setting.Setting;
public class ServerConf {
private static final Setting SETTING = new Setting("server.setting");
public static final ServerVo VO = SETTING.toBean(ServerVo.class);
private ServerConf() {
}
}
ServerVo:
package com.soft.config;
import lombok.Data;
@Data
public class ServerVo {
private int port;
private String contextPath;
private String resourceBase;
private int maxThreads;
private String ipWhiteList;// 逗号分隔ip
}
BUserController
package com.soft.controller;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import com.soft.dao.BBroadbandUserDAO;
import com.soft.exception.AppException;
import com.soft.exception.ErrorType;
import com.soft.exception.ReqException;
import com.soft.po.BBroadbandUser;
import com.soft.util.Result;
import cn.hutool.core.util.StrUtil;
@Path("/buser")
public class BUserController {
@POST
@Path("/find")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Result findByKey(BBroadbandUser req) {
String username = req.getUsername();
if (StrUtil.isBlank(username)) {
throw new ReqException(ErrorType.ReqParamMissing);
}
BBroadbandUser po = new BBroadbandUser();
po.setUsername(username);
BBroadbandUserDAO udao = new BBroadbandUserDAO();
BBroadbandUser user = udao.queryByPK(po);
if (user==null) {
throw new AppException(ErrorType.DataNotExist);
}
return Result.ok(user);
}
}
User:
package com.soft.po;
import java.util.Date;
import lombok.Data;
@Data
public class User {
private String username;
private int age;
private int status;
private Date moddate;
}
package com.soft.controller;
import com.soft.exception.BaseException;
import com.soft.exception.ErrorType;
import com.soft.util.Result;
import org.eclipse.jetty.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
/**
* 异常捕获, 此类必须放在此controller包下,放在其他目录,客户端会抛异常
*/
@Provider
public class ExceptionMappingResource implements ExceptionMapper<Throwable> {
private static final Logger log = LoggerFactory.getLogger(ExceptionMappingResource.class);
@Override
public Response toResponse(Throwable t) {
Result re = null;
if (t instanceof BaseException) {
BaseException e = (BaseException) t;
re = new Result(e.getCode(), e.getMessage());
log.info("{}", re);
} else {
re = new Result(ErrorType.Fail.getCode(), t.getMessage());
log.error("", t);
}
return Response.status(HttpStatus.INTERNAL_SERVER_ERROR_500).entity(re).build();
}
}
package com.soft.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJsonProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
@Provider
@Produces(MediaType.APPLICATION_JSON) //必须增加@Produces注解
public class JacksonConf extends JacksonJsonProvider {
private static final Logger log = LoggerFactory.getLogger(JacksonConf.class);
@Override
public void writeTo(Object value, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException {
//log.info("------------date format convert-------------");
// 将写给客户端的数据中的Date类型字段转换为特定格式
ObjectMapper mapper = new ObjectMapper();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
mapper.setDateFormat(sdf);
// 必须设置, 写给客户端的json数据中,Date类型字段才能转为自定义的格式
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.writeValue(entityStream, value);// 结果写给客户端
}
}
package com.soft;
import com.soft.controller.JacksonConf;
import org.glassfish.jersey.server.ResourceConfig;
import javax.ws.rs.ApplicationPath;
//@ApplicationPath("/abc") //注解无效
public class RestApplication extends ResourceConfig {
public RestApplication() {
System.out.println("---------ResourceConfig---------");
// 服务类所在的包路径
packages("com.soft.controller");
register(JacksonConf.class);
// register(JacksonJsonProvider.class);
// register(ValidationFeature.class);
}
}
server.setting:
port=8080
contextPath=/
resourceBase=@sh.WORK_HOME@/conf
maxThreads=30
ipWhiteList=
说明:
- ExceptionMappingResource必须放在controller目录下,在其他目录,controller代码中抛出自定义异常时,会导致客户端抛异常 - 使用JerseyStart,jetty-server:9.4.40.v20210413版本 启动时, JacksonConf类必须增加注解@Produces(MediaType.APPLICATION_JSON),才能Date日期序列化生效 - 使用JettyStart,jetty-server:9.4.31.v20200723版本 启动时, JacksonConf类无需增加注解@Produces(MediaType.APPLICATION_JSON),也能Date日期序列化生效 - 必须使用jetty 9.4.31.v20200723版本 ,RestApplication配置的Date类型序列化才会成功; 稍高版本 jetty 9.4.41.v20210516 也无效,无法转换Date格式. 解决: 这是因为未增加注解@Produces(MediaType.APPLICATION_JSON), 增加后可使用jetty9高版本.
以上是关于jersey+jetty+jdk8实现restful接口的主要内容,如果未能解决你的问题,请参考以下文章
带有 XML 参数的 REST 服务操作上的 HTTP 错误 415 不受支持的媒体类型(Jersey + Jetty)