Práctica de refactorización de Java: implemente un cargador de configuración y utilice patrones de diseño para optimizarlo

1. Escribe delante

En proyectos de autoinvestigación, a menudo no se considera el uso de Spring o Springboot framework, por lo que no se pueden utilizar los artefactos de Spring que simplifican la configuración. En este caso, es necesario escribir un cargador de configuración usted mismo.

2. Versión inicial

1. Configuración de la herramienta de clase de entidad

Esta herramienta puede convertir la configuración de Propiedades en clases de entidad Java.

import java.lang.reflect.Method;
import java.util.Properties;

/**
 * 配置工具类
 */
public class PropertiesUtils {
    
    

    /**
     * 配置映射到实体类
     */
    public static void properties2Object(final Properties p, final Object object) {
    
    
        properties2Object(p, object, "");
    }

    public static void properties2Object(final Properties p, final Object object, String prefix) {
    
    
        Method[] methods = object.getClass().getMethods();
        for (Method method : methods) {
    
    
            String methodName = method.getName();
            if (methodName.startsWith("set")) {
    
    
                try {
    
    
                    // 获取配置的key
                    String lastName = methodName.substring(4);
                    String firstName = methodName.substring(3, 4);
                    String key = prefix + firstName.toLowerCase() + lastName;
                    String value = p.getProperty(key);
                    if (value != null) {
    
    
                        Class<?>[] parameterTypes = method.getParameterTypes();
                        if (parameterTypes.length > 0) {
    
    
                            // 参数类型名
                            String paramName = parameterTypes[0].getSimpleName();
                            Object arg;
                            if (paramName.equals("int") || paramName.equals("Integer")) {
    
    
                                arg = Integer.parseInt(value);
                            } else if (paramName.equalsIgnoreCase("Double")) {
    
    
                                arg = Double.parseDouble(value);
                            } else if (paramName.equalsIgnoreCase("Boolean")) {
    
    
                                arg = Boolean.parseBoolean(value);
                            } else if (paramName.equalsIgnoreCase("Float")) {
    
    
                                arg = Float.parseFloat(value);
                            } else if (paramName.equalsIgnoreCase("String")) {
    
    
                                arg = value;
                            } else {
    
    
                                // 其他类型,根据需求  待提供
                                continue;
                            }
                            method.invoke(object, arg);
                        }
                    }
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                }
            }
        }

    }
}

2. Clase de configuración

/**
 * 配置类
 */
public class Config {
    
    

    // 端口
    private String port = "8888";

    // 注册中心
    private String registerUrl = "localhost:8848";

    // 服务名
    private String applicationName;

    // 环境
    private String env = "dev";

    public String getPort() {
    
    
        return port;
    }

    public void setPort(String port) {
    
    
        this.port = port;
    }

    public String getRegisterUrl() {
    
    
        return registerUrl;
    }

    public void setRegisterUrl(String registerUrl) {
    
    
        this.registerUrl = registerUrl;
    }

    public String getApplicationName() {
    
    
        return applicationName;
    }

    public void setApplicationName(String applicationName) {
    
    
        this.applicationName = applicationName;
    }

    public String getEnv() {
    
    
        return env;
    }

    public void setEnv(String env) {
    
    
        this.env = env;
    }
}

3. Configurar la clase de carga

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Properties;

public class ConfigLoader {
    
    

    // 配置文件位置
    private static final String CONFIG_FILE = "myConfig.properties";
    // 环境变量前缀
    private static final String ENV_PREFIX = "CONFIG_";
    // JVM参数前缀
    private static final String JVM_PREFIX = "config.";

    /**
     * 单例
     */
    private ConfigLoader() {
    
    
    }

    private static final ConfigLoader INSTANCE = new ConfigLoader();

    public static ConfigLoader getInstance() {
    
    
        return INSTANCE;
    }

    private Config config;

    public static Config getConfig() {
    
    
        return INSTANCE.config;
    }

