在某些业务场景中,我们只需要业务代码中定义相应的接口或者相应的注解,并不需要实现对应的逻辑。

比如 mybatis和feign: 在 mybatis 中,我们只需要定义对应的mapper接口;在 feign 中,我们只需要定义对应业务系统中的接口即可。

那么在这种场景下,具体的业务逻辑时怎么执行的呢,其实原理都是动态代理。

我们这里不具体介绍动态代理,主要看一下它在springboot项目中的实际应用,下面我们模仿feign来实现一个调用三方接口的 httpclient。

一、定义注解

定义好注解,方便后面扫描使用了该注解的类和方法

package com.mysgk.blogdemo.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target; import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME; /**
* @author mysgk
* @link https://www.cnblogs.com/mysgk/p/15619895.html
* <p>
* 这里应该还有一个注解来标识方法,demo为了方便都使用该注解
*/
@Retention(RUNTIME)
@Target({TYPE, METHOD})
@Documented
public @interface MyHttpClient {
}

二、建立动态代理类

用于产生代理类并执行对应方法

package com.mysgk.blogdemo.proxy;

import org.springframework.beans.factory.FactoryBean;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; /**
* @author mysgk
* @link https://www.cnblogs.com/mysgk/p/15619895.html
*/
public class RibbonAopProxyFactory<T> implements FactoryBean<T>, InvocationHandler { private Class<T> interfaceClass; public Class<T> getInterfaceClass() {
return interfaceClass;
} public void setInterfaceClass(Class<T> interfaceClass) {
this.interfaceClass = interfaceClass;
} @Override
public T getObject() throws Exception {
final Class[] interfaces = {interfaceClass};
return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), interfaces, this);
} @Override
public Class<?> getObjectType() {
return interfaceClass;
} @Override
public boolean isSingleton() {
return true;
} /**
* 真正执行的方法,会被aop拦截
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return "invoke " + interfaceClass.getName() + "." + method.getName() + " , do anything ..";
}
}

三、注入spring容器

在项目启动时,扫描第一步定义的注解,生成该类的实现类,并将其注入到spring容器

package com.mysgk.blogdemo.start;

import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import com.mysgk.blogdemo.annotation.MyHttpClient;
import com.mysgk.blogdemo.proxy.RibbonAopProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.stereotype.Component;
import java.util.Set; /**
* @author mysgk
* @link https://www.cnblogs.com/mysgk/p/15619895.html
*/
@Component
public class ScanHttpClients implements BeanDefinitionRegistryPostProcessor { public void run(BeanDefinitionRegistry registry) { Set<Class<?>> scanPackage = ClassUtil.scanPackageByAnnotation("com.mysgk", MyHttpClient.class); for (Class<?> cls : scanPackage) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(cls);
GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();
definition.getPropertyValues().add("interfaceClass", definition.getBeanClassName());
definition.setBeanClass(RibbonAopProxyFactory.class);
definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
String beanName = StrUtil.removePreAndLowerFirst(cls.getSimpleName(), 0) + "RibbonClient";
registry.registerBeanDefinition(beanName, definition);
}
} @Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
run(registry);
} @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { }
}

四、编写拦截器

拦截动态代理生成的方法,实现我们真正的业务逻辑


package com.mysgk.blogdemo.aop; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate; /**
* @author mysgk
* @link https://www.cnblogs.com/mysgk/p/15619895.html
*/
@Component
@Aspect
public class InterceptAnnotation { @Autowired
RestTemplate ribbonLoadBalanced; @Pointcut("@annotation(com.mysgk.blogdemo.annotation.MyHttpClient)")
public void execute() { } @Around("execute()")
public Object interceptAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {
/**
* 此处省略 获取 url, httpMethod, requestEntity, responseType 等参数的处理过程
*/
ResponseEntity<?> exchange = ribbonLoadBalanced.exchange("http://www.baidu.com", HttpMethod.GET, HttpEntity.EMPTY, Object.class);
return exchange.getBody();
} }

五、创建客户端调用类

此类实现将远程接口当做本地方法调用

package com.mysgk.blogdemo.client;

import com.mysgk.blogdemo.annotation.MyHttpClient;
import org.springframework.web.bind.annotation.PostMapping; /**
* @author mysgk
* @link https://www.cnblogs.com/mysgk/p/15619895.html
*/
@MyHttpClient
public interface MyHttpClientTest { @MyHttpClient
@PostMapping(value = "test/t1")
Object test(String param); }

六、main方法测试

注入客户端进行调用

package com.mysgk.blogdemo;

import com.mysgk.blogdemo.client.MyHttpClientTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate; /**
* @author mysgk
* @link https://www.cnblogs.com/mysgk/p/15619895.html
*/
@SpringBootApplication
@RestController
public class Main { @Bean
RestTemplate restTemplate() {
return new RestTemplate();
} @Autowired
MyHttpClientTest myHttpClientTest; public static void main(String[] args) {
SpringApplication.run(Main.class, args);
} @GetMapping("/t1")
public Object t1() {
return myHttpClientTest.test("1");
}
}

七、启动项目

访问 127.0.0.1:8080/t1,得到如下结果

此时我们调用的是

myHttpClientTest.test

但是真正运行的是

RibbonAopProxyFactory.invoke

此时返回的应该是

invoke com.mysgk.blogdemo.client.MyHttpClientTest.test  , do anything ..

由于我们使用aop拦截了该方法,所以最终的结果是访问到了baidu

调用流程图

