【原创】Code Demo-自定义IoC容器

MyApplicationContext.java

package com.lagou.edu.factory;

import com.lagou.edu.annotation.MyAutowired;
import com.lagou.edu.annotation.MyComponent;
import com.lagou.edu.annotation.MyTransactional;
import com.lagou.edu.utils.StringUtils;
import org.springframework.beans.factory.annotation.Qualifier;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author sunli
 */
public class MyApplicationContext {

    /**
     * 自定义IoC容器
     */
    private Map<String, Object> instanceMap = new HashMap<>();

    /**
     * 声明式事务用代理对象工厂
     */
    private ProxyFactory proxyFactory;

    /**
     * 根据key取得自定义IoC容器中的实例化对象
     *
     * @param id 自定义IoC容器的key
     * @return 实例化对象
     */
    public Object getBean(String id) {
        return instanceMap.get(id);
    }

    /**
     * 创建指定包名下类的实例化对象,并放入自定义IoC容器
     *
     * @param packageName 包名
     */
    public void createBean(String packageName) throws Exception {

        // 获取指定包名下所有类的全限定名
        Set<String> classNames = getClassName(packageName, true);
        // 获取类加载器,用来加载包内类文件
        ClassLoader loader = Thread.currentThread().getContextClassLoader();

        if (classNames != null) {
            for (String className : classNames) {
                doCreateBean(loader.loadClass(className));
            }
        }
    }