    /**
     * 加载配置:
     * 优先级高的会覆盖优先级低的
     * 运行参数 ->jvm参数 -> 环境变量 -> 配置文件 ->默认值
     */
    public Config load(String[] args) {
    
    
        // 默认值
        config = new Config();

        // 配置文件
        loadFromConfigFile();

        // 环境变量
        loadFromEnv();

        // jvm参数
        loadFromJvm();

        // 运行参数
        loadFromArgs(args);
        return config;
    }

    /**
     * 从运行参数加载
     */
    private void loadFromArgs(String[] args) {
    
    
        // --port=1234
        if(args != null && args.length > 0) {
    
    
            Properties properties = new Properties();
            for (String arg : args) {
    
    
                if(arg.startsWith("--") && arg.contains("=")) {
    
    
                    properties.put(arg.substring(2, arg.indexOf("=")).trim(),
                            arg.substring(arg.indexOf("=") + 1).trim());
                }
            }
            PropertiesUtils.properties2Object(properties, config);
        }
    }


    /**
     * 从JVM参数加载
     */
    private void loadFromJvm() {
    
    
        Properties properties = System.getProperties();
        PropertiesUtils.properties2Object(properties, config);
    }

    /**
     * 从环境变量加载
     */
    private void loadFromEnv() {
    
    
        Map<String, String> env = System.getenv();
        Properties properties = new Properties();
        properties.putAll(env);
        PropertiesUtils.properties2Object(properties, config, ENV_PREFIX);

    }

    /**
     * 从配置文件加载配置
     */
    private void loadFromConfigFile() {
    
    
        InputStream inputStream = ConfigLoader.class.getClassLoader().getResourceAsStream(CONFIG_FILE);
        if(inputStream != null) {
    
    
            Properties properties = new Properties();
            try {
    
    
                properties.load(inputStream);
                PropertiesUtils.properties2Object(properties, config, JVM_PREFIX);
            } catch (IOException e) {
    
    
                System.out.println("load config file " + CONFIG_FILE + " error");
                e.printStackTrace();
            } finally {
    
    
                try {
    
    
                    inputStream.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }

        }

    }

}

4. Prueba

public class ConfigTest {
    
    

    public static void main(String[] args) {
    
    
        ConfigLoader.getInstance().load(args);
        Config config = ConfigLoader.getConfig();
        System.out.println(config.getPort());
    }
}

Resultado: obtenga configuraciones a partir de valores predeterminados, archivos de configuración, variables de entorno, parámetros de jvm y parámetros de ejecución de Java.

3. Extensión 1: utilice el modelo de cadena de responsabilidad para proporcionar una secuencia de carga de configuración conectable

1. Análisis de objetivos

La carga de la configuración actual depende del método de carga. Cuando se agrega o elimina un complemento de configuración, este método debe modificarse.

A continuación optimizaremos el método de carga.

2. Interfaz de nivel superior del procesador

/**
 * 配置加载处理接口
 */
public interface IConfigLoadHandler {
    
    

    /**
     * 对配置进行加载
     */
    void handle(Config config);

}

3. Cadena de responsabilidad

public class ConfigLoadHandlerChain {
    
    

    private List<IConfigLoadHandler> handlers = new ArrayList<>();

    public static ConfigLoadHandlerChain getInstance() {
    
    
        return new ConfigLoadHandlerChain();
    }

    public ConfigLoadHandlerChain addHandler(IConfigLoadHandler handler) {
    
    
        this.handlers.add(handler);
        return this;
    }

    public void handle(Config config) {
    
    
        for (IConfigLoadHandler handler : handlers) {
    
    
            handler.handle(config);
        }
    }
}

4. Implementación del procesador

/**
 * 从启动参数加载
 */
public class ConfigLoadFromArgs implements IConfigLoadHandler {
    
    

    @Override
    public void handle(Config config) {
    
    
        // --port=1234
        String[] args = ConfigLoader.getArgs();
        if(args != null && args.length > 0) {
    
    
            Properties properties = new Properties();
            for (String arg : args) {
    
    
                if(arg.startsWith("--") && arg.contains("=")) {
    
    
                    properties.put(arg.substring(2, arg.indexOf("=")).trim(),
                            arg.substring(arg.indexOf("=") + 1).trim());
                }
            }
            PropertiesUtils.properties2Object(properties, config);
        }
    }
}
/**
 * 从环境变量加载
 */
public class ConfigLoadFromEnv implements IConfigLoadHandler {
    
    

