导读:
前些时间写了很多编码习惯的帖子,今天写一点简单的技术贴。其实我个人觉得编码习惯是最主要的,比技术重要,但初学者还是喜欢看技术贴,今天就写一个小Demo,也加深自己的理解。
JDK的动态代理是非常重要的技术,使用的地方很多,用于代理接口,Spring的AOP也会用到。技术细节这里不贴了,我不是技术高手,大家可以上网搜索一下一大把,今天我们结合spring编写一个简陋的“框架”。
完整代码已经上传到GITHUB,地址在最后面。
假设我们需要调用另外一个系统提供了的GET请求 http://localhost:8081/test/get2?key=somekey**
  
 
我们只需要定义一个接口:
import cn.xiaowenjie.myrestutil.http.GET;
import cn.xiaowenjie.myrestutil.http.Param;
import cn.xiaowenjie.myrestutil.http.Rest;
import cn.xiaowenjie.retrofitdemo.beans.ResultBean;
@Rest("http://localhost:8081/test")
public interface IRequestDemo{
  @GET
  ResultBeanget1();
  @GET("/get2")
  ResultBeangetWithKey(@Param("key")String key);
}
 
 然后直接注入该接口即可:
@Service
public class TestService{
  @Autowired
  IRequestDemo demo;
  public void test(){
    // 调用接口,得到结果
    ResultBean get1 = demo.get1();
    ResultBean get2 = demo.getWithKey("key-------");
  }
}
 
 这就是今天Demo的效果,看着还行,有点类似restfeign,当然离真正应用还有一段差距。我们这里主要是学习动态代理和基本的spring应用。
总共不到200行代码,很容易阅读,逐一说明实现过程。
这里定义三个注解
/**
 * 包装服务器信息类,目前只有host,其他自己配置即可。
 */
@Data
public class RestInfo{
  private String host;
}
 
 /**
 * 请求信息包装类
 */
@Data
public class RequestInfo{
  private String url;
  private Class<?> returnType;
  private LinkedHashMap<String, String> params;
}
 
 /**
 * 处理网络请求接口
 */
public interface IRequestHandle{
  Objecthandle(RestInfo restInfo, RequestInfo request);
}
 
 Spring启动的时候,扫描所有的带Rest注解的接口。如下这种
@Rest("http://localhost:8081/test")
public interface IRequestDemo
 
 定义一个工具Bean,Bean注册的时候使用 Reflections 扫描工程里面属于带Rest注解的接口。
@Component
public class RestUtilInit{
  @Autowired
  IRequestHandle requestHandle;
  @PostConstruct
  public void init(){
    Set<Class<?>> requests = new Reflections("cn.xiaowenjie").getTypesAnnotatedWith(Rest.class);
    for (Class<?> cls : requests) {
      createProxyClass(cls);
    }
  }
}
 
 创建动态代理实现如下:
private void createProxyClass(Class<?> cls){
  System.err.println("/tcreate proxy for class:" + cls);
  // rest服务器相关信息
  final RestInfo restInfo = extractRestInfo(cls);
  InvocationHandler handler = new InvocationHandler() {
    @Override
    publicObjectinvoke(Object proxy, Method method, Object[] args)throwsThrowable{
      RequestInfo request = extractRequestInfo(method, args);
      return requestHandle.handle(restInfo, request);
    }
  };
  // 创建动态代理类
  Object proxy = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[] { cls }, handler);
  registerBean(cls.getName(), proxy);
}
 
 其实就是把请求包装成RestInfo和RequestInfo,然后创建 一个InvocationHandler实现接口代理,在里面调用IRequestHandle接口处理请求。不复杂,就几行代码。
@Autowired
ApplicationContext ctx;
public void registerBean(String name, Object obj){
  // 获取BeanFactory
  DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) ctx
      .getAutowireCapableBeanFactory();
  // 动态注册bean.
  defaultListableBeanFactory.registerSingleton(name, obj);
}
 
 /**
 * 实现了IRequestHandle,基于resttemplate处理rest请求。
 * 需要在spring容器中注册RestTemplate。
 */
