在web程序开发过程中,通常我们会需要对我们的接口进行访问控制,例如控制用户的访问权限、记录用户的访问日志等,在我们使用Jersey进行Restful服务开发中,同样会有类似需求,下面我们介绍下,使用Jersey框架,实现接口访问的三种方式。
一、使用注解名称绑定过滤器
1.1 创建名称绑定注解
使用@NameBinding注解,可以定义一个运行时的自定义注解,该注解可以用于定义类级别名称和泪的方法。例如,我们定义一个用户访问的注解。
@NameBinding //标识名称绑定的注解
@Target({ElementType.TYPE, ElementType.METHOD}) //表示该注解可以使用在类和方法上。
@Retention(value = RetentionPolicy.RUNTIME)
public @interface UserLogger {
}
上面代码我们定义了一个名称绑定注解UserLogger。
1.2 注解绑定过滤器
创建了注解之后,我们需要将注解和Jersey中的Provider 组件绑定,示例中我们使用的是过滤器。
@Provider
@UserLogger
@Priority(Priorities.USER)
public class LoggerFilter implements ContainerRequestFilter ,ContainerResponseFilter{
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
System.out.println("访问请求日志过滤器执行了>>>>>>>>>>>>");
}
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
System.out.println("访问响应日志过滤器执行了>>>>>>>>>>>");
}
}
@Provider 注解Jersey注解,Jersey扫描到该注解,就会创建对应的组件对象。
@UserLogger 就是我们自定义的名称绑定注解
@Priority 是用于表示该过滤器的执行顺序,其中参数为long类型,对于请求过滤器,该数值越小越先执行,响应过滤器则相反。
ContainerRequestFilter 为请求过滤器
ContainerResponseFilter 为响应过滤器
在请求和响应的过滤方法中,我们简单的打印输出。
需要注意的是,我们创建了过滤器之后需要在Jersey中进行声明,我们在Jerysey的ResourceConfig 子类中,注册该过滤器,注册有两种方式,一种是扫描包的形式,这时需要在过滤器上加上@Provider注解,另一种是直接注册该过滤器。
packages("com.xxxx.xxxx.xxxx.filter"); //扫描包的形式 过滤器所在的包
register(LoggerFilter.class); //直接注册过滤器
1.3 注解绑定接口
上面我们创建好注解和过滤器之后,需要将在我们需要使用过滤器的接口方法上使用注解。
@Path("/test")
public class TestResource {
@GET
@Path("/1")
public String test1(){
return "不带过滤器";
}
@GET
@Path("/2")
@UserLogger
public String test2(){
return "带过滤器";
}
}
我们创建了两个接口,一个路径是/test/1 没有使用过滤器,一个路径是/test/2使用注解,按照我们的设计,当访问./test/1时,日志过滤器不起作用,访问/test/2时日志过滤器起作用。
二、使用动态绑定的形式注册过滤器
上面介绍的名称保定的形式需要通过自定义注解的形式来实现过滤器绑定,而动态绑定则不需要新增注解,而是需要编码的形式,实现动态绑定。动态绑定需要实现动态特征接口javax.ws.rs.container,DynamiFeature,定义扩展点方法,请求方法类型等匹配信息,在运行期,一旦Provider匹配到当前处理类或方法,面向切面的Provider方法就是触发。
2.1 实现动态绑定特征
public class LoggerDynaimcFeature implements DynamicFeature {
@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
String name = resourceInfo.getResourceMethod().getName();
if("test2".equals(name)){
context.register(LoggerFilter.class);
}
}
}
上面的代码实现的是当我们访问的是test2方法时,就会注册LoggerFilter,实现该过滤器的方法。
我们需要在同样我们需要注册该动态特征来
register(LoggerDynaimcFeature .class);
这时我们重新启动项目时,分别访问test/1和test/2,就会看到只有test2时,日志过滤器才会起作用。
2.2 名称绑定和动态绑定对比
动态绑定相比于名称绑定,不需要自定义注解,使用纯编码的形式实现。
名称绑定相比于动态绑定使用范围更广,因为我们使用注解的方式,可以对任意资源,任意方法进行控制。而使用动态绑定的形式,我们需要在动态特征类进行对应的匹配,适用范围较窄。
三、使用注解绑定拦截器
前面的两种形式是通过过滤器的形式,对接口控制,一般我们做日志访问记录等方式,采用该方式较好。而如果我们需要控制用户对接口的访问,例如登陆控制,权限控制等,就需要使用方法拦截器。
由于Jersey中的AOP是基于HK2框架实现的,所以对应的拦截器的功能也是由HK2框架实现。
现在我们模拟实现一个登陆拦截器的功能。
3.1 登录器注解
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginTest {
}
3.2 方法拦截器
@LoginTest
public class LoginTestMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
return "没有权限访问";
}
}
3.3 方法拦截器的绑定
Jersey在调用方法拦截器的时候,需要InterceptionService的实现。
该接口中有三个方法,在执行对应的接口方法之前会调用getMethodInteceptors()方法,获取对应的拦截器,并执行拦截器。
public class JerseyInterceptor implements InterceptionService {
private static Map<Annotation, MethodInterceptor> map = new HashMap<>();
static{
Annotation[] annotations = LoginTestMethodInterceptor.class.getAnnotations();
for(Annotation annotation : annotations){
map.put(annotation, new LoginTestMethodInterceptor());
}
}
@Override
public Filter getDescriptorFilter() {
return new Filter() {
public boolean matches(Descriptor descriptor) {
return true;
}
};
}
@Override
public List<MethodInterceptor> getMethodInterceptors(Method method) {
Annotation[] annotations = method.getAnnotations();
List<MethodInterceptor> list = new ArrayList<>();
for (Annotation annotation :annotations){
if(map.get(annotation) != null){
list.add(map.get(annotation));
}
}
return list;
}
@Override
public List<ConstructorInterceptor> getConstructorInterceptors(Constructor<?> constructor) {
return null;
}
}
上面代码可以看到,我们是将注解与拦截器绑定,通过方法上的注解,获取方法对应的拦截器,并执行拦截器。
我们创建的拦截服务实现,需要与拦截服务进行绑定,这时需要AbstracctBinding对象的实现‘。
public class JerseyBinding extends AbstractBinder {
@Override
protected void configure() {
this.bind(JerseyInterceptor.class).to(InterceptionService.class).in(Singleton.class);
}
}
public class JerseyFeature implements Feature {
@Override
public boolean configure(FeatureContext context) {
context.register(new JerseyBinding());
return true;
}
}
同样我们需要在ResourceConfig中注册该特征类
-
register(JerseyFeature.class);
3,4 测试
测试接口如下
@Path("/test")
public class TestResource {
@Path("/1")
@GET
public String test1(){
return "不带拦截器";
}
@Path("/2")
@GET
@LoginTest
public String test2(){
return "不带拦截器";
}
}
我们访问test/1的时候可以看到不带拦截器,访问test/2的时候可以看到没有访问权限