Java反射+自定义注解实现配置文件数组加载(实现@ConfigurationProperties("xxx"))

Java、Rust 技术交流群: 783303214

一、背景

     最近有小朋友问我,怎么样在.properties文件中配置数组参数,我给他举了上篇文章中的注解@ConfigurationProperties("xxx"),但是遗憾的是他们的项目并没有接入spring,而是用netty写的什么sdk吧,我猜,所以上述注解无法使用,加上自己很久没有玩反射了,就将就着写了一个demo,以供初学者借鉴,话不多说,不懂的看注释,写的还是比较详细。

 

二、代码实现

  首先,我们定义一个注解类,并规定好其作用域等信息

  

package com.github;

import java.lang.annotation.*;

/**
 * @Author: BBSee
 * @E-mail: [email protected]
 * @CreateDate: 16:16 2019/7/22 0022
 * @Description:
 */
@Target({ ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface BBSee {

    String value() default "";
    /**
     * The prefix of your properties fields
     */

    String prefix() default "";

    /**
     * These two boolean fields have not been analysed at present
     * if you do have this requirement,add it to the {@link com.github.PropertiesLoader<>}
     * @return
     */
    boolean ignoreInvalidFields() default false;

    boolean ignoreUnknownFields() default true;
}

  我们规定该注解只能使用于类上,即我们的pojo类上,prefix为配置文件中的字段前缀,接下来,我们定义注解解释器并对配置文件进行获取,由于时间不是很多,所以只写了核心的功能,即注解加载数组信息,根据pojo类的字段名称加载配置信息等,会有些bug,但是都无关痛痒,具体的看代码注释:

package com.github;

import com.github.conf.DataSourceNumberProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;
import java.util.regex.Pattern;

/**
 * @Author: BBSee
 * @E-mail: [email protected]
 * @CreateDate: 16:48 2019/7/22 0030
 * @Description:
 */
@Slf4j
public class PropertiesLoader  {
    private static final String DEFAULT_PROPERTIES_PATH="/application.properties";
    private  Properties properties;
    private static final String LEFT_BRACKET="[";
    private static final String RIGHT_BRACKET="]";
    /**
     * this demo uses  regular expression to match fields and array bracket,
     * it is highly recommend to use expression parsing engine,such as: ANTLR
     */
    public  static final Pattern BRACKET_PATTERN=Pattern.compile("^[a-zA-Z][a-zA-Z\\.0-9]+\\[+[0-9]+\\]$");


    public E getProperty(Class clazz) throws IllegalAccessException, InstantiationException, IntrospectionException, InvocationTargetException {
        /**
         * when the pojo is annotated
         **/
        if(clazz.isAnnotationPresent(BBSee.class)){
          return   getAnnotatedProperty(clazz);
        }else {
            /**when the pojo is not annotated,return an empty pojo */
            log.warn("pojo class without  annotation {@link com.github.BBSee.class} has " +
                    "not been supported yet,no data injected into class: "+clazz.getName());
            return (E)clazz.newInstance();
        }

    }

    /**
     * @param clazz
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    private E getAnnotatedProperty(Class clazz) throws IllegalAccessException, InstantiationException, IntrospectionException, InvocationTargetException {
        String annoPrefix=clazz.getDeclaredAnnotation(BBSee.class).prefix();
        /**
         * when a class has been annotated with the @BBSee annotation and used
         * the default prefix:empty string value,
         * this method tries to find out the .properties fields,
         * matching with a lower case of the pojo class name
         */
        String prefix=annoPrefix.isEmpty()?clazz.getName().toLowerCase():annoPrefix;
        Field[] fields=clazz.getDeclaredFields();
        Assert.hasText(prefix, "prefix name must not be null !");
        Assert.notNull(fields, "pojo fields must not be null!");
        /**
         * If you do not have Assert,using objects static methods instead
         */
        //Objects.requireNonNull(prefix,"prefix name must not be null !");
        Object pojo=clazz.newInstance();
        List keys=this.getPropertyListByPrefix(prefix,null);
        /**
         * no .properties fields matched,
         * do nothing but return a empty pojo instance
         */
        if(keys==null||keys.isEmpty()){
            return (E)pojo;
        }

        for(Field field:fields){
            /**
             * only support class which is an
             *  instance of {@link java.util.List}
             */

            if(java.util.List.class.isAssignableFrom(field.getType())) {
                String fieldName = field.getName();
                Type genericType = field.getGenericType();
                List data=new LinkedList();
                List sortedKeys=this.getPropertyListByPrefix(prefix,fieldName);
                if (Optional.ofNullable(sortedKeys).isPresent()){
                    sortedKeys.forEach(key->{
                        data.add(properties.get(key));
                    });
                }
                PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz);
                Method writeMethod = pd.getWriteMethod();
                writeMethod.invoke(pojo,data);
            }
            /**do nothing with other data constructs  **/
        }
        return (E)pojo;
    }

    /**
     * find out the matching fields names by prefix
     * @param prefix
     * @return
     */
   private List getPropertyListByPrefix(String prefix,String fieldName){
       List keys=new LinkedList();
       Iterator iterator=properties.keySet().iterator();
       while(iterator.hasNext()){
           String keyStr=iterator.next().toString();
           /**
            * Confirm that there are at least one matching value
            */
           if(fieldName==null){
               keys.add(keyStr);
               break;
           }
           /**
            * It seems that,
            * this comparison algorithm has some security and mapping bugs,
            * but I don`t have enough time to fix it,
            * thus,fixing it by yourself
            */
           //FIXME
           if(keyStr.startsWith(prefix)&&
                   keyStr.substring(0,prefix.length()).equalsIgnoreCase(prefix)){
               if(BRACKET_PATTERN.matcher(keyStr).matches()&&
                       keyStr.substring(keyStr.indexOf(LEFT_BRACKET)-fieldName.length(),
                               keyStr.indexOf(LEFT_BRACKET)).equals(fieldName)){
                   keys.add(keyStr);
               }
               }
           }
       /**
        * SORT the keys by index before return
        */
       if (Optional.ofNullable(keys).isPresent()){
         Collections.sort(keys, (o1, o2) -> {
            int index1=getIndex(o1);
            int index2=getIndex(o2);
            if(index1>index2){
                return 1;
            }else if (index10){
          for(String uri:path){
                try {
                    load(uri);
                }catch (IOException e){
                    log.error(e.getMessage());
                    continue;
                }
          }
       }else{
           load(DEFAULT_PROPERTIES_PATH);
       }
   }


   private void load(String path) throws IOException {
       try(InputStream file=PropertiesLoader.class.getResourceAsStream(path)) {
           properties.load(file);
       } catch (IOException e) {
           throw new IOException("error occurred while loading property file:"+path,e.getCause());
       }
   }

   public Properties getProperties(){
       return this.properties;
   }

   /**Test using of this class
    */

   public static void main(String[] args){
       PropertiesLoader propertiesLoader=new PropertiesLoader();
       try {
           propertiesLoader.load();
          DataSourceNumberProperties properties= propertiesLoader.getProperty(DataSourceNumberProperties.class);
        int a=1;
           /**
            * you can handle these following exceptions in properties loader instead of throwing it
            * do it yourself
            */
       } catch (IOException e) {
           e.printStackTrace();
       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (IntrospectionException e) {
           e.printStackTrace();
       } catch (InstantiationException e) {
           e.printStackTrace();
       } catch (InvocationTargetException e) {
           e.printStackTrace();
       }
   }
}

代码可以优化,比如穿插一些设计模式,增加一些静态方法等等,比如我们定义一个pojo类:

@Data
@BBSee(prefix="data")
public class  DataSourceNumberProperties {
	private List sources;
   
}

配置文件中数组:

data.sources[0]=1
data.sources[2]=2
data.sources[3]=3

loader根据字段解析,排序,对应相应的数据类型,代码中正则不支持中文,可以加上。

以上。

你可能感兴趣的:(Java)