Java注解和反射简单实现IoC

Java注解和反射简单实现IoC

之前学spring boot时, 就一直对依赖注入的实现很好奇, 当时没有学会注解和反射, 大学Java教材只到1.7的版本, 都不知道注解是什么玩意儿。之后学了注解和反射后, 就想着自己实现一个用注解自动装配的样例当作练习。

IoC

我们传统的方式创建对象是通过new来主动创建对象的, 但这样会造成代码耦合度过高: 假设我们有100个地方使用了A接口的某个实现类B, 代码经过版本跟新后我们废弃掉了实现类B, 而改用新的实现类C, 那么我们怎么改呢? 用这种传统的方式我们就只能够手动的一个一个修改, 但这样太麻烦了, 而且违反了开闭原则:

IoC_传统呢哇创建对象
IoC不是主动的new对象, 而是在初始化的时候, IoC 容器通过反射将对象注入进去, 所以在修改代码的时候, 我们不需要修改每个使用了该接口的地方, 而是只要将IoC容器中的对象替换掉即可:

IoC容器注入对象
在spring boot中, 我们只需要在需要注入的地方加入@Autowired注解spring容器就会自动为其装配对象。

实例编写

为了更好的理解IoC, 同时作为对Java 注解及反射的练习, 编写了一个用注解注入对象的实例:
首先是注解部分:

/**
 * 注解扫描
 * 扫描value中的包下的所有注解
 *
 * @author Bobasyu
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyScan {
    String value() default "";
}
/**
 * 标有该注解的类会在IoC容器中产生相对应的对象
 *
 * @author bobasyu
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCompetent {
}
/**
 * 标有这个注解的变量将会被注入相对应的对象
 * @author Bobasyu
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutoWired {
}

@MyScan注解标记在启动类上, 在启动后IoC容器会递归扫描该注解的值所对应的包目录及其子目录(默认为启动类对应目录), 检查里面的类是否标记有@MyCompetent注解, 若标有该注解, 则将该类实例化存入容器, 同时, 检查容器中的对象实例, 若对象有属性标记有@MyAutoWired注解则从容器中找到其对应的对象注入进去。

其次是IoC容器类, 该容器使用一个HashMap存放IoC容器中的对象实例, 可以通过类名来获取对象:

/**
 * IoC容器
 *
 * @author Bobasyu
 */
public class MyContext {
    /**
     * 装有 [类名 : 类对应的实例] 的容器
     */
    private Map<Class, Object> objectMap;

    protected MyContext() {
        this.objectMap = new HashMap<>();
    }

