对于Spring框架的思考:模拟Spring + 注解 + 设计模式实现业务逻辑实现

581 阅读3分钟

场景:某一业务场景要处理好很多业务逻辑,而且可能会不断增加或增加更多的业务逻辑

class Service {
    void process() {
        this.before1(); // 在此之前1
        this.before2(); // 在此之前2
        this.baseProcess(); //核心逻辑
        this.after1(); // 在此之后1
        this.after2(); // 在此之后2
        ...
    }
}

这样每增加一个逻辑就要在此基础上增加代码(或许可以用代理或Spring-aop解决),如果每编写一个逻辑类只需加上自定义的注解,然后被Service#process方法扫描到并调用就方便了。

1. 定义一个业务逻辑BaseService接口或抽象类,定义三个方法。

定义接口是为了反射调用的时候方便类的初始化和函数参数的规范传递,避免一个实现一个写法,这里没有用接口interface而是抽象类。是否抽象类比接口被反射实现的时候更麻烦?

public abstract class BaseService {

    /* 逻辑1 */
    public String process1(Map param) {
        System.out.println("param = " + param);
        return null;
    }

    /* 逻辑2 */
    public String process2(Map param) {
        return null;
    }

}

2. 定义一个注解KanbanHolder

定义注解一定加生命周期注解@Retention(RetentionPolicy.RUNTIME),不然测试的时候识别不到...

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface KanbanHolder {
    KanbanHolderEnum value() default KanbanHolderEnum.KANBAN_HOLDER_ENUM1;
}

//随便写了个枚举,觉得必有大用~
public enum KanbanHolderEnum {
    KANBAN_HOLDER_ENUM1,
    KANBAN_HOLDER_ENUM2

}

3. 三个BaseService实现类,并标记注解

@KanbanHolder()
public class Process1Handler extends BaseService {

    @Override
    public String process1(Map param) {
        System.out.println(" Process1Handler.process1() .... ");
        return super.process1(param);
    }
}

@KanbanHolder(KanbanHolderEnum.KANBAN_HOLDER_ENUM2)
public class Process2Handler extends BaseService {

    @Override
    public String process2(Map param) {
        System.out.println(" Process2Handler.process2() .... ");
        return super.process2(param);
    }
}

@KanbanHolder(KanbanHolderEnum.KANBAN_HOLDER_ENUM1)
public class SynToLog extends BaseService {

    @Override
    public void saveLog (String val) {
        System.out.println("saveLogProcess = " + val);
    }

}

4. 扫描所有类,提炼标记了注解的类

核心方法Spring,很多奇淫巧计的根基。


private static final String CLASS_SUFFIX = ".class";
private static final String CLASS_FILE_PREFIX = File.separator + "classes" + File.separator;
private static final String PACKAGE_SEPARATOR = ".";
public static String packageName = "com.projectcycle.kanban";
public static List<BaseService> KanbanHolderContext = new ArrayList();

/*  */
public static void mainHole() throws Exception {
    // 获取packageName 下所有类路径
    List<String> classes = initClass();
    // 解析1 解析标记KanbanHolder的类
    for (String className: classes ) {
        Class<?> aClass = Class.forName(className);
        // 是否标注KanbanHolder.class注解 并且实现了 BaseService接口
        if (aClass.isAnnotationPresent(KanbanHolder.class) && BaseService.class.isAssignableFrom(aClass)) {
            // TODO 解析注解内容
            // 放入容器中
            KanbanHolderContext.add((BaseService) aClass.newInstance());
        }
    }
}

