自定义注解的扫描和代理bean的注册

824 阅读3分钟

摘要

新建接口的自定义注解,并实现扫描器对注解进行扫描。然后,对接口进行代理bean的注册。从而实现接口代理的自动注册。

前言

开发工具:idea jdk版本:1.8 spring boot版本:2.0.9 研发背景:为了去掉项目中冗余的动态代理实例化代码,打算通过自定义注解配合扫描器,实现自动的动态代理实例化。

注:扫描类借鉴了其他大佬的代码,并进行了修改。

正文

一、自定义注解

(一)接口的自定义注解:

新建一个自定义注解:用来修饰要代理的接口和方法


/**
 * 自动适配当前项目网关
 */
@Target( {ElementType.METHOD, ElementType.TYPE} )
@Retention( RetentionPolicy.RUNTIME )
public @interface Manager {

    /**
     * 代理类的 bean name
     * @return
     */
    String beanName() default "";

    /**
     * 网关name
     * @return
     */
    String gatewayName() default "";

    /**
     * 网关接口路径
     * @return
     */
    String servicePath() default "";

    /**
     * 网关版本
     * @return
     */
    String version() default "";

    /**
     * 接口参数
     * @return
     */
    String[] params() default {};

    /**
     * 接口名称
     * @return
     */
    String serviceName() default "";

    /**
     * 请求方法类型: post、get
     * @return
     */
    RequestMethod[] method() default {};

}

(二)参考@Enable模块驱动新建自定义注解:

1、参考@Enable模块驱动新建自定义注解:通过自定义注解来开启扫描功能,让功能的开启变得直观可见,提高代码可读性。关于@Enable模块驱动,请参考度娘等搜索引擎,或《Spring Boot 编程思想(核心篇)》

@Target( { ElementType.TYPE } )
@Retention( RetentionPolicy.RUNTIME )
@Import( ManagerBeanDefinitionRegistry.class ) // 扫描功能实现类
public @interface ManagerBeanScanner {

    /**
     * 要扫描的包路径
     */
    String[] include();

    /**
     * 不要扫描的包路径
     */
    String[] exclude() default {};

}

2、使用方式:在启动类上添加该注解,即可启动扫描功能

@SpringBootApplication
@ServletComponentScan
@ImportResource( {
        "classpath:config/spring.xml"
} )
// 开启自定义注解扫描
@ManagerBeanScanner( include = "com.liuy.aaa", exclude = "com.liuy.aaa.bbb" )
public class MyApplication {

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

}

二、扫描自定义注解


/**
 * @Author liuy
 * @Date 2022/7/22 13:39
 */
@Slf4j
public class ManagerBeanDefinitionRegistry implements ResourceLoaderAware, ImportBeanDefinitionRegistrar {

    private ResourcePatternResolver resourcePatternResolver;
    private CachingMetadataReaderFactory metadataReaderFactory;
    private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
    private String[] include;
    private String[] exclude;

    @Override
    public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata,
                                         BeanDefinitionRegistry registry) {
        Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes( ManagerBeanScanner.class.getName(), true );
        include = ( String[] ) annotationAttributes.get( "include" );
        exclude = ( String[] ) annotationAttributes.get( "exclude" );

        Set<BeanDomain> beanClazzs = scannerPackages();
        outer:
        for (BeanDomain beanDomain : beanClazzs) {

            Class<?> beanClazz = beanDomain.getClazz();
            String beanName = beanDomain.getBeanName();
            // 如果没有 beanName, 取接口名(单个首字母大写的转小写,多个首字母大写的不动)
            beanName = StringUtils.isEmpty( beanName ) ? StringUtils.uncapitalize( beanClazz.getSimpleName() ) : beanName;

            // 如果已存在同名 bean , 则跳过
            if ( registry.isBeanNameInUse( beanName ) ) {
                continue outer;
            }

            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition( beanName );
            GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();

            // 这里传入接口
            definition.getConstructorArgumentValues().addGenericArgumentValue(beanClazz);

            // 注意,这里的BeanClass是生成Bean实例的工厂,不是Bean本身。
            // FactoryBean是一种特殊的Bean,其返回的对象不是指定类的一个实例,
            // 其返回的是该工厂Bean的getObject方法所返回的对象。
            definition.setBeanClass( ManagerBeanFactory.class );

            //这里采用的是byType方式注入,类似的还有byName等
            definition.setAutowireMode( GenericBeanDefinition.AUTOWIRE_BY_TYPE );

            log.info( "注册manager层代理bean: {}, bean 名称: {}", beanClazz.getName(), beanName );

            registry.registerBeanDefinition( beanName, definition );

        }

    }

    private Set<BeanDomain> scannerPackages() {

        Set<BeanDomain> set = new LinkedHashSet<>();

        try {
            Resource[] resources = new Resource[]{};
            for ( String aPackage : include ) {
                String folderPath = ClassUtils.convertClassNameToResourcePath( aPackage );
                String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + folderPath + "/" + DEFAULT_RESOURCE_PATTERN;
                Resource[] resources1 = this.resourcePatternResolver.getResources( packageSearchPath );
                resources = ArrayUtils.addAll( resources1, resources );
            }

            outer:
            for ( Resource resource : resources ) {
                if ( resource.isReadable() ) {
                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader( resource );
                    ClassMetadata classMetadata = metadataReader.getClassMetadata();
                    // 只扫描接口
                    if ( !classMetadata.isInterface() ) {
                        continue;
                    }

                    String className = classMetadata.getClassName();
                    for ( String excludePackage : exclude ) {
                        if ( className.indexOf( excludePackage ) >= 0 ) {
                            continue outer;
                        }
                    }

                    AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
                    BeanDomain beanDomain = getBeanDomain( annotationMetadata, className );

                    if ( beanDomain != null ) {
                        set.add( beanDomain );
                    }
                }
            }
        } catch ( IOException | ClassNotFoundException e ) {
            e.printStackTrace();
        }
        return set;
    }


    private BeanDomain getBeanDomain( AnnotationMetadata annotationMetadata, String className ) throws ClassNotFoundException {

        if ( annotationMetadata.hasAnnotation( Manager.class.getName() ) ) {
            BeanDomain beanDomain = new BeanDomain();
            Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes( Manager.class.getName() );
            beanDomain.setBeanName( String.valueOf( attributes.get( "beanName" ) ) );
            beanDomain.setClazz( Class.forName( className ) );
            return beanDomain;
        }

        return null;
    }

    @Override
    public void setResourceLoader( ResourceLoader resourceLoader ) {
        this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
    }

    @Data
    private class BeanDomain {
        private String beanName;
        private Class<?> clazz;
    }

}

三、实例化动态代理

/**
 * @Author liuy
 * @Date 2022/7/29 11:29
 */
public class ManagerBeanFactory<T> implements FactoryBean<T> {

    @Resource
    private ClientProxy clientProxy;

    private Class<T> interfaceType;

    public ManagerBeanFactory( Class<T> interfaceType ) {
        this.interfaceType = interfaceType;
    }

    @Override
    public T getObject() {
        return clientProxy.getProxy( interfaceType );
    }

    @Override
    public Class<T> getObjectType() {
        return interfaceType;
    }

}

这个动态代理类 ClientProxy,我就不提供了,和这篇文章的主要内容没有关系,而且篇幅会变得很长,了解过rpc的应该会理解。

三、采坑

主要代码都可以在网上搜到,但是并不是很好找。也可以通过读源码来了解相关开发思路。

四、后记

感谢分享知识的老师。不知道他们现在是否都转行了。