最后附上pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId>
<artifactId>bolgdemo</artifactId>
<version>1.0-SNAPSHOT</version> <dependencies> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.10</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.1.5.RELEASE</version>
<exclusions>
<exclusion>
<artifactId>httpclient</artifactId>
<groupId>org.apache.httpcomponents</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<artifactId>httpclient</artifactId>
<groupId>org.apache.httpcomponents</groupId>
<version>4.5.13</version>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins> </build>
</project>

SpringBoot 动态代理实现三方接口调用的更多相关文章

  1. 使用动态代理实现dao接口

    使用动态代理实现dao接口的实现类 MyBatis允许只声明一个dao接口,而无需写dao实现类的方式实现数据库操作.前提是必须保证Mapper文件中的<mapper>标签的namespa ...

  2. SpringBoot27 JDK动态代理详解、获取指定的类类型、动态注册Bean、接口调用框架

    1 JDK动态代理详解 静态代理.JDK动态代理.Cglib动态代理的简单实现方式和区别请参见我的另外一篇博文. 1.1 JDK代理的基本步骤 >通过实现InvocationHandler接口来 ...

  3. 基于JDK动态代理实现的接口链式调用(Fluent Interface)工具

    什么是链式接口(Fluent Interface) 根据wikipedia上的定义,Fluent interface是一种通过链式调用方法来完成方法的调用,其操作分为终结与中间操作两种.[1] 下面是 ...

  4. MyBatis总结三:使用动态代理实现dao接口

    由于我们上一篇实现MyBatis的增删改查的接口实现类的方法都是通过sqlsession调用方法,参数也都类似,所以我们使用动态代理的方式来完善这一点 MyBatis动态代理生成dao的步骤: 编写数 ...

  5. Springboot+Dubbo使用Zipkin进行接口调用链路追踪

    Zipkin介绍: Zipkin是一个分布式链路跟踪系统,可以采集时序数据来协助定位延迟等相关问题.数据可以存储在cassandra,MySQL,ES,mem中.分布式链路跟踪是个老话题,国内也有类似 ...

  6. MyBatis使用mapper动态代理实现DAO接口

    工具: mysql 5.5.62   IDEA 参考自:https://www.cnblogs.com/best/p/5688040.html 遇到的问题: 无法读取src/main/java下配置文 ...

  7. SpringBoot之RESTFul风格的接口调用(jQuery-Ajax)

    一.Get $.ajax({ type: "get", url: "url地址", async: true, dataType:"json" ...

  8. springboot rabbitmq消息同步用作接口调用

    1.引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId> ...

  9. 最简单的动态代理实例(spring基于接口代理的AOP原理)

    JDK的动态代理是基于接口的 package com.open.aop; public interface BusinessInterface {     public void processBus ...

随机推荐

  1. pycharm安装pika提示CondaHTTPError: HTTP 000 CONNECTION FAILED for url <https://repo.anaconda.com>

    1. 问题描述: pycharm安装第三方库时提示CondaHTTPError: HTTP 000 CONNECTION FAILED. 2. 错误原因:默认镜像源访问速度过慢,会导致超时从而导致更新 ...

  2. vue3.x异步组件

    在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块 vue2.x 曾经简单的异步组件 components: { AsyncComponent: () =& ...

  3. Coursera Deep Learning笔记 序列模型(一)循环序列模型[RNN GRU LSTM]

    参考1 参考2 参考3 1. 为什么选择序列模型 序列模型能够应用在许多领域,例如: 语音识别 音乐发生器 情感分类 DNA序列分析 机器翻译 视频动作识别 命名实体识别 这些序列模型都可以称作使用标 ...

  4. Java:并发笔记-05

    Java:并发笔记-05 说明:这是看了 bilibili 上 黑马程序员 的课程 java并发编程 后做的笔记 4. 共享模型之内存 本章内容 上一章讲解的 Monitor 主要关注的是访问共享变量 ...

  5. 第1次 Beta Scrum Meeting

    本次会议为Beta阶段第1次Scrum Meeting会议 会议概要 会议时间:2021年5月29日 会议地点:「腾讯会议」线上进行 会议时长:0.5小时 会议内容简介:本次会议为Beta阶段第1次会 ...

  6. [技术博客]在团队中使用Pull Request来管理代码

    在团队中使用Pull Request来管理代码 前言 在参加多人共同开发项目,且选用Git作为代码托管工具的时候,我们不免会遇到分支冲突.覆盖.合并等问题.显然,因为同一个仓库是属于大家的,所以每个人 ...

  7. 攻防世界 杂项 7.Aesop_secret

    打开发现是个gif,以为有个二维码扫一下就给flag,结果被欺骗.呜呜呜 好了,还是使用编辑器看一下吧 发现了好玩的,U2FsdGVkX19QwGkcgD0fTjZxgijRzQOGbCWALh4sR ...

  8. Less-5闯关失败

    进行第五关的通关还是用之前的方式进行测试以及判断是什么类型的注入.通过判断我们不难发现是字符型注入.但是出了问题,我们会发现按照原来的步骤进行注入都会返回"You are in " ...

  9. Vue:Vue的介绍以及组件剖析

    介绍 现在,随着基于JavaScript的单页应用程序(SPA)和服务器端渲染(SSR)的兴起,可以用JavaScript编写整个前端应用程序,并整洁地管理和维护该应用程序的前端代码.诸如Angula ...

  10. hdu 5083 Instruction (稍比较复杂的模拟题)

    题意: 二进制指令转汇编指令,汇编指令转二进制指令. 思路: 额,条理分好,想全,思维不能乱. 代码: int findyu(char yu[50],char c){ int l=strlen(yu) ...