动态注册bean,Spring官方套路:使用ImportBeanDefinitionRegistrar

动态注册bean,Spring官方套路:使用ImportBeanDefinitionRegistrar

动态注册bean,Spring官方套路:使用ImportBeanDefinitionRegistrar


[上一篇文章][1]中介绍了Spring提供的动态注册bean的方法。这里会介绍一下Spring官方实现动态注册bean的套路。


ImportBeanDefinitionRegistrar

Spring官方在动态注册bean时,大部分套路其实是使用ImportBeanDefinitionRegistrar接口。

所有实现了该接口的类的都会被ConfigurationClassPostProcessor处理,ConfigurationClassPostProcessor实现了BeanFactoryPostProcessor接口,所以ImportBeanDefinitionRegistrar中动态注册的bean是优先与依赖其的bean初始化的,也能被aop、validator等机制处理。


使用方法

ImportBeanDefinitionRegistrar需要配合@Configuration和@Import注解,@Configuration定义Java格式的Spring配置文件,@Import注解导入实现了ImportBeanDefinitionRegistrar接口的类。


例子:

要实现的效果如下,在接口上使用注解定义url、http方法类型等信息,程序根据这些信息动态生成实现类

@Component
    @HTTPUtil
    public interface IRequestDemo {
        //调用test1时,会对http://abc.com发送get请求
        @HTTPRequest(url = "http://abc.com")
        
        HttpResult<String> test1();
        //调用test2时,会对http://test2.com发送post请求
        @HTTPRequest(url = "http://test2.com", httpMethod = HTTPMethod.POST)
        HttpResult<String> test2();
    }

例子思路来自于我的同事[晓风轻][2]的文章[《编写简陋的接口调用框架》][3]。

此处为了简化,我并没有实现完整的http请求代理,而是把注意力集中到ImportBeanDefinitionRegistrar的实现上。

对完整实现感兴趣的,可以参考项目github.com/xwjie/MyRest


例子编写步骤

1. 首先编写核心ImportBeanDefinitionRegistrar接口,重要代码如下:

主要思路是利用ClassPathScanningCandidateComponentProvider获取标注了HTTPUtil注解的接口,并使用JDK动态代理为期生成代理对象。然后使用DefaultListableBeanFactory将代理对象注册到容器中。如下:

@Slf4j
public class HTTPRequestRegistrar implements ImportBeanDefinitionRegistrar,
       ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware, BeanFactoryAware {
   @Override
   public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
       registerHttpRequest(beanDefinitionRegistry);
   }

   /**
    * 注册动态bean的主要方法
    *
    * @param beanDefinitionRegistry
    */
   private void registerHttpRequest(BeanDefinitionRegistry beanDefinitionRegistry) {
       ClassPathScanningCandidateComponentProvider classScanner = getClassScanner();
       classScanner.setResourceLoader(this.resourceLoader);
       //指定只关注标注了@HTTPUtil注解的接口
       AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(HTTPUtil.class);
       classScanner.addIncludeFilter(annotationTypeFilter);
       //指定扫描的基础包
       String basePack = "com.example.registerbean";
       Set<BeanDefinition> beanDefinitionSet = classScanner.findCandidateComponents(basePack);
       for (BeanDefinition beanDefinition : beanDefinitionSet) {
           if (beanDefinition instanceof AnnotatedBeanDefinition) {
               registerBeans(((AnnotatedBeanDefinition) beanDefinition));
           }
       }
   }

   /**
    * 创建动态代理,并动态注册到容器中
    *
    * @param annotatedBeanDefinition
    */
   private void registerBeans(AnnotatedBeanDefinition annotatedBeanDefinition) {
       String className = annotatedBeanDefinition.getBeanClassName();
       ((DefaultListableBeanFactory) this.beanFactory).registerSingleton(className, createProxy(annotatedBeanDefinition));
   }

   /**
    * 构造Class扫描器,设置了只扫描顶级接口,不扫描内部类
    *
    * @return
    */
   private ClassPathScanningCandidateComponentProvider getClassScanner() {
       return new ClassPathScanningCandidateComponentProvider(false, this.environment) {

           @Override
           protected boolean isCandidateComponent(
                   AnnotatedBeanDefinition beanDefinition) {
               if (beanDefinition.getMetadata().isInterface()) {
                   try {
                       Class<?> target = ClassUtils.forName(
                               beanDefinition.getMetadata().getClassName(),
                               classLoader);
                       return !target.isAnnotation();
                   } catch (Exception ex) {
                       log.error("load class exception:", ex);
                   }
               }
               return false;
           }
       };
   }

   /**
    * 创建动态代理
    *
    * @param annotatedBeanDefinition
    * @return
    */
   private Object createProxy(AnnotatedBeanDefinition annotatedBeanDefinition) {
       try {
           AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
           Class<?> target = Class.forName(annotationMetadata.getClassName());
           InvocationHandler invocationHandler = createInvocationHandler();
           Object proxy = Proxy.newProxyInstance(HTTPRequest.class.getClassLoader(), new Class[]{target}, invocationHandler);
           return proxy;
       } catch (ClassNotFoundException e) {
           log.error(e.getMessage());
       }
       return null;
   }

   /**
    * 创建InvocationHandler,将方法调用全部代理给DemoHttpHandler
    *
    * @return
    */
   private InvocationHandler createInvocationHandler() {
       return new InvocationHandler() {
           private DemoHttpHandler demoHttpHandler = new DemoHttpHandler();

           @Override
           public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

               return demoHttpHandler.handle(method);
           }
       };
   }        
    ... 省略setter代码   
}

2.编写注解,并在其中使用@Import导入第1步编写的HTTPRequestRegistrar

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(HTTPRequestRegistrar.class)
public @interface EnableHttpUtil {
}

3.将@EnableHttpUtil添加到@Configuration注解下,如果使用了Spring-Boot,由于@SpringBootApplication注解包含了@Configuration注解,可以将@EnableHttpUtil添加到@SpringBootApplication注解下。

@SpringBootApplication
@EnableHttpUtil
public class RegisterbeanImportBeanDefinitionRegistrarApplication {

	public static void main(String[] args) {
SpringApplication.run(RegisterbeanImportBeanDefinitionRegistrarApplication.class, args);
	}
}

4.使用,直接注入IRequestDemo即可

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class RegisterbeanImportBeanDefinitionRegistrarApplicationTests {
   @Autowired
   IRequestDemo iRequestDemo;

   @Test
   public void test1() {
       HttpResult<String> result = this.iRequestDemo.test1();
       String response = result.getResponse();
log.info(">>>>>>>>>>{}", response);
       assertEquals("http request: url=http://abc.com and method=GET",response);
   }

   @Test
   public void test2() {
       HttpResult<String> result = this.iRequestDemo.test2();
       String response = result.getResponse();
log.info(">>>>>>>>>>{}", response);
       assertEquals("http request: url=http://test2.com and method=POST",response);
   }

}

完整可以运行demo代码在github.com/pkpk1234/reg


[1]: Spring动态注册bean

[2]: 知乎用户

[3]: 编写简陋的接口调用框架 - 动态代理学习

发布于 2017-10-14