像mybatis一樣,Spring啟動時為接口創建代理對象並自動注入


有些時候,我們需要為一些接口創建代理對象,並放入Spring的IOC容器中,比如,當我們需要構建一個RPC框架客戶端程序時,客戶端肯定只有服務的接口,並沒有具體的實現,實現在遠程服務器,這個時候,我們就可以為這些服務接口創建代理對象,並將代理對象放入IOC容器中,當我們需要調用服務時,通過接口請求服務,最終由代理對象發起網絡請求,將服務請求發送到遠程服務器,遠程服務器執行后,再將結果返回到客戶端,代理對象收到遠程執行結果后,最終將執行結果返回到服務調用者。

第一種方式:通過Spring給我們提供的factoryBean接口手工注冊

兩個測試接口:

  1. package com.mtl.itf;
  2.  
  3. /**
  4. * 說明:用戶服務測試
  5. *
  6. * @作者 莫天龍
  7. * @時間 2019/04/30 10:00
  8. */
  9. public interface UserService {
  10. public void save(String user);
  11. }
  1. package com.mtl.itf;
  2.  
  3. /**
  4. * 說明:測試服務接口
  5. *
  6. * @作者 莫天龍
  7. * @時間 2019/04/29 17:48
  8. */
  9. public interface Service {
  10. public void test(String s);
  11. }

factoryBean類:

  1. package com.mtl.interfaceProxy;
  2.  
  3. import org.springframework.beans.factory.FactoryBean;
  4.  
  5. import java.lang.reflect.InvocationHandler;
  6. import java.lang.reflect.Method;
  7. import java.lang.reflect.Proxy;
  8. import java.util.Arrays;
  9.  
  10. /**
  11. * 說明:代理對象的FactoryBean,繼承至Spring FactoryBean,通過調用getBean獲取代理對象
  12. *
  13. * @作者 莫天龍
  14. * @時間 2019/04/29 17:33
  15. */
  16. public class ProxyFactoryBean<T> implements FactoryBean {
  17. //被代理的接口Class對象
  18. private Class<T> interfaceClass;
  19.  
  20. public ProxyFactoryBean(Class<T> interfaceClass) {
  21. this.interfaceClass = interfaceClass;
  22. }
  23.  
  24.  
  25. @Override
  26. public T getObject() throws Exception {
  27. //通過JDK動態代理創建代理類
  28. return (T)Proxy.newProxyInstance(
  29. interfaceClass.getClassLoader(), new Class[]{interfaceClass},
  30. new InvocationHandler() {
  31. @Override
  32. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  33. //實現業務邏輯,比如發起網絡連接,執行遠程調用,獲取到結果,並返回
  34. System.out.println(method.getName()+ " method invoked ! param: "+ Arrays.toString(args));
  35. return null;
  36. }
  37. });
  38. }
  39.  
  40. @Override
  41. public Class<?> getObjectType() {
  42. return interfaceClass;
  43. }
  44. }

xml配置信息:

  1. <bean id= "service" class="com.mtl.interfaceProxy.ProxyFactoryBean">
  2. <constructor-arg name= "interfaceClass" type="java.lang.Class" value="com.mtl.itf.Service"/>
  3. </bean>
  4. <bean id= "userService" class="com.mtl.interfaceProxy.ProxyFactoryBean">
  5. <constructor-arg name= "interfaceClass" type="java.lang.Class" value="com.mtl.itf.UserService"/>
  6. </bean>

測試類:

  1. public static void main(String[] args){
  2. ClassPathXmlApplicationContext applicationContext= new ClassPathXmlApplicationContext("bean*.xml");
  3. Service bean = applicationContext.getBean(Service.class);
  4. bean.test(" 222");
  5. UserService userService = applicationContext.getBean(UserService.class);
  6. userService.save("user");
  7. applicationContext.close();
  8. }

測試結果:

如期的達到了目的,但是這樣配置,有點麻煩,因為沒一個接口都需要在spring的配置文件里面配置一下,所以我們想mybatis也是為接口創建了代理對象,但是我們並沒有把每個Mapper接口配置到xml中,而是配置了一個basePackge(包名),就可以為包名下的接口創建代理對象,這個是怎么實現的呢?

先來認識一下Spring為我們提供的幾個類和接口

org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor

根據文檔說明:允許在常規的BeanFactoryPostProcessor檢測開始之前注冊更多的bean定義。其中有個方法postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry),文檔說明:在應用程序上下文的標准初始化之后修改其內部bean定義注冊表。所有常規bean定義都已加載,但還沒有實例化bean。這允許在下一個后處理階段開始之前添加更多的bean定義。說明BeanDefinitionRegistryPostProcessor這個接口對應的實現類,通過postProcessBeanDefinitionRegistry這個方法來實現在容器標准初始化之后可以添加更多的bean定義,通過查看原代碼,BeanDefinitionRegistry對象有如下常用的方法:

void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);//注冊一個bean定義

void removeBeanDefinition(String beanName);//刪除一個已有的bean定義

