1.前言

上一篇博客有大概的介绍了如何去动态生成一个bean,那像Feign和Mybaits等又是怎么做到通过一个注解在接口上的标记,去扫描动态生成的bean实例?本文将实现一个这样的案例。
github地址

2.实现详细

本案例会通过设置一个标记注解,和一个开启标记注解的的开关注解,然后扫描此标记注解标记的接口,去动态生成它的bean实例,然后再service中注入此实例。

2.1 第一步:定义标注注解

这里先定义一个注解BeanMark

package pers.lhl.study.dynamic.register.bean.annotation;


import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface BeanMark {
}

2.2第二步:定义开关注解

设置开关注解,开关注解携带扫描包路径信息:

package pers.lhl.study.dynamic.register.bean.annotation;

import org.springframework.context.annotation.Import;
import pers.lhl.study.dynamic.register.bean.register.BeanMarkRegister;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(BeanMarkRegister.class)
public @interface EnableBeanMark {
    String[] packages()  default "";
}

2.2第二步:bean定义扫描和注册

注意上面的注解有import一个BeanMarkRegister,这个类主要实现了ImportBeanDefinitionRegistrar关于ImportBeanDefinitionRegistrar的基本作用是:

可以向Spring容器中注册bean实例。Spring官方在动态注册bean时,大部分套路其实是使用ImportBeanDefinitionRegistrar接口。
所有实现了该接口的类都会被ConfigurationClassPostProcessor处理,ConfigurationClassPostProcessor实现了BeanFactoryPostProcessor接口,所以ImportBeanDefinitionRegistrar中动态注册的bean是优先于依赖其的bean初始化的,也能被aop、validator等机制处理。

当然也有其他的一些机制可以动态注册bean,但会有注册时机的问题导致一些spring的AOP和DI功能存在缺陷,这个可以了解我的上一篇博客。
这个类中会去扫描BeanMark注解,然后将扫描到的类通过jdk动态代理,生成对应的实例。
如下是这BeanMarkRegister的内容

package pers.lhl.study.dynamic.register.bean.register;

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.ClassUtils;
import pers.lhl.study.dynamic.register.bean.annotation.BeanMark;
import pers.lhl.study.dynamic.register.bean.annotation.EnableBeanMark;
import pers.lhl.study.dynamic.register.bean.handler.BeanMarkInvocationHandler;

import java.lang.reflect.Proxy;
import java.util.*;

/**
 * @author LHL
 * @version 1.0
 * @Description
 * @date 2022/3/1 22:01
 * @since
 */
public class BeanMarkRegister implements ResourceLoaderAware, EnvironmentAware, ImportBeanDefinitionRegistrar {
    
    private ResourceLoader resourceLoader;
    
    private Environment environment;
    
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<String, Object> attrs = importingClassMetadata.getAnnotationAttributes(EnableBeanMark.class.getName());
        // 获取{@link EnableBeanMark#packages}指定的包路径
        final String[] packages = attrs == null ? null : (String[]) attrs.get("packages");
        if (packages == null) {
            return;
        }
        //使用classpath注解扫描器
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false,
            this.environment) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                // 作为案例这里先不做判断,全部通过
                return true;
            }
        };
        //设置资源加载器
        scanner.setResourceLoader(this.resourceLoader);
        // 过滤只扫描BeanMark注解
        scanner.addIncludeFilter(new AnnotationTypeFilter(BeanMark.class));
        Set<BeanDefinition> beanDefinitions = new HashSet<BeanDefinition>();
        for (String basePackage : packages) {
            beanDefinitions.addAll(scanner.findCandidateComponents(basePackage));
        }
        //根据bean定义进行注册
        for (BeanDefinition beanDefinition : beanDefinitions) {
            registerBeanDefinition(beanDefinition, registry);
        }
    }
    
    /**
     * bean注册
     * @param beanDefinition
     * @param registry
     */
    private void registerBeanDefinition(BeanDefinition beanDefinition, BeanDefinitionRegistry registry) {
        Class type = ClassUtils.resolveClassName(
            ((AnnotatedBeanDefinition) beanDefinition).getMetadata().getClassName(), null);
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(type, () -> {
            //通过bean实例回调生成对应的代理类实例
            return Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type }, new BeanMarkInvocationHandler());
        });
        BeanDefinition thisBeanDefinition = builder.getBeanDefinition();
        BeanDefinitionHolder holder = new BeanDefinitionHolder(thisBeanDefinition,
            thisBeanDefinition.getBeanClassName());
        //注册
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }
    
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
    
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}

注意上面有一个动态代理的调用处理器BeanMarkInvocationHandler,这是其实现,当调用时,我们直接返回调用的方法信息:

package pers.lhl.study.dynamic.register.bean.handler;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @author LHL
 * @version 1.0
 * @Description
 * @date 2022/3/1 22:16
 * @since
 */
public class BeanMarkInvocationHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 返回代理方法名称
        return method.toGenericString();
    }
}

3.测试详细

3.1第一步:定义被标记的类

这里定义两个接口类StudentTeacher

package pers.lhl.study.dynamic.register.bean.client;

import pers.lhl.study.dynamic.register.bean.annotation.BeanMark;

/**
 * @author LHL
 * @version 1.0
 * @Description
 * @date 2022/3/1 21:55
 * @since
 */
@BeanMark
public interface Student {
    /**
     * 获取学生信息
     * @return
     */
    String getInfo();
}

package pers.lhl.study.dynamic.register.bean.client;

import pers.lhl.study.dynamic.register.bean.annotation.BeanMark;

/**
 * @author LHL
 * @version 1.0
 * @Description
 * @date 2022/3/1 21:56
 * @since
 */
@BeanMark
public interface Teacher {
    /**
     * 获取教师信息
     * @return
     */
    String getInfo();
}

3.2第二步:注入并调用被标记类

本service类会注入,然后调用其对应的函数:

package pers.lhl.study.dynamic.register.bean.service;

import org.springframework.beans.factory.annotation.Autowired;
import pers.lhl.study.dynamic.register.bean.client.Student;
import pers.lhl.study.dynamic.register.bean.client.Teacher;

import javax.annotation.PostConstruct;

/**
 * @author LHL
 * @version 1.0
 * @Description
 * @date 2022/3/1 21:55
 * @since
 */
@org.springframework.stereotype.Service
public class Service {
    @Autowired
    private Student student;
    
    @Autowired
    private Teacher teacher;
    
    @PostConstruct
    public void post(){
        System.out.println(student.getInfo());
        System.out.println(teacher.getInfo());
    }
}

3.3第三步:定义入口类

package pers.lhl.study.dynamic.register.bean;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import pers.lhl.study.dynamic.register.bean.annotation.EnableBeanMark;

/**
 * @author LHL
 * @version 1.0
 * @Description
 * @date 2022/3/1 21:50
 * @since 1.0
 */
@SpringBootApplication
@EnableBeanMark(packages = "pers.lhl.study.dynamic.register.bean.client")
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}

3.4第四步:输出

如下是输出结果,在控制台成功输出了各自的被调用函数信息:

2022-03-01 22:36:57.066  INFO 26688 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1411 ms
public abstract java.lang.String pers.lhl.study.dynamic.register.bean.client.Student.getInfo()
public abstract java.lang.String pers.lhl.study.dynamic.register.bean.client.Teacher.getInfo()
2022-03-01 22:36:57.231  INFO 26688 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'

4.结束语

当然,这里只演示了动态注册的其中一种方案,中间可以有多种手段去进行注册,class的扫描等等,后续会不断完善。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