spring动态创建bean:扫描注解动态生成代理bean(二)
文章目录1.前言2.实现详细2.1 第一步:定义标注注解2.2第二步:定义开关注解2.2第二步:bean定义扫描和注册3.测试详细3.1第一步:定义被标记的类3.2第二步:注入并调用被标记类3.3第三步:定义入口类3.4第四步:输出4.结束语1.前言上一篇博客有大概的介绍了如何去动态生成一个bean,那像Feign和Mybaits等又是怎么做到通过一个注解再接口上的标记去动态对应的bean实例,本
文章目录
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第一步:定义被标记的类
这里定义两个接口类Student
和Teacher
:
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的扫描等等,后续会不断完善。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)