    /**
     * 使用加载的类创建实例化对象
     *
     * @param clazz 用来实例化对象的类
     */
    private void doCreateBean(Class<?> clazz) throws Exception {

        Boolean hasMyComponent;
        String key;
        Object instance;

        /* 1 不实例化注解和接口,只实例化类 */
        if (clazz.isAnnotation() || clazz.isInterface()) {
            return;
        }

        /* 2 获取该类所有注解,过滤需要IoC的自定义注解的类(@MyComponent,@MyService,@MyRepository),然后创建实例化对象 */
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            hasMyComponent = false;

            /* 2.1 过滤@MyComponent */
            if (annotation.annotationType().equals(MyComponent.class)) {
                hasMyComponent = true;

            } else {
                for (Annotation subAnnotation : annotation.annotationType().getAnnotations()) {
                    // 过滤MyComponent的别名注解@MyService、@MyRepository
                    if (subAnnotation.annotationType().equals(MyComponent.class)) {
                        hasMyComponent = true;
                        break;
                    }
                }
            }

            /* 2.2 自定义注解的场合,进行实例化 */
            if (hasMyComponent) {
                /* 2.2.1 IoC容器中已经创建实例化对象的场合,退出 */
                if (hasBeanInstance(clazz)) {
                    return;
                }

                /* 2.2.2 获取自定义注解中value的值,作为IoC容器中的key */
                key = (String) annotation.getClass().getDeclaredMethod("value").invoke(annotation);
                // 如果没有设置value,就使用类名(小写首字母)
                if (StringUtils.isEmpty(key)) {
                    key = StringUtils.lowerFirst(clazz.getSimpleName());
                }

                /* 2.2.3 获取该类实例化对象,存入自定义的IoC容器 */
                instance = clazz.getDeclaredConstructor().newInstance();
                instanceMap.put(key, instance);

                /* 2.2.4 【直播视频要求】该类实现接口的场合,再将接口全限定名作为key放入容器,但实例化对象相同 */
                for (Class<?> interfaceClass : clazz.getInterfaces()) {
                    instanceMap.put(interfaceClass.getName(), instance);
                }

                break;
            }
        }
    }

    /**
     * 自动注入bean对象
     *
     * @throws IllegalAccessException
     */
    public void autowireBean() throws IllegalAccessException {
        for (Map.Entry<String, Object> map : instanceMap.entrySet()) {
            Object instance = map.getValue();
            populateBean(instance);
        }
    }

    /**
     * 维护对象属性依赖关系
     *
     * @throws IllegalAccessException
     */
    private void populateBean(Object instance) throws IllegalAccessException {

        /* 1 获取实例化对象的属性,并完成属性对象赋值 */
        Field[] fields = instance.getClass().getDeclaredFields();
        for (Field field : fields) {

            Boolean hasAutowired = false;
            Boolean hasQualifier = false;

            /* 1.1 获取该属性所有注解,过滤需要赋值的对象(@MyAutowired) */
            for (Annotation annotation : field.getAnnotations()) {
                if (annotation.annotationType().equals(MyAutowired.class)) {
                    hasAutowired = true;

                    // 判断是否需要根据名称赋值
                    if (annotation.annotationType().equals(Qualifier.class)) {
                        hasQualifier = true;
                    }

                    break;
                }
            }

            /* 1.2 需要赋值的场合,先使用key查找自定义IoC容器中的实例化对象,然后完成注入操作 */
            if (hasAutowired) {
                // 打开属性访问权限
                field.setAccessible(true);

                // 该属性已经注入过的场合,退出
                if (field.get(instance) != null) {
                    return;
                }

                String id;
                Object bean;

                /* 1.2.1 获取key */
                if (hasQualifier) {
                    // 需要按照名称查找的场合,获取名称
                    id = field.getAnnotation(Qualifier.class).value();
                } else {
                    // 以外的场合,使用类型查找
                    id = StringUtils.lowerFirst(field.getType().getSimpleName());
                }

                /* 1.2.2 根据key获取自定义IoC容器中的实例化对象 */
                bean = getBean(id);

                /* 1.2.3 【直播视频要求】如果以上步骤没有查找到(该属性为接口类型),则使用接口全限定名称查找 */
                if (bean == null) {
                    id = field.getType().getName();
                    bean = getBean(id);
                }

                /* 1.2.4 完成注入操作 */
                field.set(instance, bean);
                // 关闭属性访问权限
                field.setAccessible(false);

                /* 1.2.5 【直播视频要求】使用递归方式维护多层依赖关系 */
                populateBean(field);
            }
        }
    }

    /**
     * 创建处理事务的代理对象,然后替换自定义IoC容器中原对象
     */
    public void createTransactionProxyBean() {

        Object proxyBean;

        // 获取创建声明式事务用代理对象的工厂
        if (proxyFactory == null) {
            proxyFactory = (ProxyFactory) getBean("proxyFactory");
        }

        for (Map.Entry<String, Object> map : instanceMap.entrySet()) {
            Object instance = map.getValue();
            proxyBean = doCreateTransactionProxyBean(instance);
            // 替换自定义IoC容器中原对象
            instanceMap.put(map.getKey(), proxyBean);
        }
    }

    /**
     * 根据实例化对象创建处理事务的代理对象
     *
     * @param instance 实例化对象
     * @return 返回代理对象
     */
    private Object doCreateTransactionProxyBean(Object instance) {

        /* 1 获取声明式事务注解,没有的场合,退出 */
        Annotation myTransactional = instance.getClass().getAnnotation(MyTransactional.class);
        if (myTransactional == null) {
            return null;
        }

        /* 2【直播视频要求】该实例实现接口的场合使用jdk原生动态代理技术,否则使用cglib方式生成代理对象,最后替换原对象 */
        Class<?>[] interfaces = instance.getClass().getInterfaces();
        if (interfaces != null && interfaces.length > 0) {
            return proxyFactory.getJdkProxy(instance);
        } else {
            return proxyFactory.getCglibProxy(instance);
        }
    }

    /**
     * 检查是否已创建该类实例
     *
     * @param clazz 需要检查的类
     * @return 检查结果
     */
    private Boolean hasBeanInstance(Class<?> clazz) {
        for (Map.Entry<String, Object> map : instanceMap.entrySet()) {
            // 通过比较实例化对象类名来确定容器中是否创建过
            if (map.getValue().getClass().getName().equals(clazz.getName())) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取某包下所有类
     *
     * @param packageName 包名
     * @param isRecursion 是否遍历子包
     * @return 类的完整名称
     */
    private Set<String> getClassName(String packageName, boolean isRecursion) {
        Set<String> classNames = null;
        ClassLoader loader = Thread.currentThread().getContextClassLoader();

        String packagePath = packageName.replace(".", "/");

        URL url = loader.getResource(packagePath);
        if (url != null) {
            String protocol = url.getProtocol();
            if (protocol.equals("file")) {
                classNames = getClassNameFromDir(url.getPath(), packageName, isRecursion);
            } else if (protocol.equals("jar")) {
//                JarFile jarFile = null;
//                try {
//                    jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
//                } catch (Exception e) {
//                    e.printStackTrace();
//                }
//
//                if (jarFile != null) {
//                    getClassNameFromJar(jarFile.entries(), packageName, isRecursion);
//                }
            }
        } else {
            /*从所有的jar包中查找包名*/
//            classNames = getClassNameFromJars(((URLClassLoader) loader).getURLs(), packageName, isRecursion);
        }

        return classNames;
    }

    /**
     * 从项目文件获取某包下所有类
     *
     * @param filePath    文件路径
     * @param packageName 包名
     * @param isRecursion 是否遍历子包
     * @return 类的完整名称
     */
    private Set<String> getClassNameFromDir(String filePath, String packageName, boolean isRecursion) {
        Set<String> className = new HashSet<>();
        File file = new File(filePath);
        File[] files = file.listFiles();
        for (File childFile : files) {
            if (childFile.isDirectory()) {
                if (isRecursion) {
                    className.addAll(getClassNameFromDir(childFile.getPath(), packageName + "." + childFile.getName(), isRecursion));
                }
            } else {
                String fileName = childFile.getName();
                if (fileName.endsWith(".class") && !fileName.contains("$")) {
                    className.add(packageName + "." + fileName.replace(".class", ""));
                }
            }
        }

        return className;
    }

//    /**
//     * @param jarEntries
//     * @param packageName
//     * @param isRecursion
//     * @return
//     */
//    private Set<String> getClassNameFromJar(Enumeration<JarEntry> jarEntries, String packageName, boolean isRecursion) {
//        Set<String> classNames = new HashSet<String>();
//
//        while (jarEntries.hasMoreElements()) {
//            JarEntry jarEntry = jarEntries.nextElement();
//            if (!jarEntry.isDirectory()) {
//                /*
//                 * 这里是为了方便,先把"/" 转成 "." 再判断 ".class" 的做法可能会有bug
//                 * (FIXME: 先把"/" 转成 "." 再判断 ".class" 的做法可能会有bug)
//                 */
//                String entryName = jarEntry.getName().replace("/", ".");
//                if (entryName.endsWith(".class") && !entryName.contains("$") && entryName.startsWith(packageName)) {
//                    entryName = entryName.replace(".class", "");
//                    if (isRecursion) {
//                        classNames.add(entryName);
//                    } else if (!entryName.replace(packageName + ".", "").contains(".")) {
//                        classNames.add(entryName);
//                    }
//                }
//            }
//        }
//
//        return classNames;
//    }
//
//    /**
//     * 从所有jar中搜索该包,并获取该包下所有类
//     *
//     * @param urls        URL集合
//     * @param packageName 包路径
//     * @param isRecursion 是否遍历子包
//     * @return 类的完整名称
//     */
//    private Set<String> getClassNameFromJars(URL[] urls, String packageName, boolean isRecursion) {
//        Set<String> classNames = new HashSet<String>();
//
//        for (int i = 0; i < urls.length; i++) {
//            String classPath = urls[i].getPath();
//
//            //不必搜索classes文件夹
//            if (classPath.endsWith("classes/")) {
//                continue;
//            }
//
//            JarFile jarFile = null;
//            try {
//                jarFile = new JarFile(classPath.substring(classPath.indexOf("/")));
//            } catch (IOException e) {
//                e.printStackTrace();
//            }
//
//            if (jarFile != null) {
//                classNames.addAll(getClassNameFromJar(jarFile.entries(), packageName, isRecursion));
//            }
//        }
//
//        return classNames;
//    }
}

ProxyFactory.java

package com.lagou.edu.factory;

import com.lagou.edu.annotation.MyAutowired;
import com.lagou.edu.annotation.MyComponent;
import com.lagou.edu.utils.TransactionManager;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author 应癫
 */
@MyComponent("proxyFactory")
public class ProxyFactory {

    @MyAutowired
    private TransactionManager transactionManager;

    /**
     * Jdk动态代理
     *
     * @param obj 委托对象
     * @return 代理对象
     */
    public Object getJdkProxy(Object obj) {

        // 获取代理对象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object result;

                        try {
                            // 开启事务(关闭事务的自动提交)
                            transactionManager.beginTransaction();

                            result = method.invoke(obj, args);

                            // 提交事务
                            transactionManager.commit();
                        } catch (Exception e) {
                            e.printStackTrace();
                            // 回滚事务
                            transactionManager.rollback();

                            // 抛出异常便于上层servlet捕获
                            throw e;
                        }

                        return result;
                    }
                });
    }

    /**
     * 使用cglib动态代理生成代理对象
     *
     * @param obj 委托对象
     * @return
     */
    public Object getCglibProxy(Object obj) {
        return Enhancer.create(obj.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object result;
                try {
                    // 开启事务(关闭事务的自动提交)
                    transactionManager.beginTransaction();

                    result = method.invoke(obj, objects);

                    // 提交事务
                    transactionManager.commit();
                } catch (Exception e) {
                    e.printStackTrace();
                    // 回滚事务
                    transactionManager.rollback();

                    // 抛出异常便于上层servlet捕获
                    throw e;

                }
                return result;
            }
        });
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342