欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Jersey的绑定机制

程序员文章站 2022-07-02 09:38:32
...

一:简介

    因为项目需要,最近研究了下restful风格的编程方式,这里也Jersey为例。Jersey是一个restful框架,其提供了面向切面的Providers功能,一般情况下我们可以手动注册到Application中,但是它支持更加灵活的方式,这就是jersey提供的绑定机制。

二:客户端封装

   Jersey Client的每次创建连接都必须耗资源,我们可以用连接池模式进行封装。

  

package com.jersey.client;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;

import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.glassfish.jersey.apache.connector.ApacheClientProperties;
import org.glassfish.jersey.apache.connector.ApacheConnectorProvider;
import org.glassfish.jersey.client.ClientConfig;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;

/**
 * 对Jersey客户端得封装 
  1、使用spring提供的几个bean实现 InitializingBean ,
 * FactoryBean 工厂bean,其返回的对象不是指定类的一个实例,其返回的是该FactoryBean的getObject方法所返回的对象
 *  DisposableBean
 *  bean的销毁
 * 
 * @author tanjie
 *
 */
@SuppressWarnings("deprecation")
@Service("jerseyClient")
public final class JerseyClient implements FactoryBean<Client>,
		InitializingBean, DisposableBean {

	/**
	 * 返回Client实例
	 */
	private Client client;

	private ClientConfig clientConfig;

	/**
	 * httpclient连接的最大连接数
	 */
	private int maxTotal = 1000;

	/**
	 * httpclient每个主机地址的并发数
	 */
	private int defaultMaxPerRoute = 100;
	
	/**
	 * 无参构造函数,spring管理的对象必须提供一个
	 */
	public JerseyClient(){
		
	}

	public JerseyClient(int maxTotal, int defaultMaxPerRoute) {
		this.maxTotal = maxTotal;
		this.defaultMaxPerRoute = defaultMaxPerRoute;
	}

	/**
	 * spring会在初始化bean之后执行该方法,优先于init-method执行,但是init-method不依赖spring,只是其采用
	 * 反射实现,效率上没有实现InitalizingBean高
	 */
	@Override
	public void afterPropertiesSet() throws Exception {
		if (null == clientConfig) {
			clientConfig = new ClientConfig();
			final PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager();
			connectionManager.setMaxTotal(maxTotal);
			connectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
			clientConfig.property(ApacheClientProperties.CONNECTION_MANAGER,
					connectionManager);
			clientConfig.connectorProvider(new ApacheConnectorProvider());
			client = ClientBuilder.newClient(clientConfig);
		}else{
			// 默认创建一个
			client = ClientBuilder.newClient();
		}
	}

	/**
	 * 实例销毁
	 */
	@Override
	public void destroy() throws Exception {
		if (null != client) {
			client.close();
		}
	}

	@Override
	public Client getObject() throws Exception {
		return this.client;
	}

	@Override
	public Class<?> getObjectType() {
		return this.client.getClass();
	}

	@Override
	public boolean isSingleton() {
		return true;
	}

}

  2.1 按名称绑定

    使用@NameBinding

  

package com.tanjie.jersey.绑定机制;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.ws.rs.NameBinding;

/**
 * 通过名称绑定
 */
@NameBinding
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(value=RetentionPolicy.RUNTIME)
public @interface Banding {

}

  绑定@Provider

 

package com.jersey.绑定机制;

import java.io.IOException;

import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;

