导读:
前些时间写了很多编码习惯的帖子,今天写一点简单的技术贴。其实我个人觉得编码习惯是最主要的,比技术重要,但初学者还是喜欢看技术贴,今天就写一个小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行左右吧。
如果您有任何想法或问题需要讨论或交流,可进入交流区发表您的想法或问题。