最近有小朋友问我,怎么样在.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根据字段解析,排序,对应相应的数据类型,代码中正则不支持中文,可以加上。
以上。