Jersey的绑定机制
程序员文章站
2022-06-16 09:40:40
...
一:简介
因为项目需要,最近研究了下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
则不会有满足切面条件。