    /**
     * 添加类,并创建相应的实例对象到map容器中
     *
     * @param clazz 所添加的类对象
     */
    protected void push(Class clazz) {
        try {
            objectMap.put(clazz, clazz.newInstance());
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取类所继承的接口
     *
     * @param clazz 通过类实例反射来所获取类所继承的的接口
     * @return 所获取的类所继承的接口
     */
    public Object getObject(Class clazz) {
        if (objectMap.containsKey(clazz)) {
            return this.getObjectMap().get(clazz);
        }
        for (Class c : objectMap.keySet()) {
            for (Class i : c.getInterfaces()) {
                if (clazz.equals(i)) {
                    return objectMap.get(c);
                }
            }
        }
        return null;
    }

    /**
     * 获取mao容器键的集合
     *
     * @return
     */
    protected Set<Class> classSet() {
        return objectMap.keySet();
    }

    public Map<Class, Object> getObjectMap() {
        return this.objectMap;
    }

}

然后是IoC的功能实现, 其内部有IoC容器, 主要功能就是扫描目录结构并注入对象:

/**
 * IoC 功能实现
 * @author Bobasyu
 */
public class MyApplication {
    private MyContext myContext;

    public MyApplication() {
        this.myContext = new MyContext();
    }

    public void run(Class clazz) {
        MyScan myScan = (MyScan) clazz.getAnnotation(MyScan.class);
        String pack = myScan.value();
        if (pack == null || "".equals(pack)) {
            pack = clazz.getPackage().toString().replace("package ", "");
        }
        // 先把包名转换为路径,首先得到项目的classpath
        String classpath = clazz.getResource("/").getPath().replace("/", File.separator);
        //然后把我们的包名basPath转换为路径名
        String basePath = classpath + pack.replace(".", File.separator);
        init(pack, basePath);
    }

    /**
     * 初始化, 获取pack包目录下所有的类的名称
     *
     * @param pack     被扫描的包的目录
     * @param basePath pack所在的绝对路径
     */
    private void init(String pack, String basePath) {
        List<String> filePath = new ArrayList<>();
        FileUtil.findFileList(new File(basePath), filePath);
        basePath = FileUtil.replaceBasePath(basePath);
        List<Class> classList = getClassList(filePath, pack, basePath);
        addObject(classList);
        injectionObject();
    }

    /**
     * 根据Class列表将带有 @MyCompetent 注解的类创建实体添加到容器中
     *
     * @param classList 需要添加到容器的Class列表
     */
    private void addObject(List<Class> classList) {
        classList.parallelStream().forEach(clazz -> {
            Annotation annotation = clazz.getAnnotation(MyCompetent.class);
            if (annotation != null && clazz != null) {
                this.getMyContext().push(clazz);
            }
        });
    }

    /**
     * 将容器中的实例中标记有@MyAutoWired注解的属性注入相应的对象
     */
    private void injectionObject() {
        this.getMyContext().classSet().parallelStream().forEach(clazz -> {
            Field[] fields = clazz.getDeclaredFields();
            Arrays.stream(fields).filter(field -> field.getAnnotation(MyAutoWired.class) != null)
                    .forEach(field -> {
                        try {
                            field.setAccessible(true);
                            Object fieldObject = this.myContext.getObject(field.getType());
                            Object clazzObject = this.myContext.getObject(clazz);
                            field.set(clazzObject, fieldObject);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    });
        });
    }

    /**
     * 根据路径获取类对象
     *
     * @param fileName 类的文件名列表
     * @param pack     包名
     * @param basePath 包的目录的绝对路径
     * @return 类对象列表
     */
    private List<Class> getClassList(List<String> fileName, String pack, String basePath) {
        return fileName.parallelStream().map(str -> {
            String className = str.replace(basePath, pack + ".")
                    .replace(".class", "")
                    .replace(File.separator, ".");

            try {
                return Class.forName(className);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            return null;
        }).collect(Collectors.toList());
    }

    private MyContext getMyContext() {
        return myContext;
    }

}

下面编写测试用接口及其实现类:

/**
 * @author Bobasyu
 */
public interface MyTestInterface {

    /**
     * 测试用
     */
    void test();
}
/**
 * @author Bobasyu
 */
@MyCompetent
public class MyTestInterfaceImpl implements MyTestInterface {

    @Override
    public void test() {
        System.out.println("test");
    }
}

启动类:

/**
 * @author Boba
 */

@MyScan("cn.bobasyu.test")
@MyCompetent
public class Main {

    @MyAutoWired
    private static MyTestAutowired myTestAutowired;

    public static void main(String[] args) {
        new MyApplication().run(Main.class);
        myTestAutowired.test();
    }
}

我们在启动类上标记了@MyScan("cn.bobasyu.test")注解, 其会在cn.bobasyu.test包对应目录及其子目录进行扫描, 发现类MainMyTestInterfaceImpl标记有@MyCompetent注解, IoC容器会将其注入到容器中, 键为该类实现的接口, 值为其实例对象。接着对容器中的实例进行检查, 发现Main的实例中存在属性MyTestAutowired myTestAutowired标有@MyAutoWired注解, 所以IoC容器会将内部存有的实现了MyTestAutowired接口的对象实例注入进去, 所以在调用过程中没有new新对象但不会报空指针异常。

此外, 还用到了一个工具处理类:


/**
 * 和文件有关的工具类
 *
 * @author Bobasyu
 */
public class FileUtil {
    public static String replaceBasePath(String basePath) {
        if (basePath.charAt(0) == File.separatorChar) {
            String str = "";
            for (int i = 1; i < basePath.length(); i++) {
                str += basePath.charAt(i);
            }
            basePath = str;
        }
        if (basePath.charAt(basePath.length() - 1) != File.separatorChar) {
            basePath += File.separator;
        }
        return basePath;
    }

    /**
     * 获取目录下的所有文件名称
     *
     * @param dir       目标文件或目录
     * @param fileNames 文件名列表, 包含文件路径
     */
    public static void findFileList(File dir, List<String> fileNames) {
        try {
            // 判断是否存在目录
            if (!dir.exists() || !dir.isDirectory()) {
                throw new FileNotFoundException("file path" + dir.getPath() + "not exist.");
            }
            // 读取目录下的所有目录文件信息
            String[] files = dir.list();
            // 循环,添加文件名或回调自身
            for (int i = 0; i < files.length; i++) {
                File file = new File(dir, files[i]);

                if (file.isFile()) {
                    // 如果文件是, 添加文件全路径名
                    fileNames.add(dir + "\\" + file.getName());
                } else {
                    // 如果是目录, 回调自身继续查询
                    findFileList(file, fileNames);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

代码链接:

https://github.com/Boba-Syu/Ioc_learning

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值