@Banding
@Priority(Priorities.USER)
@Provider
public class NameBindingFilter implements ContainerRequestFilter,
		ContainerResponseFilter {

	/**
	 * @param requestContext服务器请求过滤器
	 * 可以分为预处理:即当服务器接收到请求后先执行处理
	 * 后处理:当服务器接收到请求并处理后在进行处理(默认情况系)
	 * 二者可同时执行
	 */
	@Override
	public void filter(ContainerRequestContext requestContext)
			throws IOException {
		System.out.println("AirNameBindingFilter请求前"
				+ requestContext.getMethod());
	}
	
	/**
	 * 服务器响应请求后的过滤器
	 * @param requestContext 容器请求上下文
	 * @param responseContext 容器响应上下文
	 */
	@Override
	public void filter(ContainerRequestContext requestContext,
			ContainerResponseContext responseContext) throws IOException {
		System.out.println("AirNameBindingFilter请求后"
				+ requestContext.getMethod() + ",url:"
				+ responseContext.getStatus());
	}

}

  提供rest接口

package com.jersey.resources;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

import com.jersey.vo.User;
import com.jersey.绑定机制.Banding;

@Banding
@Path("/helloword")
public class RestfulHello {

	/**
	 * @GET 方法是幂等的,因为读取同一个资源,总是得到相同的数据,GET方法也是线程安全的
	 * 因为读取资源不会对其状态做改到
	 * 1、在接口中定义了资源请求的类型后,在实现类上不用再定义
	 * @return
	 */
	@GET
	@Produces(MediaType.TEXT_PLAIN)
	public String sayHello() {
		return "restful hello word";
	}

	@GET
	@Path("/{param}")
	@Produces("text/plain;charset=UTF-8")
	public String sayHello2UserByText(@PathParam("param") String username) {
		return "Hello " + username;
	}

	@GET
	@Path("/get")
	@Produces(MediaType.APPLICATION_JSON)
	public User HH(@QueryParam("username") String username) {
		User user = new User();
		user.setId(1);
		user.setName(username);
		return user;
	}
}

  单元测试:

 

package com.jersey_restful;

import javax.annotation.Resource;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.Invocation.Builder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.Response;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext.xml" })
public class NameBindTest{

	@Resource(name = "jerseyClient")
	private transient Client jerseyClient;

	@Test
	public void test() {
		WebTarget target = jerseyClient.target(
				"http://localhost:8080/jersey_restful/restful/helloword")
		Builder builder = target.request();
		Response response = builder.get();
		GenericType<String> genericType = new GenericType<String>(String.class);
		if (200 == response.getStatus()) {
			System.out.println("请求OK");
			System.out.println("返回值:" + response.readEntity(genericType));
		}
	}
}

  运行效果:

AirNameBindingFilter请求前GET
请求OK
返回值:restful hello world
AirNameBindingFilter请求后GET,url:200

 

  2.2 动态绑定

    Jersey支持动态绑定,可以更个性化的加载,在运行期只要匹配的动态绑定扩展的方法,面向切面的Provider就会被加载

   定义动态绑定:

  

package com.tanjie.jersey.绑定机制;

import javax.ws.rs.GET;
import javax.ws.rs.container.DynamicFeature;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.Provider;

import com.tanjie.jersey.resources.RestfulHello;

@Provider
public final class 动态绑定  implements DynamicFeature{

	@Override
	public void configure(ResourceInfo resourceInfo, FeatureContext context) {
		    boolean 是否是这个类 = RestfulHello.class.isAssignableFrom(resourceInfo.getResourceClass());
	        boolean 是否是这个方法名 = resourceInfo.getResourceMethod().getName().contains("sayHello");
	        boolean 是否是这个请求类型 = resourceInfo.getResourceMethod().isAnnotationPresent(GET.class);
	        if (是否是这个类 && 是否是这个方法名 && 是否是这个请求类型) {
	            context.register(AirDynamicBindingFilter.class);
	        }
	}

}

  切面

 

public class AirDynamicBindingFilter implements ContainerRequestFilter {
	
    @Override
    public void filter(final ContainerRequestContext requestContext) throws IOException {
    	   System.out.println("动态绑定业务处理");
    }
}

  浏览器执行:

http://localhost:8080/jersey_restful/restful/helloword

  运行效果:

动态绑定业务处理

  如果继续执行:

http://localhost:8080/jersey_restful/restful/helloword/get?username=zs

  则不会有满足切面条件。