FeignClient原理解析,100行コードはfeign機能を実現し,mybatisのmapper,dubbo,feignは原理シミュレーションを実現する.Springスキャンカスタム注釈原理.Javassist動的エージェントの原理を実現


この文章はspringの拡張メカニズムを理解することに重点を置いている.拡張機構を理解した.今後はspringを独自に柔軟に拡張できます.
  • 背景紹介:Feginの機能には一定の認識が必要で、簡単に言えばFeginが負う責任はサービスAにサービスBのインタフェースを呼び出すことであり、例えばサービスBにcontrollerが書かれており、サービスAがこのcontrollerを呼び出すにはfeginを通じて直接呼び出すことができ、大部分の使用シーンはSpring CloudでRPC呼び出しを行う際に使用される.
  • 実際の現象:feign、またはmybatisなどのフレームワークを使用する場合、mybaitsのmapper、feignのclientなどのインタフェースを先に定義する特徴があります.しかし、springはbeanを管理してくれるときも、具体的なクラスを管理するしかなく、インタフェースを管理することはできません.インタフェースでも、管理するときも、インタフェースの実現を探しに行くだけです.実現していないか、複数の実現がある場合は、エラーを報告します.もちろん、注入方式で、どの具体的なオブジェクトを管理するかを指定することができます.これはspringの注入方式で、後で単独で説明することができます.

  • 以上の現象に基づいて、いくつかの問題を考える必要があります.
  • これらのフレームワークで使用されるインタフェースはどのようにspringによって管理されていますか?
  • @Mapperや@RestClientなどの注釈、springはどのようにこれらの注釈を認識していますか?私たちは自分で注釈を書いてspringをスキャンすることができますか?
  • springにbeanを管理してもらいたいのですが、@Compentや@Beanなどを使う以外に、spring自体がサポートしているのも、私たちがよく使う注釈のほかに、私たちのbeanを管理する方法はありますか?
  • .Feignを使う時、私達はすべて1つの@EnableFeignClientsを通じて(通って)これはまたどのように帰るのですか?

  • 上記の問題を持って,コードによってfeignを一歩一歩実現し,springの真実を一歩一歩解く.
  • 大神はみな模倣から始まった.では、私たちも模倣フレームワークから始めます.
  • フレームワークを真似て、@EnableFeignClients注記も書きます.@EnableSongFeignClient
  • と呼ばれています.
    	@Target(ElementType.TYPE)
    	@Retention(RetentionPolicy.RUNTIME)
    	@Documented
    	@Import(RestClientTest.class)
    	public static @interface EnableSongFeignClient {
         }
    
  • 上記の注釈があり、springBootのように、この注釈を起動クラスの上に書くこともできます.
  • 原理は簡単ですが、なぜこれを使って私たちのFeginを開くことができますか?

  • @Import(RestClientTest.class)に、SongFeignClientTestをインポートします.classというクラスは、このように書かれているだけで、springが起動したときにSongFeignClientTestというクラスを実行します.
  • では、このクラスは何をしましたか.
  • なぜ起動するときにこのクラスを実行するのか、このクラスは私たちがFeignを実現することと何の関係があるのでしょうか.
  • 上記のようにSongFeignClientTestRegistrarを見てみましょう.classという類はまた何をしましたか?

  • 問題を持って、私たちは下を見続けますか?
    public class SongFeignClientTestRegistrar implements ImportBeanDefinitionRegistrar{
         
    @Override
    	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
         
    		
    	}
    }
    

    ImportBeanDefinitionRegistrarインタフェースで定義されたクラスは、その複写方法が{@code@Import}方式で注入された場合にのみ実行されます.
  • このクラスを見て、はっと悟った.このクラスはImportBeanDefinitionRegistrarという方法も実現した.クラス名を通じて、springにBDを登録することだ.(spring管理bean、最初のステップはbeanをBDにスキャンすることです)だから、この方法はspringにbeanを管理してもらうためのもので、BDを登録すればspringは自然にbeanを作成してくれます.では、彼はここでどんなオブジェクトを管理してくれますか.私たちはまだオブジェクトがありませんか.?
  • では、もう一つの注釈を書きます:@FeignClient、@SongFeignClient
  • と呼ばれています.
    @Target(ElementType.TYPE)
    	@Retention(RetentionPolicy.RUNTIME)
    	@Documented
    	public static @interface SongFeignClient {
         
    		string baseUrl()
    	}
    }
    

    私たちが実現しなければならない機能は、@SongFeignClientという注釈を加えたインタフェースで、インタフェースの中の方法は、メソッドが指定したサービスを呼び出すのに役立ちます.@Mappingのように、この注釈を加えると、mybatisはsqlを実行します.
    考えを整理する:
  • @SongFeignClient注記が追加されたインタフェースが見つかりました.
  • インタフェースを動的に実現し、インタフェースの中の方法を書き換える.方法の内容はRPC呼び出しを行う具体的な論理である.
  • インタフェースで実装されたオブジェクトをspring管理に渡す.

  • 考え方に沿って一歩一歩進みます.
  • はまずインタフェースの動的実装をオブジェクトにし,springをどのように管理するかについては,実装をオブジェクトにすると述べている.思考:インタフェースをオブジェクトにするように、javaを直接書くことで実現することはできません.できる技術はたくさんありますが、Feignなどの関連技術のソースコードはJavassistを通じて実現されています.jdkダイナミックエージェントを使って実現することができます.その後、Javassistを使う方法も文章の最後に書きます.
  • 
    	public static class SongClientFactoryBean implements FactoryBean<Object> {
         
    		
    		private final Class<?> type ;
    		private final String baseUrl ;
    		
    		public RestClientFactoryBean(Class<?> type, String baseUrl) {
         
    			this.type = type;
    			this.baseUrl = baseUrl;
    		}
    
    		@Override
    		public Object getObject() throws Exception {
         
    			return Proxy.newProxyInstance(
    					Thread.currentThread().getContextClassLoader(),
    					new Class[]{
         type},
    					new SongClientImpl(baseUrl));
    		}
    
    		@Override
    		public Class<?> getObjectType() {
         
    			return type;
    		}
    		
    	}
    
  • FactoryBeanは、beanでもあり、このbeanが何であるかは、そのメソッドのgetObject()メソッドの実装によって返され、何であるかがbeanである.beanがあれば、springにbeanを渡す方法を考えなければならない.
  • Proxy.新ProxyInstance(params 1,params 2,params 3)この方法はjdkダイナミックエージェントがダイナミッククラスを生成する方法です.私のここで簡単なことは、次の3つのパラメータ分がそれぞれどういう意味を表していますか:
  • Thread.currentThread().getContextClassLoader():jdkダイナミックエージェントが生成するクラスがどこに配置されるかを示します.
  • new Class[]{type}:jdkダイナミックエージェントはインタフェースに基づいて実現され、javaはマルチ継承されるため、このパラメータは実装が必要なインタフェースに伝達される.複数の継承があるため、複数のインタフェースがある可能性があります.ここでは配列
  • を受け入れます.
  • InvocationHandlerの3番目のパラメータは、インタフェースの方法であり、具体的な実装内容である.私たちのインタフェースを実現した後にしなければならないことを書いています.メソッド名は森羅万象なので統一しなければならないが,統一する方法はInvocationHandlerのクラスを実現しなければ伝わらない.ここでInvocationHandlerというインタフェースを実現するオブジェクトはSongClientImplです.具体的な内容は以下の通りです.
  • public static class SongClientImpl implements InvocationHandler{
          
    		  private final RestTemplate restTemplate = new RestTemplate();
    		  private final String baseUrl ;
    		  public RestClientImpl(String baseUrl){
          
    		 		 this.baseUrl = baseUrl;
    		}
    		@Override
    		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          
    			GetMapping get = AnnotationUtils.findAnnotation(method, GetMapping.class);
    			if (get != null) {
          
    				String url = get.value()[0];
    				//    ribbon,        
    				return restTemplate.getForObject(baseUrl + url, method.getReturnType());
    			}
    			PostMapping post = AnnotationUtils.findAnnotation(method, PostMapping.class);
    			if (post != null) {
          
    				String url = post.value()[0];
    				return restTemplate.postForObject(baseUrl + url, args[0], method.getReturnType());
    			}
    			return null;
    	}
    
  • 上記の操作により、オブジェクトを作成することができ、FactoryBeanを実現することによって生成するオブジェクトをspringのbeanにすることができる.
  • 最後に考えなければなりません.このbeanをspring管理に渡すと、@EnableSongFeignClientで最初に登録されたSongFeignClientTestRegistrarクラスを使います.
  • public class SongFeignClientTestRegistrar implements ImportBeanDefinitionRegistrar{
         
    	/*
    	*
    	*   spring        ,        ,      register   spring
    	*      :
    	* 	1.     ,             。
    	* 	2.     ,                   RestClientFactoryBean            
    	*   3.          spring  
    	*
    	* */
    	@Override
    	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
         
    		//    
    		ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false){
         
    			//        
    			@Override
    			protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
         
    				if (beanDefinition.getMetadata().isInterface()) {
         
    					return true ;
    				}
    				return super.isCandidateComponent(beanDefinition);
    			}
    		};
    		//    RestClient       
    		scanner.addIncludeFilter(new AnnotationTypeFilter(RestClient.class));
    		
    		String scanPackage;
    		try {
         
    			//       
    			scanPackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
    		} catch (ClassNotFoundException e1) {
         
    			throw new RuntimeException(e1);
    		}
    		
    		for (BeanDefinition b : scanner.findCandidateComponents(scanPackage)) {
         
    			AnnotatedBeanDefinition abd = (AnnotatedBeanDefinition) b;
    			//   RestClient   
    			String baseUrl = (String) abd.getMetadata().getAnnotationAttributes(RestClient.class.getName(),true).get("baseUrl");
    			try {
         
    				
    				//     bean
    				registry.registerBeanDefinition("rest-client--" + b.getBeanClassName(),
    						BeanDefinitionBuilder.genericBeanDefinition(RestClientFactoryBean.class)
    						.addConstructorArgValue(Class.forName(b.getBeanClassName()))
    						.addConstructorArgValue(baseUrl)
    						.getBeanDefinition());
    			} catch (Exception e) {
         
    				throw new RuntimeException(e);
    			}
    		}
    	}
    }
    

    考えを整理する.
  • は、グラムをスキャンすることによって、私たちが追加した注釈をスキャンします.
  • がスキャンされた後、スキャンに追加された注釈のインタフェースを上のRestClientFactoryBeanに渡し、エージェントオブジェクト
  • の生成を手伝ってもらう.
  • 生成されたエージェントオブジェクトをspring管理
  • に渡す.
    これで基本原理のfeignを完成しました.
    スプリングについては、カスタム注釈の原理をスキャンすると
  • C r a s P h t h S a n n n i n g CandidateComponentProviderクラスはspringでクラス/パッケージスキャンを行うためのツールである.
  • は、jarなどのclasspathの下にあるすべてのパケットを含む.あなたが普段使っているComponentScan注釈はこのクラスで処理されています.
  • キーは、動的に解析クラスをロードするのではなく、asmによってクラスを解析します.これは、クラスが初期化されないことを意味します.
  • で、指定した注釈のクラスのみを返すなど、スキャンルールをカスタマイズできます.
  • public class ClassPathScanningCandidateComponentProvider    {
         
    	
    	public static void main(String[] args) {
         
    		// new     false,     spring       ,                true。
    		ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false){
         
    			//            ,            
    			//     ,             ,       ,            
    			@Override
    			protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
         
    				if (beanDefinition.getMetadata().isInterface()) {
         
    					return true ;
    				}
    				return super.isCandidateComponent(beanDefinition);
    			}
    		};
    		//      ,          
    		scanner.addIncludeFilter(new AnnotationTypeFilter(RestClient.class));
    		scanner.findCandidateComponents("com.dragonsoft").forEach(System.out :: println);
    		
    	}
    
    

    彩卵
    どのようにここを見て、そんなに長い間Javassistを添付して動的エージェントの原理を実現します
    public class JavassistTest {
         
    	
    	public static void main(String[] args) throws Exception{
         
    		//       ClassPool,        ,      CtClass      ,       
    		ClassPool cp = new ClassPool(true);
    		CtClass cls = cp.makeClass("com.gframework.samplecode.BB");
    		cls.addInterface(cp.get(AA.class.getName()));
    		CtMethod f = new CtMethod(CtClass.voidType, "fun", null, cls);
    		f.setModifiers(Modifier.PUBLIC);
    		f.setBody("{"
    				+ "System.out.println(\"Hello\");"
    				+ "System.out.println(\"World\");"
    				+ "}");
    		cls.addMethod(f);
    		
    		CtMethod f2 = new CtMethod(CtClass.voidType, "fun2", new CtClass[]{
         cp.get("java.lang.String")}, cls);
    		f2.setModifiers(Modifier.PUBLIC);
    		f2.setBody("{"
    				+ "String ddd = $1;"
    				+ "System.out.println(\"Hello22\" + ddd + new java.util.Date());"
    				+ "System.out.println(\"World22\");"
    				+ "}");
    		cls.addMethod(f2);
    		
    		
    		AA a = (AA)cls.toClass().newInstance();
    		a.fun();
    		a.fun2("AAAAA");
    	}
    
    }
    interface AA{
         
    	public void fun();
    	public void fun2(String str);
    }
    
  • CtClassが注目すべき方法:
  • freeze:変更不可にするクラスをフリーズします.
  • isFrozen:クラスが凍結されたかどうかを判断します.
  • prune:クラスの不要なプロパティを削除し、メモリの消費量を削減します.このメソッドを呼び出すと、多くのメソッドが正常に使用できなくなり、慎重に使用できません.
  • defrost:クラスを解凍し、変更できるようにします.クラスがdefrostされることを事前に知っていた場合、pruneメソッドの呼び出しは禁止されます.
  • detach:classをClassPoolから削除します.
  • writeFile:CtClassにより生成する.classファイル;
  • toClass:CtClassをクラスローダでロードします.

  • CtMethodのいくつかの重要な方法:
  • insertBefore:メソッドの開始位置にコードを挿入します.
  • insterAfter:exceptionに遭遇しない限り、メソッドのすべてのreturn文の前にコードを挿入して、文が実行されることを保証します.
  • insertAt:指定された場所にコードを挿入します.
  • setBody:メソッドの内容を書き込むコードに設定し、メソッドがabstractによって修飾されると、この修飾子が除去される.
  • make:新しいメソッドを作成します.

  • ***干物だらけで、自分がはっきり言っているかどうか分かりませんが、皆さんが何か収穫があることを望んでいます.次回の更新を楽しみにしていますpice