    // 环境变量前缀
    private static final String ENV_PREFIX = "CONFIG_";

    @Override
    public void handle(Config config) {
    
    
        Map<String, String> env = System.getenv();
        Properties properties = new Properties();
        properties.putAll(env);
        PropertiesUtils.properties2Object(properties, config, ENV_PREFIX);
    }
}

/**
 * 从配置文件加载
 */
public class ConfigLoadFromFile implements IConfigLoadHandler {
    
    

    // 配置文件位置
    private static final String CONFIG_FILE = "myConfig.properties";

    @Override
    public void handle(Config config) {
    
    
        InputStream inputStream = ConfigLoader.class.getClassLoader().getResourceAsStream(CONFIG_FILE);
        if(inputStream != null) {
    
    
            Properties properties = new Properties();
            try {
    
    
                properties.load(inputStream);
                PropertiesUtils.properties2Object(properties, config);
            } catch (IOException e) {
    
    
                System.out.println("load config file " + CONFIG_FILE + " error");
                e.printStackTrace();
            } finally {
    
    
                try {
    
    
                    inputStream.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }

        }
    }
}

/**
 * 从JVM参数加载
 */
public class ConfigLoadFromJvm implements IConfigLoadHandler {
    
    

    // 环境变量前缀
    private static final String JVM_PREFIX = "config.";

    @Override
    public void handle(Config config) {
    
    
        Properties properties = System.getProperties();
        PropertiesUtils.properties2Object(properties, config, JVM_PREFIX);
    }
}

5. Modificación del método de carga.

    /**
     * 加载配置:
     * 优先级高的会覆盖优先级低的
     * 运行参数 ->jvm参数 -> 环境变量 -> 配置文件 ->默认值
     */
    public Config load(String[] args) {
    
    
        ConfigLoader.args = args;
        // 默认值
        config = new Config();

        ConfigLoadHandlerChain configLoadHandlerChain =
                ConfigLoadHandlerChain.getInstance()
                        .addHandler(new ConfigLoadFromFile())// 配置文件
                        .addHandler(new ConfigLoadFromEnv())// 环境变量
                        .addHandler(new ConfigLoadFromJvm())// jvm参数
                        .addHandler(new ConfigLoadFromArgs());// 运行参数
        configLoadHandlerChain.handle(config);
        return config;
    }

En este momento, cuando ampliamos el procesador de carga de configuración, solo necesitamos definir una clase de implementación del procesador e implementar la interfaz IConfigLoadHandler, y luego implementar la capacidad de conexión en el método de carga.

4. Extensión 2: escaneo dinámico de clases que deben configurarse

1. Análisis de objetivos

La configuración que cargamos antes solo se puede cargar en una clase de configuración fija y está altamente acoplada con la clase de implementación de carga de configuración.

Queremos implementar una clase de configuración que se pueda separar como Spring. Por ejemplo, una clase de configuración administra los parámetros de inicio y otra clase de configuración administra los parámetros de la aplicación.

2. Herramienta de escaneo de paquetes

import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 * 包扫描工具类
 */
public class PackageScanner {
    
    

    private static final String CLASS_FILE_SUFFIX = ".class";

    public static List<Class<?>> scanPackage(String packageName, Class<? extends Annotation> annotation) {
    
    
        List<Class<?>> classes = new ArrayList<>();

        String packagePath = packageName.replace('.', '/');
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        URL packageURL = classLoader.getResource(packagePath);

        if (packageURL != null) {
    
    
            File packageDirectory = new File(packageURL.getFile());
            if (packageDirectory.exists()) {
    
    
                scanFiles(packageName, packageDirectory, annotation, classes);
            }
        }

        return classes;
    }