還有一些其他方法,比如查看獲取某個bean的定義,查看某個beanName是否已經定義,查看spring IOC容器中一共定義了多個個bean(也就是Spring IOC容器中有多少個實例)等等,讀者可以查看源代碼查看了解其他方法,通過查看這些方法,似乎了解到我們可以在spring 容器啟動中,可以管理這些bean。我們發現注冊一個bean定義,需要一個BeanDefinition對象,我們來看一下這個對象是什么。

org.springframework.beans.factory.config.BeanDefinition

查看文檔說明:bean定義描述了一個bean實例,它具有屬性值、構造函數參數值和由具體實現提供的進一步信息。

所以,自定義注冊一個bean,就是構建一個BeanDefinition對象,當然spring也為我們提供了創建方法:

通過org.springframework.beans.factory.support.BeanDefinitionBuilder類

上代碼,測試一下吧!

有一個Person類:

  1. package com.mtl.beanDefine.test;
  2.  
  3. /**
  4. * 說明:
  5. *
  6. * @作者 莫天龍
  7. * @時間 2019/04/30 9:30
  8. */
  9. public class Person {
  10. private String name;
  11. private int age;
  12.  
  13. public String getName() {
  14. return name;
  15. }
  16.  
  17. public void setName(String name) {
  18. this.name = name;
  19. }
  20.  
  21. public int getAge() {
  22. return age;
  23. }
  24.  
  25. public void setAge(int age) {
  26. this.age = age;
  27. }
  28.  
  29. @Override
  30. public String toString() {
  31. return "Person{" +
  32. "name='" + name + '\'' +
  33. ", age=" + age +
  34. '}';
  35. }
  36. }

自己實現一個BeanDefinitionRegistryPostProcessor

  1. package com.mtl.beanDefine.test;
  2.  
  3. import org.springframework.beans.BeansException;
  4. import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  5. import org.springframework.beans.factory.support.*;
  6.  
  7. /**
  8. * 說明:測試BeanDefinitionRegistryPostProcessor接口,自定義添加bean到容器
  9. *
  10. * @作者 莫天龍
  11. * @時間 2019/04/30 9:29
  12. */
  13. public class BeanDefineTest implements BeanDefinitionRegistryPostProcessor {
  14. @Override
  15. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
  16. //創建beanDefinition構建器
  17. BeanDefinitionBuilder beanDefinitionBuilder=BeanDefinitionBuilder.genericBeanDefinition(Person.class);
  18. //獲取到創建beanDefinition
  19. GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionBuilder.getBeanDefinition();
  20. //通過此方法可以將age屬性的值設置為 23,就像xml配置中的property標簽
  21. beanDefinition.getPropertyValues().add( "age", new Integer(23));
  22. beanDefinition.getPropertyValues().add( "name", "Mike");
  23. //設置bean的主動注入類型為根據type注入
  24. beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
  25. //最后調用注入方法
  26. registry.registerBeanDefinition( "person", beanDefinition);
  27. }
  28.  
  29. @Override
  30. public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  31. //容器后處理器,暫不處理
  32. }
  33. }

將我們實現的BeanDefinitionRegistryPostProcessor類配置到spring中,這樣spring在容器啟動中會自動執行該類的postProcessBeanDefinitionRegistry方法,就像FactroyBean、bean后處理器、容器后處理器一樣。

注意,只配置了這個類,不配置Person類。

<bean class="com.mtl.beanDefine.test.BeanDefineTest"/>

編寫測試類

  1. public class MainTest {
  2. public static void main(String[] args){
  3. ClassPathXmlApplicationContext applicationContext= new ClassPathXmlApplicationContext("test.xml");
  4. Person per = applicationContext.getBean(Person.class);
  5. System.out.println(per);
  6. applicationContext.close();
  7. }
  8. }

測試結果:

通過測試結果發現,我們參與了容器啟動過程,並將Person對象注入到了Spring中。

所以,我們可以通過這個方法動態的將接口生成的代理對象注入到Spring中。

第二種方法:通過BeanDefinitionRegistryPostProcessor接口動態注入。

現在還有一個問題,Mybatis是如何掃描到某個包下的接口的呢?

認識一下這個類:org.springframework.context.annotation.ClassPathBeanDefinitionScanner

文檔說明:一個bean定義掃描器,它檢測類路徑上的bean候選項,並向給定的注冊表(BeanFactory或ApplicationContext)注冊相應的bean定義。通過可配置的類型過濾器檢測候選類。默認過濾器包括用Spring的@Component、@Repository、@Service或@Controller原型注解的類。

