jersey/velocity学习

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")
@PathParamuri路径参数写在方法的参数中,获得请求路径参数。比如:@PathParam("username")  String userName
@QueryParamuri路径请求参数写在方法的参数中,获得请求路径附带的参数。比如:@QueryParam("desc")  String desc
@DefaultValue设置@QueryParam参数的默认值如果@QueryParam没有接收到值,就使用默认值。比如:@DefaultValue("description") @QueryParam("desc") String desc
@FormParamform传递的参数接受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、 requestresponse、$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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值