今天小编给大家分享一下SpringBoot怎么通过自定义注解实现配置类的自动注入的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。
前言
SpringBoot中通过
@ConfigurationProperties或
@Value注解就可以获取配置文件中的属性定义并绑定到Java Bean或属性上,这也是我们平常使用最多的一种方式。但是小胖在开发过程中就遇到一个问题:在做MQ的开发中,配置文件中会配置多个生产者分别提供不同的业务能力,如果通过
@ConfigurationProperties注解来实现的话,这就意味着需要创建多个属性一样的配置类,虽然说可以实现功能,但是很明显,这不是一个很好的设计。场景如下所示:
producer1: password: xxx app: xxx address: url1 enabled: false producer2: password: xxx app: xxx address: url1 enabled: false
实现思路
在我们日常的开发工作中,经常可以见到的是通过自定义注解+拦截器+反射从而实现对权限的校验或者对实体类字段值格式进行校验。那么,我们是不是也可以参考这个思路达到我们的目的呢?答案是肯定的,其实如果对Mabatis等组件比较熟悉的话,就可以看到这样的设计。我们话不多少,开搞~
开搞
以下内容,为了方便,我们将配置相关内容改为人员(people)
自定义配置类读取配置
首先,有一点是不会改变的,我们需要自定义一个配置类,用于读取配置文件中的配置。这里,我们需要改变一下我们配置文件信息里。将所有的配置信息放到一个类里。
my: peoples: people1: userName: 张三 userSex: 男 people2: userName: 李四 userSex: 女
然后,定义一个配置类用来接收,这里通过
@ConfigurationProperties注解实现对配置的注入。要注意,因为我们在peoples下面有很多的people,因此,属性应给定义的是一个MAP的类型。
@Component @ConfigurationProperties(prefix = "my",ignoreUnknownFields = false) public class PeopleConfigs { private Map<String, PeopleEntity> peoples; public Map<String, PeopleEntity> getPeoples() { return peoples; } public void setPeoples(Map<String, PeopleEntity> peoples) { this.peoples = peoples; } @Override public String toString() { return "PeopleConfigs{" + "peoples=" + peoples + '}'; } } public class PeopleEntity { private String userName; private String userSex; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserSex() { return userSex; } public void setUserSex(String userSex) { this.userSex = userSex; } @Override public String toString() { return "PeopleEntity{" + "userName='" + userName + ''' + ", userSex='" + userSex + ''' + '}'; } }
这样,Springboot就会自动加载我们这个配置类。但是,这个的整个
PeopleConfigs是一个Bean,并不能达到我们本文的目的,因此我们进行后续的步骤。
自定义注解
我们声明一个运行时的注解,在属性上进行使用。这里定义name用来标记需要注入的是哪个人。
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface People { String name() default ""; }
创建子配置Bean
首先,定义一个autoConfig的配置类,该类通过
@EnableConfigurationProperties注解,指定PeopleConfig Bean在本类之前进行装载。通过
@Bean方法注解进行bean声明,此处调用的是单个people配置类的bean生成的方法。
@Configuration @EnableConfigurationProperties({PeopleConfigs.class}) public class PeopleAutoConfig { @Autowired PeopleConfigs peopleConfigs; @Bean public PeopleRegister peopleRegister(){ return new PeopleRegister(peopleConfigs); } }
通过反射进行people bean的注入
这里不得不提到
BeanPostProcessor类,该类为我们提供了springBoot在bean初始化前后方便我们进行其他自定义操作的一些接口。我们这里通过实现
postProcessBeforeInitialization方法,在bean装载之前,通过反射判断对应bean上是否有我们自定义的people注解。如果有,则进行注入操作。详细代码如下:
public class PeopleRegister implements BeanPostProcessor, ApplicationContextAware { private final PeopleConfigs peopleConfigs; private GenericApplicationContext applicationContext; PeopleRegister(PeopleConfigs peopleConfigs){ this.peopleConfigs = peopleConfigs; } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { Class<?> beanClass = AopUtils.getTargetClass(bean); Field[] fields = beanClass.getDeclaredFields(); Field[] var5 = fields; int var6 = fields.length; for(int var7 = 0;var7<var6;var7++){ Field field = var5[var7]; People annotation = field.getAnnotation(People.class); if (annotation!=null){ PeopleEntity entity = this.peopleConfigs.getPeoples().get(annotation.name()); if (!this.applicationContext.containsBean(annotation.name())){ ConfigurableListableBeanFactory beanFactory = this.applicationContext.getBeanFactory(); Object wrapperBean = beanFactory.initializeBean(entity, annotation.name()); beanFactory.registerSingleton(annotation.name(), Objects.requireNonNull(wrapperBean)); } try{ field.setAccessible(true); field.set(bean, this.applicationContext.getBean(annotation.name(), PeopleEntity.class)); }catch (Exception e){ e.printStackTrace(); } } } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = (GenericApplicationContext)applicationContext; } }
使用
前面工作进行完成后,接下来就是我们的使用环节,这里,我们仅需要通过
@People(name = "人")指定即可:
@Controller public class BaseController { @Autowired PeopleConfigs peopleConfigs; @People(name = "people1") PeopleEntity people1; @People(name = "people2") PeopleEntity people2; @ResponseBody @GetMapping("/test") public String test() { return peopleConfigs.toString()+people1.toString()+people2.toString(); } }
效果