重寫一下這個類:

  1. package com.mtl.interfaceProxy;
  2.  
  3. import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
  4. import org.springframework.beans.factory.config.BeanDefinitionHolder;
  5. import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  6. import org.springframework.beans.factory.support.GenericBeanDefinition;
  7. import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
  8. import org.springframework.core.type.AnnotationMetadata;
  9. import org.springframework.core.type.classreading.MetadataReader;
  10. import org.springframework.core.type.classreading.MetadataReaderFactory;
  11. import org.springframework.core.type.filter.TypeFilter;
  12.  
  13. import java.io.IOException;
  14. import java.util.Set;
  15.  
  16. /**
  17. * 說明:用於掃描給定包名下的接口
  18. *
  19. * @作者 莫天龍
  20. * @時間 2019/04/29 16:34
  21. */
  22. public class InterfaceScanner extends ClassPathBeanDefinitionScanner {
  23. public InterfaceScanner(BeanDefinitionRegistry registry) {
  24. //registry是Spring的Bean注冊中心
  25. // false表示不使用ClassPathBeanDefinitionScanner默認的TypeFilter
  26. // 默認的TypeFilter只會掃描帶有@Service,@Controller,@Repository,@Component注解的類
  27. super(registry,false);
  28. }
  29.  
  30. //調用父類執行掃描
  31. @Override
  32. protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
  33. addFilter();
  34. Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
  35. if (beanDefinitionHolders.isEmpty()){
  36. System.err.println( "No Interface Found!");
  37. } else{
  38. //創建代理對象
  39. createBeanDefinition(beanDefinitionHolders);
  40. }
  41. return beanDefinitionHolders;
  42. }
  43.  
  44. //只掃描頂級接口
  45. @Override
  46. protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
  47. AnnotationMetadata metadata = beanDefinition.getMetadata();
  48. return metadata.isInterface()&&metadata.isIndependent();
  49. }
  50.  
  51. /**
  52. * 掃描所有類
  53. */
  54. private void addFilter(){
  55. addIncludeFilter( new TypeFilter() {
  56. @Override
  57. public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
  58. return true;
  59. }
  60. });
  61. }
  62.  
  63. /**
  64. * 為掃描到的接口創建代理對象
  65. * @param beanDefinitionHolders
  66. */
  67. private void createBeanDefinition(Set<BeanDefinitionHolder> beanDefinitionHolders){
  68. for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
  69. GenericBeanDefinition beanDefinition=((GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition());
  70. //將bean的真實類型改變為FactoryBean
  71. beanDefinition.getConstructorArgumentValues().
  72. addGenericArgumentValue(beanDefinition.getBeanClassName());
  73. beanDefinition.setBeanClass(ProxyFactoryBean.class);
  74. beanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
  75. }
  76. }
  77. }

重寫doScan方法,因為我們需要將掃描到的接口指定FactroyBean為我們創建代理對象,父類的doScan方法,默認掃描的是帶有@Service,@Controller,@Repository,@Component注解的類,而不是接口。

重寫isCandidateComponent方法,因為父類默認將接口忽略掉的,而我們恰恰只掃描接口。

再看一下父類的doScan方法,返回的是Set<BeanDefinitionHolder>,而BeanDefinitionHolder可以獲取到BeanDefinition,所以私有方法createBeanDefinition,就是在處理BeanDefinition,可以看到createBeanDefinition方法並沒有調用registry.registerBeanDefinition方法,為什么?

ClassPathBeanDefinitionScanner類文檔說清楚了:

他是一個bean定義掃描器,它檢測類路徑上的bean候選項,並向給定的注冊表(BeanFactory或ApplicationContext)注冊相應的bean定義。

會自己會為我們注冊,所以我們不需要手工調用,從構造方法也可以看出,傳入了一個BeanDefinitionRegistry對象。

測試一下:

BeanDefinitionRegistryPostProcessor實現
  1. package com.mtl.interfaceProxy;
  2.  
  3. import org.springframework.beans.BeansException;
  4. import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  5. import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  6. import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
  7.  
  8. /**
  9. * 說明:接口代理對象配置類
  10. * BeanDefinitionRegistryPostProcessor允許在常規的BeanFactoryPostProcessor檢測開始之前注冊更多的bean定義。
  11. *
  12. * @作者 莫天龍
  13. * @時間 2019/04/29 17:43
  14. */
  15. public class InterfaceProxyconfigure implements BeanDefinitionRegistryPostProcessor {
  16. private String basePackge;
  17.  
  18. public void setBasePackge(String basePackge) {
  19. this.basePackge = basePackge;
  20. }
  21.  
  22. /**
  23. * 在應用程序上下文的標准初始化之后修改其內部bean定義注冊表。所有常規bean定義都已加載,但還沒有實例化bean。這允許在下一個后處理階段開始之前添加更多的bean定義
  24. * @param registry
  25. * @throws BeansException
  26. */
  27. @Override
  28. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
  29. InterfaceScanner scanner= new InterfaceScanner(registry);
  30. scanner.scan(basePackge);
  31. }
  32.  
  33. @Override
  34. public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  35.  
  36. }
  37. }

xml配置:

  1. <bean class="com.mtl.interfaceProxy.InterfaceProxyconfigure">
  2. <property name= "basePackge" value="com.mtl.itf"/>
  3. </bean>

測試類:

  1. public static void main(String[] args){
  2. ClassPathXmlApplicationContext applicationContext= new ClassPathXmlApplicationContext("app*.xml");
  3. Service service = applicationContext.getBean(Service.class);
  4. UserService UserService = applicationContext.getBean(UserService.class);
  5. service.test(" 123");
  6. UserService.save("user");
  7. applicationContext.close();
  8. }

執行結果:

 

完整實例代碼:github

轉自 https://blog.csdn.net/u014022405/article/details/89703609


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM