前言
最近開發(fā)cloud項目,里面涉及到服務(wù)間調(diào)用,最后使用的openfeign解決的,于是對于openfeign的底層原理有些興趣了,提前透露一下底層無非就是使用一些http調(diào)用的工具幫助我們實現(xiàn)了請求調(diào)用
Openfeign實現(xiàn)思路
前期準備
基本依賴項
- 首先創(chuàng)建一個springboot項目
- 有一個發(fā)送請求的工具這里使用的ribbon
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.9.RELEASE</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
開始實現(xiàn)
自定義注解
創(chuàng)建兩個自定義注解,分別用于在啟動類和接口上添加
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@Import(OpenInstantiationAwareBeanPostProcessor.class)//這個類重點注意
public @interface EnableRemoteClient {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface CustomProxy {
String servie();
String address();
}
自定義代理類
代理類中有一個RestTemplate 用于發(fā)送請求
public class CustomProxyHandler implements InvocationHandler {
RestTemplate restTemplate=new RestTemplate();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//首先獲取對象
Class<?> declaringClass = method.getDeclaringClass();
//判斷當前的被代理對象是否使用了自定的注解
if(declaringClass.isAnnotationPresent(CustomProxy.class)){
CustomProxy annotation = declaringClass.getAnnotation(CustomProxy.class);
String servie = annotation.servie();
String address = annotation.address();
String url=address+servie;
RequestMapping annotation1 = method.getAnnotation(RequestMapping.class);
url=url+annotation1.value()[0];
//判斷請求方法是否入?yún)?/span>
if(args!=null&&args.length!=0){
return restTemplate.getForObject(url,method.getReturnType(),args);
}else {
return restTemplate.getForObject(url,method.getReturnType());
}
}else{
return null;
}
}
}
定義創(chuàng)建代理對象的工廠
用于創(chuàng)建代理對象使用,
public class ProxyFactory {
/**
* @description: 創(chuàng)建具體的代理對象
* @author:
* @date: 2023/9/1 17:30
* @param: [targetInterface]
* @return: T
**/
public static <T> T createProxy(Class<T> targetInterface) {
return (T) Proxy.newProxyInstance(
targetInterface.getClassLoader(),
new Class[]{targetInterface},
new CustomProxyHandler()
);
}
}
InstantiationAwareBeanPostProcessor實現(xiàn)bean的注入
InstantiationAwareBeanPostProcessor
是 Spring 框架提供的一個擴展接口,它在 Spring 容器實例化 bean 之前和之后對 bean 進行處理。其主要功能如下:
-
實例化前置處理(Before Instantiation):在 Spring 容器實例化 bean 之前,可以通過這個接口來自定義 bean 的實例化方式。可以在此處進行一些特殊的準備工作,比如使用自定義的實例化邏輯或者切入點的選擇。
-
實例化后置處理(After Instantiation):在 Spring 容器實例化 bean 之后,可以通過這個接口對實例化后的 bean 進行處理。可以在此處做一些初始化的操作,比如對屬性進行賦值、調(diào)用初始化方法等。
-
屬性設(shè)置前置處理(Before Property Set):在 Spring 容器對 bean 的屬性進行設(shè)置之前,可以通過這個接口來自定義屬性的設(shè)置方式。可以在此處對屬性進行修改或者校驗操作。
-
屬性設(shè)置后置處理(After Property Set):在 Spring 容器對 bean 的屬性進行設(shè)置之后,可以通過這個接口對屬性設(shè)置后的 bean 進行處理??梢栽诖颂幾鲆恍傩栽O(shè)置后的額外操作。
-
初始化前置處理(Before Initialization):在 Spring 容器對 bean 進行初始化之前,可以通過這個接口來自定義初始化的方式??梢栽诖颂幪砑右恍╊~外的初始化邏輯。
-
初始化后置處理(After Initialization):在 Spring 容器對 bean 進行初始化之后,可以通過這個接口對初始化后的 bean 進行處理??梢栽诖颂幾鲆恍┏跏蓟蟮念~外操作。
通過實現(xiàn) InstantiationAwareBeanPostProcessor
接口,并重寫其中的方法,可以在 Spring 容器實例化和初始化 bean 的各個階段進行自定義處理,從而靈活地對 bean 進行定制化的操作。
OpenInstantiationAwareBeanPostProcessor 自定義
public class OpenInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
/**
* 實例化前,后面的方法可以不用看了
* @param beanClass
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
log.info("正在為:{}生成代理對象,被代理的類為:{}",beanName,beanClass.getName());
if(!beanClass.isAnnotationPresent(CustomProxy.class)){
return null;
}
//動態(tài)代理里面需要實現(xiàn)的方法,本文采用的是jdk動態(tài)代理
//返回代理對象
Object object = ProxyFactory.createProxy(beanClass);
return object;
}
/**
* 實例化后
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return true;
}
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
return pvs;
}
/**
* 初始化錢
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
/**
* 初始化后
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
}
feign接口
這里的servie沒有參數(shù)是因為我的另一個服務(wù)中沒有配置context-path,如果你們自己的被調(diào)用的那個demo中有配置一定要加上哦
@CustomProxy(servie = "",address = "http://localhost:8082/")
public interface MyService {
@RequestMapping(value = "/QuerySubselect",method = RequestMethod.GET)
String test();
}
啟動類
@SpringBootApplication
@EnableRemoteClient
@Import({MyService.class})//接口注冊,注意接口上面加@Se
public class OpenfeignDemoApplication {
public static void main(String[] args) {
SpringApplication.run(OpenfeignDemoApplication.class, args);
}
}
小結(jié)
這個版本的實現(xiàn)目前spring還不能識別我添加了注解的這個接口,需要在啟動類中使用@Import
注解手動配置,所以還不完善,不過后期會繼續(xù)完善的,目前博主也在學習這個,這個思路就是基于我們正常的發(fā)送請求,然后有一個人幫助我們?nèi)グl(fā)送請求這個思路。
踩坑記錄
@Import
將接口 MyService
標識為 @Import
的目的是為了在啟動類中將該接口的實現(xiàn)類(動態(tài)代理對象)注冊到 Spring 容器中。
當你使用 @Import({MyService.class})
注解時,Spring 在啟動時會掃描被注解的類,并根據(jù)其類型進行相應的處理。在這種情況下,MyService
類型是一個接口,而不是一個具體的類。
由于接口無法直接實例化,因此你需要在某處提供對 MyService
接口的實現(xiàn)類的創(chuàng)建邏輯。通常情況下,使用動態(tài)代理是一種常見的方式。
動態(tài)代理是一種在運行時生成代理類的機制,可以在不修改原始接口源代碼的情況下,通過代理類來增強接口的功能。你可以編寫一個動態(tài)代理類,實現(xiàn) MyService
接口,并在動態(tài)代理類中實現(xiàn)你所需的增強邏輯。
然后,在啟動類上使用 @Import
注解,并傳入該動態(tài)代理類的類型,告訴 Spring 在啟動時需要將該動態(tài)代理類注冊到容器中。
這樣,當其他組件需要使用 MyService
接口時,Spring 容器會從容器中獲取該接口的實例,而實際上獲得的是動態(tài)代理對象,該代理對象會在實際調(diào)用接口方法時根據(jù)你的增強邏輯進行處理。
- 因為openfeign的標注的接口沒有實現(xiàn)類所以需要這個
@Import
注解幫助我們告訴spring容器可以去容器中去這個類型的代理對象,目前還沒有想到其他的方式不用使用@Import
注解聲明的。后續(xù)再想想辦法
@Component和@Configuration區(qū)別是什么
@Configuration
注解不能直接添加到接口上,因為 @Configuration
注解是用于標識一個類為配置類的注解,它主要用于定義和配置 Bean 對象以及其他的配置信息。
一開始使用這個注解導致使用了自定義注解的接口一直掃描不到,后來修改為@Component
注解后就可以了。@Component
是 Spring Framework 中的一個核心注解之一,用于將一個普通的 Java 類標識為一個可以被 Spring 自動掃描并管理的組件。
具體來說,@Component
注解可以應用于以下場景:
-
Bean 的自動掃描和注冊:通過在類上添加
@Component
注解,Spring 容器會自動掃描并將該類作為 Bean 進行注冊,使得我們可以通過依賴注入等方式方便地使用該類的實例。 -
類型指定的自動裝配:當需要進行自動裝配時,Spring 容器會檢測所有被
@Component
注解標記的類,并根據(jù)類型進行自動裝配。
@Component
注解是一個通用的注解,如果對應的類有更具體的角色或作用,還可以使用其他派生注解,例如:
-
@Controller
:標識一個類是控制器(Controller)組件,用于接收和處理用戶請求。 -
@Service
:標識一個類是服務(wù)(Service)組件,用于封裝業(yè)務(wù)邏輯。 -
@Repository
:標識一個類是數(shù)據(jù)訪問倉庫(Repository)組件,用于訪問持久化數(shù)據(jù)。
這些派生注解都是基于 @Component
注解的,繼承了 @Component
的功能,并且更明確地表示了對應類的角色和用途。文章來源:http://www.zghlxwxcb.cn/news/detail-688762.html
總結(jié)
對于這次去實現(xiàn)這么一個框架的小demo自己思考了一天,不能說很長時間吧,主要是因為對于spring中提供的一些擴展機制還不是很了解,但是我相信它一定提供了能夠解決我上面問題的一些api的,只是我還沒有發(fā)現(xiàn)而已。對于這些技術(shù)的應用底層原理還是很簡單的,可以發(fā)現(xiàn)無非就是沒讓你做的事情有人幫你做了這么一個過程,只不過這個過程被人封裝起來以后就是一個技術(shù)或者一個框架了。文章來源地址http://www.zghlxwxcb.cn/news/detail-688762.html
到了這里,關(guān)于手寫Openfeign實現(xiàn)原理——極簡版的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!