摘要
新建接口的自定义注解,并实现扫描器对注解进行扫描。然后,对接口进行代理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的应该会理解。
三、采坑
主要代码都可以在网上搜到,但是并不是很好找。也可以通过读源码来了解相关开发思路。
四、后记
感谢分享知识的老师。不知道他们现在是否都转行了。