首页 > 文章列表 > SpringBoot怎么通过自定义注解实现配置类的自动注入

SpringBoot怎么通过自定义注解实现配置类的自动注入

springboot
306 2023-05-17

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();

    }

}

效果