/* 扫描class */
private static List<String> initClass() {
    List<String> result = new ArrayList<String>();
    String suffixPath = packageName.replaceAll("\.", "/");
    ClassLoader loader = Thread.currentThread().getContextClassLoader();
    try {
        Enumeration<URL> urls = loader.getResources(suffixPath);
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            if (url != null) {
                String protocol = url.getProtocol();
                if ("file".equals(protocol)) {
                    String path = url.getPath();
                    result = getAllClassNameByFile(new File(path), true);
                }
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    return result;
}

private static List<String> getAllClassNameByFile(File file, boolean flag) {
    List<String> result = new ArrayList<String>();
    if (!file.exists()) {
        return result;
    }
    if (file.isFile()) {
        String path = file.getPath();
        // 注意:这里替换文件分割符要用replace。因为replaceAll里面的参数是正则表达式,而windows环境中File.separator="\"的,因此会有问题
        if (path.endsWith(CLASS_SUFFIX)) {
            path = path.replace(CLASS_SUFFIX, "");
            // 从"/classes/"后面开始截取
            String clazzName = path.substring(path.indexOf(CLASS_FILE_PREFIX) + CLASS_FILE_PREFIX.length())
                    .replace(File.separator, PACKAGE_SEPARATOR);
            if (-1 == clazzName.indexOf("$")) {
                result.add(clazzName);
            }
        }

    } else {
        File[] listFiles = file.listFiles();
        if (listFiles != null && listFiles.length > 0) {
            for (File f : listFiles) {
                if (flag) {
                    result.addAll(getAllClassNameByFile(f, flag));
                } else {
                    if (f.isFile()) {
                        String path = f.getPath();
                        if (path.endsWith(CLASS_SUFFIX)) {
                            path = path.replace(CLASS_SUFFIX, "");
                            // 从"/classes/"后面开始截取
                            String clazzName = path.substring(path.indexOf(CLASS_FILE_PREFIX) + CLASS_FILE_PREFIX.length())
                                    .replace(File.separator, PACKAGE_SEPARATOR);
                            if (-1 == clazzName.indexOf("$")) {
                                result.add(clazzName);
                            }
                        }
                        // todo jarFile
                    }
                }
            }
        }
    }
    return result;
}

通过这波操作就已经把标记了@KanbanHolder的三个实现类加载到 static修饰的 KanbanHolderContext容器中,斗胆用了容器这个词,其实就是static。所以Spring所初始化的方法就是static修饰的beanDefinitionMap

  1. 编写核心业务逻辑类,循环调用KanbanHolderContext容器中保存的BaseService类 在以下代码中循环调用KanbanHolderContext时,出现了问题,反射如何获取到实现类究竟实现了抽象类的哪个方法呢?

methods.png Debug 发现通过反射解析出的Process1Handler类中methods为抽象父类的所有方法。

百度了半天发现了问题,通过getMethods()获取的方是会返回超类和超类接口的方法,而用[getDeclaredMethods]可以获取到不包含超类的方法

Class#getDeclaredMethods.png

overrideAndNot.png 如上图,写完代码后发现,仅getDeclaredMethods() 还是不够,若是该类中即重写了super的方法(process1),又定义了自己的方法(process11),该如何去提炼出process1

这里只需调用重写了抽象父类的方法 通过反射来区分类中的方法是重写(override)还是普通方法

/** 获去子类重写超类的方法list */
public static List<Method> isOverride(Class b) {
    Method[] bfs = b.getDeclaredMethods();
    Method[] afs = b.getSuperclass().getDeclaredMethods();
    List<Method> methodsOverride = new ArrayList<Method>();
    for (Method bm : bfs) {
        for (Method am : afs) {
            if (bm.getName().equals(am.getName()) &&
                    bm.getReturnType().equals(am.getReturnType())) {
                Class[] bc = bm.getParameterTypes();
                Class[] ac = am.getParameterTypes();
                if (bc.length == ac.length) {
                    boolean isEqual = true;
                    for (int i = 0; i < bc.length; i++) {
                        if (!bc[i].equals(ac[i])) {
                            isEqual = false;
                            break;
                        }
                    }
                    if (isEqual) {
                        methodsOverride.add(bm);
                    }
                }
            }
        }
    }
    return methodsOverride;
}

拿到要调用的方法名后,接下来就可以提取出override方法了,并invoke

public static void main(String[] args) throws Exception {
    System.out.println(" 容器初始化中.... ");
    MyComponent.mainHole();
    System.out.println(" 容器初始化完毕.... 数量" + MyComponent.KanbanHolderContext);

    Map param = new HashMap(16);
    param.put("name", "foo");
    foo(param);
}

public static void foo(Map param) throws IllegalAccessException, InstantiationException {
    for (BaseService baseService : MyComponent.KanbanHolderContext) {
        // 取出重写了抽象父类的方法
        Class<? extends BaseService> aClass = baseService.getClass();
        List<Method> overrideMethods = isOverride(aClass);
        for (Method method : overrideMethods) {
            try {
                // invoke 
                method.invoke(baseService, param);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}

sout.png