Jersey是一个RESTFUL请求服务JAVA框架,与常规的JAVA编程使用的springmvc框架类似,它主要用于处理业务逻辑层。
jersey同样提供DI,是由glassfish hk2实现,也就是说,如果想单独使用jersey一套,需要另外学习Bean容器,譬如可以选择集成spirng使用;
springMVC出发点即是WEB,但jersey出发点确实RESTFull,体现点在与接口的设计方面,如springMVC返回复杂结构需要使用ModelAndView,而jersey仅仅需要返回一个流或者文件句柄;
Jersey的Response方法支持更好返回结果,方便的返回Status,包括200,303,401,403;
快速配置
maven
<!--jersey-->
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<version>2.0</version>
</dependency>
<!--JAXB API-->
<dependency>
<groupId>javax.xml.ws</groupId>
<artifactId>jaxws-api</artifactId>
<version>2.1</version>
</dependency>
<!-- Json支持 -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.12</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.12</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.12</version>
</dependency>
web.xml
<servlet>
<servlet-name>JerseyServlet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>cn.com.mink.resource.APIApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>JerseyServlet</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
常用注解
Annotation | 作用 | 说明 |
@GET | 查询请求 | 相当于数据库的查询数据操作 |
@POST | 插入请求 | 相当于数据库的插入数据操作 |
@PUT | 更新请求 | 相当于数据库的更新数据操作 |
@DELETE | 删除请求 | 相当于数据的删除数据操作 |
@Path | uri路径 | 定义资源的访问路径,client通过这个路径访问资源。比如:@Path("user") |
@Produces | 指定返回MIME格式 | 资源按照那种数据格式返回,可取的值有:MediaType.APPLICATION_XXX。比如:@Produces(MediaType.APPLICATION_XML) |
@Consumes | 接受指定的MIME格式 | 只有符合这个参数设置的请求再能访问到这个资源。比如@Consumes("application/x-www-form-urlencoded") |
@PathParam | uri路径参数 | 写在方法的参数中,获得请求路径参数。比如:@PathParam("username") String userName |
@QueryParam | uri路径请求参数 | 写在方法的参数中,获得请求路径附带的参数。比如:@QueryParam("desc") String desc |
@DefaultValue | 设置@QueryParam参数的默认值 | 如果@QueryParam没有接收到值,就使用默认值。比如:@DefaultValue("description") @QueryParam("desc") String desc |
@FormParam | form传递的参数 | 接受form传递过来的参数。比如:@FormParam("name") String userName |
@BeanParam | 通过Bena的形式传递参数 | 接受client传递的bean类型的参数,同时这个bean可以在属性上配置@FormParam用以解决client的属性名称和bean的属性名称不一致的问题。比如:@BeanParam User user |
@Context | 获得一些系统环境信息 | 通过@Context可以获得以下信息:UriInfo、ServletConfig、ServletContext、HttpServletRequest、HttpServletResponse和HttpHeaders等 |
@XmlRootElement | 将bean转换为xml | 如果要讲bean以xml或json的格式返回,必须要这个注解。比如: @XmlRootElement public class User{...} |
1、@QueryParam如果需要为参数设置默认值,可以使用 @DefaultValue:
@GET
@Path("/user")
@Produces("text/plain")
public User getUser(@QueryParam("name") String name,
@DefaultValue("26") @QueryParam("age") int age) {
...
}
对于多个同名参数,只需要把name定义成list,即可完成接收。
2、@BeanParam用于封装form数据到对象。需要使用@PathParam等注解指明从哪里取得数据
public class User {
@PathParam("userName)
private String userName;
@MatrixParam("m")
@Encoded
@DefaultValue("default")
private String matrixParam;
@HeaderParam("header")
private String headerParam;
3、要获取sevlet原生对象,可以使用@context,它可以注解字段或方法参数:
@Path("/")
public class Resource {
@Context
HttpServletRequest req;
@Context
ServletConfig servletConfig;
4、UriInfo对象保存着所有path,query pram的信息,还有一些常用的uriapi:
@GET
public String get(@Context UriInfo ui) {
MultivaluedMap<String, String> queryParams = ui.getQueryParameters();
MultivaluedMap<String, String> pathParams = ui.getPathParameters();
}
uriInfo.getBaseUri()//context url
uriInfo.getAbsolutePath()//requesturl+queryparm
uriInfo.getAbsolutePath();//requesturl
除了直接获取连接对象,也可以获取uribuilder,然后构建一个新的对象:
UriBuilder ub = uriInfo.getAbsolutePathBuilder();
URI userUri = ub.path("path").queryParam("name", "value").build();
//requesturl/path?name=value
以下两个静态方法用于绝对化相对连接和相对化绝对链接,相对于根目录。
UriInfo.resolve(java.net.URI)
UriInfo.relativize(java.net.URI)
uribuilder也可以使用模板字符串:
UriBuilder.fromUri("http://localhost/")
.path("{a}")
.queryParam("name", "{value}")
.build("segment", "value");
UriBuilder.fromUri("http://{host}/{path}?q={param}")
.resolveTemplate("host", "localhost")
.resolveTemplate("path", "myApp")
.resolveTemplate("param", "value").build();
5、@pathParm可以指定URI路径参数:
@Path("/users/{username}")
public class UserResource {
@GET
@Produces("text/xml")
public String getUser(@PathParam("username") String userName) {
...
}
}
还可以给path参数提供正则表达式掩码,如果不匹配,返回404.
@Path("users/{username: [a-zA-Z][a-zA-Z_0-9]*}")
response返回值
1、文本类型,方法返回值为string,直接返回string即可。
2、各类非文本类型,使用response包装返回。
return Response.ok(bufferedImage, "image/png").build();
return Response.status(Response.Status.OK).entity( bufferedImage).build();
//以上两个方法结果相同
ok函数的第一个参数是object类型,可以接收byte[]、BufferedImage、InputStream、File等对象,将其作为字节数据返回客户端。
3、302临时重定向
Response.temporaryRedirect(URI.create("/url")).build();
4、301永久重定向。
Response.seeOther(URI.create("/url")).build();
5、返回自定义pojo类型,通过自定义转换器处理。
返回自定义pojo类时,程序会调用内部注册的MessageBodyWriter转换器将pojo类通过OutputStream输出给客户端。可以注册自定义的pojo类和转换器,以实现特定功能。实际上,jersey结合velocity的关键一步,就是这样做的。
@Provider
@Produces(MediaType.TEXT_HTML)
public class VelocityHtmlWriter implements MessageBodyWriter<VelocityHtmlParams> {
@Context
ServletContext sc;
public long getSize(VelocityHtmlParams params, Class<?> type,
java.lang.reflect.Type genericType, java.lang.annotation.Annotation[] annotations, MediaType mediaType) {
return -1;
}
public boolean isWriteable(Class<?> type,
java.lang.reflect.Type genericType,
java.lang.annotation.Annotation[] annotations, MediaType mediaType) {
if (!mediaType.toString().equals(MediaType.TEXT_HTML)) {
return false;
}
return true;
}
public void writeTo(VelocityHtmlParams params, Class<?> type,
java.lang.reflect.Type genericType,
java.lang.annotation.Annotation[] annotations, MediaType mediaType,
MultivaluedMap<java.lang.String, java.lang.Object> httpHeaders,
java.io.OutputStream entityStream) {
Template t = Velocity.getTemplate(params.getFileName());
String appVer = sc.getInitParameter("buildDateTime");
String appName = sc.getInitParameter("appName");
String manualUrl = sc.getInitParameter("manualUrl");
String headerBGColor = sc.getInitParameter("headerBGColor");
if (appName == null) {
appName = "ESS営業管理システム";
}
if (headerBGColor == null) {
headerBGColor = "#56a";
}
params.putParam("appVer", appVer);
params.putParam("appName", appName);
params.putParam("manualUrl", manualUrl);
params.putParam("appLabel", ResourceBundle.getBundle("label/appLabel"));
params.putParam("pspLabel", ResourceBundle.getBundle("label/pspLabel"));
params.putParam("headerBGColor", headerBGColor);
//httpHeaders.putSingle("X-TEST-HEADER1", "AAZ");
httpHeaders.putSingle("Expires", "Expires: Tue, 25 Aug 2010 16:00 GMT");
try {
Writer writer = new OutputStreamWriter(entityStream, "UTF-8");
t.merge(params.getContext(), writer);
writer.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class VelocityHtmlParams {
private VelocityContext context;
private EventCartridge ec;
private VelocityHtmlEscapeHandler ve;
private String fileName;
public VelocityHtmlParams(String fileName) {
this.fileName = fileName;
ec = new EventCartridge();
ve = new VelocityHtmlEscapeHandler();
ec.addEventHandler(ve);
context = new VelocityContext();
context.put("esc", new EscapeTool());
context.put("util", new VelocityUtil());
ec.attachToContext(context);
}
public void putParam(String key, Object value) {
context.put(key, value);
}
public String getFileName() {
return fileName;
}
public VelocityContext getContext() {
return context;
}
public void setMinusSign(String minusSign) {
ec.removeEventHandler(ve);
VelocityHtmlEscapeHandler vhe = new VelocityHtmlEscapeHandler();
vhe.setMinusSign(minusSign);
ec.addEventHandler(vhe);
}
}
注意,如果没有使用web.xml文件,转换器需要在初始化时注册:
@ApplicationPath("/ws")
public class ApplicationConfig extends Application {
public ApplicationConfig() {
Velocity.setProperty("resource.loader", "class");
Velocity.setProperty("class.resource.loader.class",
"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
Velocity.setProperty("input.encoding", "UTF-8");
Velocity.setProperty("output.encoding", "UTF-8");
Velocity.init();
}
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new java.util.HashSet<>();
resources.add(VelocityHtmlWriter.class);
resources.add(MultiPartFeature.class);
addRestResourceClasses(resources);
return resources;
}
private void addRestResourceClasses(Set<Class<?>> resources) {
resources.add(.VelocityHtmlWriter.class);
}
}
resource类的生命周期
@RequestScoped 默认生命周期,类似struts2的action,与request绑定的多例模式。
@Stateless 类似单例,但提供了池化机制,增加伸缩性能,但不可用于多线程通讯。
@Singleton 单例模式
显然,通过这些注解,你可以自由选择struts的action还是springMVC的controller模式。
@RequestScoped+字段级@QueryParam注解,即可实现aciton
@Stateless+方法级@QueryParam注解,即可实现controller
jersey的依赖注入
@javax.ws.rs.ext.Provider
放在class相当于@component;放在方法上相当于@beans
@javax.inject.Inject
@javax.annotation.Resource;
Resource是老版本的注入注解, Inject完全兼容Resource,另外Resource会匹配类型和名称,相当于@qualified。
@javax.inject.Named;
cdi的限定符,按名称进行筛选。
public class NamedAnnotation extends AnnotationLiteral<Named> implements Named {
private final String value;
public NamedAnnotation(final String value) {
this.value = value;
}
public String value() {
return value;
}
}
//
@Inject @Named
private AcmsdService acmsdService;
@Inject
private Instance<AcmsdService> acmsdServiceInstance;
private AcmsdService getAcmsdService(String namedService) {
try {
return acmsdServiceInstance.select(new NamedAnnotation(namedService)).get();
} catch (UnsatisfiedResolutionException e) {
return acmsdService;
}
}
返回json
MOXy (默认)
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<version>2.30.1</version>
</dependency>
Java API for JSON Processing (JSON-P)
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-processing</artifactId>
<version>2.30.1</version>
</dependency>
Jackson
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.30.1</version>
</dependency>
Jettison
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jettison</artifactId>
<version>2.30.1</version>
</dependency>
以上四个包都提供jersey的json支持,使用方法介绍如下:
1、pojo直接转化
MOXy Jackson(需要配置转换器)支持
@XmlRootElement
public class MyJaxbBean {
public int age;
@XmlElement(name="king")//起别名
public String name;
@XmlTransient//不转化
public int age;
}
如图所示,直接使用一个注解,就可以定义一个obj转换器,resource中直接返回该类型即可,自动转化为json数据。
2、底层api
JSON-P Jettison支持
//json p
JsonObject myObject = Json.createObjectBuilder()
.add("name", "Agamemnon")
.add("age", 32)
.build();
//jettison
JSONObject myObject = new JSONObject();
try {
myObject.put("name", "Agamemnon");
myObject.put("age", 32);
} catch (JSONException ex) {
LOGGER.log(Level.SEVERE, "Error ...", ex);
}
显然为了省去配置的功夫,推荐使用MOXy
文件下载
public static Response transToResponse(final Workbook workbook, String fileName) throws UnsupportedEncodingException {
return Response.ok(new StreamingOutput() {
@Override
public void write(OutputStream outputStream) throws IOException, WebApplicationException {
workbook.write(outputStream);
}
}).header("Content-Disposition", "attachment;filename=\"" + URLEncoder.encode(fileName, "utf-8") + "\"").build();
}
public static Response transToZip(final HashMap<String, byte[]> bookMap, String fileName) throws UnsupportedEncodingException {
return Response.ok(new StreamingOutput() {
@Override
public void write(OutputStream outputStream) throws IOException, WebApplicationException {
ZipOutputStream zos = new ZipOutputStream(outputStream);
for (Map.Entry<String, byte[]> workbookEntry : bookMap.entrySet()) {
zos.putNextEntry(new ZipEntry(workbookEntry.getKey()));
zos.write(workbookEntry.getValue());
zos.closeEntry();
}
zos.finish();
}
}).header("Content-Disposition", "attachment;filename=\"" + URLEncoder.encode(fileName, "utf-8") + "\"").build();
}
中间件,过滤器
前拦截器
import javax.ws.rs.container.ContainerRequestFilter;
public class AuthorizationRequestFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext)
throws IOException {
final SecurityContext securityContext =
requestContext.getSecurityContext();
if (securityContext == null ||
!securityContext.isUserInRole("privileged")) {
requestContext.abortWith(Response
.status(Response.Status.UNAUTHORIZED)
.entity("User cannot access the resource.")
.build());
}
}
}
如上代码所示,注册的请求拦截器将在每一个请求创建时拦截,然后执行拦截逻辑,需要注意的是,中断的chain方法是通过.abortWith执行的。
后拦截器
public class PoweredByResponseFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
responseContext.getHeaders().add("X-Powered-By", "Jersey :-)");
}
}
将在response返回前端前执行,此时第一个requestcontext参数是只读的。
注意,前后过滤器都只在@path的url匹配和执行,如果没有url匹配的情况下,是不执行的,如果想要在url匹配前就执行,可以加@PreMatching注解。
@PreMatching
public class PreMatchingFilter implements ContainerRequestFilter {
此时注册的拦截器都是全局的,如果想要自定义拦截的资源方法,可以使用名称绑定。首先自定义一个注解:
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface Compress {}
然后将注解用在拦截器和resource方法(也可以是类)上即可。
@Compress
public class GZIPWriterInterceptor implements WriterInterceptor {
......
@GET
@Path("too-much-data")
@Compress
public String getVeryLongString() {
多过滤器情况下,可以分配优先级,请求过滤器低到高,响应过滤器高到低。
@Priority(1000)
拦截器
ReaderInterceptor,WriterInterceptor可以拦截到输入的url参数和输出的data数据,用流进行包装,提供中间件拦截功能,因为用得较少,此处略过。
【velocity】
该项目使用velocity模板技术来代替html,结合jersey返回页面给前端。
语法摘要
1、指令使用# 变量使用$
2、单个变量$name即可,如果需要动态拼接,使用大括号。${name}_path
主要语法
赋值:
#set($directoryRoot = “www” )
循环:
#foreach ($element in $list)
This is $element.
#end
$foreach是循环中内置的计数器变量:
$foreach.count 从1开始的计数
$foreach.index 从0开始的计数
$foreach.first 返回是否是第一个的布尔值
$foreach.last 返回是否是最后一个的布尔值
#break 指令中断循环
固定次数循环:
#foreach ( $bar in [2…-2] )
取值:
可以通过.操作通过方法获取(字段时默认调用get方法),实际上是通过调用getAttr,getattr,get(“attr”),isAttr四种取值方法来获取。
可以使用[]取值,实际上就是指定调用get()方法。
KaTeX parse error: Expected 'EOF', got '#' at position 28: …foo.get(0) 条件: #̲if (condition) …foo && $bar)
include和parse引入页面:
#include (“one.gif”, “two.txt”, “three.htm” )
#parse (“parsefoo.vm”)
前者可以引入多个,后者只能引入一个
前者静态引入,后者会调用模板引擎替换变量
内置对象:
r
e
q
u
e
s
t
、
request、
request、response、$session
Java操作:
Template t = Velocity.getTemplate(params.getFileName());
//注意,这里默认会在classpath下找,输入相对于classpath的相对路径即可
Writer writer = new OutputStreamWriter(entityStream, "UTF-8");
VelocityContext ctx = new VelocityContext();
ctx.put("name", "velocity");
ctx.put("date", (new Date()).toString());
t.merge(ctx, writer);
writer.flush();
详细可以查看:https://www.ctolib.com/docs-Velocity-c-220510.html