@Component
public class RestTemplateRequestHandleimplements IRequestHandle{
  @Autowired
  RestTemplate rest;
  @Override
  publicObjecthandle(RestInfo restInfo, RequestInfo request){
    System.err.println("/n/n/thandle request, restInfo=" + restInfo);
    System.err.println("/thandle request, request=" + request);
    String url = extractUrl(restInfo, request);
    System.err.println("/thandle url:" + url);
    //TODO 目前只写了get请求,需要支持post等在这里增加
    //TODO 需要在这里增加异常处理,如登录失败,链接不上
    Object result = rest.getForObject(url, request.getReturnType());
    return result;
  }
  /**
   * 生成真实的url
   * 
   * @param restInfo
   * @param request
   * @return
   */
  privateStringextractUrl(RestInfo restInfo, RequestInfo request){
    String url = restInfo.getHost() + request.getUrl();
    if (request.getParams() == null) {
      return url;
    }
    Set<Entry<String, String>> entrySet = request.getParams().entrySet();
    String params = "";
    for (Iterator<Entry<String, String>> iterator = entrySet.iterator(); iterator.hasNext();) {
      Entry<String, String> entry = iterator.next();
      params += entry.getKey() + '=' + entry.getValue() + '&';
    }
    return url + '?' + params.substring(0, params.length() - 1);
  }
}
 
 @RunWith(SpringRunner.class)
@SpringBootTest(classes = MyRestUtilApplication.class)
public class MyRestUtilApplicationTests{
  @Autowired
  IRequestDemo demo;
  @Test
  public void test(){
    ResultBean get1 = demo.get1();
    System.out.println(get1);
  }
  @Test
  public void test2(){
    ResultBean get2 = demo.getWithKey("2332323");
    System.out.println(get2);
  }
}
 
   
 
测试正常,接口调用正常。
@ComponentScan("cn.xiaowenjie")
@SpringBootApplication
public class MyRestUtilApplication{
  public static void main(String[] args){
    SpringApplication.run(MyRestUtilApplication.class, args);
  }
  @Autowired(required = false)
  List<ClientHttpRequestInterceptor> interceptors;
  @Bean
  publicRestTemplaterestTemplate(){
    System.out.println("-------restTemplate-------");
    RestTemplate restTemplate = new RestTemplate();
    // 设置拦截器,用于http basic的认证等
    restTemplate.setInterceptors(interceptors);
    return restTemplate;
  }
}
 
 拦截器把认证信息放在头里面。
@Component
public class HttpBasicRequestInterceptorimplements ClientHttpRequestInterceptor{
  @Override
  publicClientHttpResponseintercept(HttpRequest request,byte[] body, ClientHttpRequestExecution execution)
      throws IOException {
    // TODO 需要得到当前用户
    System.out.println("---------需要得到当前用户,然后设置账号密码-----------");
    String plainCreds = "xiaowenjie:admin";
    byte[] plainCredsBytes = plainCreds.getBytes();
    byte[] base64CredsBytes = Base64.encodeBase64(plainCredsBytes);
    String headerValue = new String(base64CredsBytes);
    HttpRequestWrapper requestWrapper = new HttpRequestWrapper(request);
    requestWrapper.getHeaders().add("Authorization", "Basic " + headerValue);
    return execution.execute(requestWrapper, body);
  }
}
 
 源码在这里: xwjie/MyRestUtil** ,欢迎加星。框架代码在单独的 MyRestUtil/myrestutil/restutil 目录中,主要逻辑都在 RestUtilInit 上,代码非常精简,一看就明白,总共200行左右吧。
  
 
如果您有任何想法或问题需要讨论或交流,可进入交流区发表您的想法或问题。