    private static void scanFiles(String packageName, File directory, Class<? extends Annotation> annotation, List<Class<?>> classes) {
    
    
        File[] files = directory.listFiles();
        if (files != null) {
    
    
            for (File file : files) {
    
    
                if (file.isFile() && file.getName().endsWith(CLASS_FILE_SUFFIX)) {
    
    
                    try {
    
    
                        // 获取文件名(去除后缀)
                        String className = packageName + "." + file.getName().substring(0, file.getName().length() - CLASS_FILE_SUFFIX.length());
                        Class<?> clazz = Class.forName(className);
                        if (clazz.isAnnotationPresent(annotation)) {
    
    
                            classes.add(clazz);
                        }
                    } catch (ClassNotFoundException e) {
    
    
                        e.printStackTrace();
                    }
                } else if (file.isDirectory()) {
    
    
                    // 递归扫描子目录
                    String subPackageName = packageName + "." + file.getName();
                    scanFiles(subPackageName, file, annotation, classes);
                }
            }
        }
    }

//    public static void main(String[] args) {
    
    
//        List<Class<?>> classes = scanPackage("com.example", Config.class);
//        for (Class<?> clazz : classes) {
    
    
//            System.out.println(clazz.getName());
//        }
//    }
}

3. Anotaciones de configuración

Las clases marcadas con esta anotación se cargarán automáticamente en la configuración.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 配置类标注
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Config {
    
    
}

4. Modificación de la clase ClassLoader

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ConfigLoader {
    
    

    // 启动参数
    private static String[] args;

    public static String[] getArgs() {
    
    
        return args;
    }

    private static Map<Class, Object> configMap = new HashMap<>();

    /**
     * 单例
     */
    private ConfigLoader() {
    
    
        // 扫描该类下所有标注@Config的类
        List<Class<?>> classes = PackageScanner.scanPackage(ConfigLoader.class.getPackage().getName(), Config.class);
        for (Class<?> clazz : classes) {
    
    
            try {
    
    
                Constructor<?> constructor = clazz.getDeclaredConstructor();
                Object o = constructor.newInstance();
                configMap.put(clazz, o);
            } catch (NoSuchMethodException e) {
    
    
                e.printStackTrace();
            } catch (InvocationTargetException e) {
    
    
                e.printStackTrace();
            } catch (InstantiationException e) {
    
    
                e.printStackTrace();
            } catch (IllegalAccessException e) {
    
    
                e.printStackTrace();
            }
        }

    }

    private static final ConfigLoader INSTANCE = new ConfigLoader();

    public static ConfigLoader getInstance() {
    
    
        return INSTANCE;
    }


    /**
     * 加载配置:
     * 优先级高的会覆盖优先级低的
     * 运行参数 ->jvm参数 -> 环境变量 -> 配置文件 ->默认值
     */
    public void load(String[] args) {
    
    
        ConfigLoader.args = args;

        ConfigLoadHandlerChain configLoadHandlerChain =
                ConfigLoadHandlerChain.getInstance()
                        .addHandler(new ConfigLoadFromFile())// 配置文件
                        .addHandler(new ConfigLoadFromEnv())// 环境变量
                        .addHandler(new ConfigLoadFromJvm())// jvm参数
                        .addHandler(new ConfigLoadFromArgs());// 运行参数
        // 加载所有配置
        configMap.forEach((k, v) -> {
    
    
            configLoadHandlerChain.handle(v);
        });
    }

    public static <T> T getConfig(Class<T> c) {
    
    
        Object o = configMap.get(c);
        return o == null ? null : (T) o;
    }

}

5. Resultados de la verificación

ApplicationConfig y UserConfig están anotados con @Config. Escribamos una clase de prueba:

public class ConfigTest {
    
    

    public static void main(String[] args) {
    
    
        ConfigLoader.getInstance().load(args);
        ApplicationConfig config = ConfigLoader.getConfig(ApplicationConfig.class);
        System.out.println(config.getPort());
        UserConfig config2 = ConfigLoader.getConfig(UserConfig.class);
        System.out.println(config2.getRegisterUrl());
    }
}

Si crea una clase de configuración más adelante, puede anotarla directamente con @Config para cargar automáticamente la configuración.

Supongo que te gusta

Origin blog.csdn.net/A_art_xiang/article/details/132968406
Recomendado
Clasificación