首页 欧洲联赛正文


本文作者:加耀            投稿  

手写IOC腹黑少爷卖萌控容器了解一下!

信任每一个java程序员在面试阅历中,都被面试官问到过AOP和IOC,用官方的言语来答复AOP和IOC,那便是切面编程和操控回转及依靠注入。

详细什么是IOC呢,IOC(inversion of control)其意义是操控回转,即咱们平常经过NEW出来的目标交由IOC来办理,当咱们在代码中经过注入注解进行目标符号时,IOC容器会将对应的目标进行特点注入,这样就省去了咱们自己NEW的进程,消除了大良耦合代码;

在这儿,将目标实例交给IOC容器办理的进程能够称为操控回转,而对被IOC办理的目标中有特别标识的需求进行注入的特点目标进行接连相关称为依靠注入DI(depend injection 依靠注入);操控回转和依靠注入是相得益彰的,统称为IOC;

在日常编码运用Spring结构时,咱们一般会运用注解@Autowried或许是@Resource来符号当时类所需求注入的目标,省去了咱们直接NEW的进程,然后削减代码耦合。但假如仅仅是帮咱们NEW咱们需求的目标,直接称之为注入即可,何来的依靠注入呢。

要点就在这个”依靠”二字上;举个代码中的简略的比方,比方咱们在拜访操控层注入了效劳层的类或许是接口,咱们假如是经过new的办法来获取到效劳层的类的实唱吧下载,用简略代码完结IOC容器,新化气候例,这样拜访操控层中注入的耐久层的目标则为null;直接调用则会报错空指针反常;

而依靠注入则是咱们在拜访操控层注入事务效劳层代码时,会将效劳层中所注入的接口或许类顺次注入,这儿边存在一个依靠联系,将依靠的需求注入的类递归注入。这便是依靠注入称号的来历;

就像咱们吃饭的时分,当咱们需求米饭时,咱们能够自己去盛米饭,这个进程是咱们主动的;可是假如结合IOC,那么咱们只需求通知效劳员,需求米饭,然后效劳员就会盛一碗米饭过来,这个动作就交给效劳员去做,而省去了自己去盛的这个进程。而且,效劳员在上米饭的时分,还将饭碗和筷子一块送过来了,米饭需求用碗来盛,需求用筷子吃,这便是依靠注入的表现;

关于IOC和AOP我也写过相关的文章,假如还不了解的同学无妨先去阅览一下

  • Spring IOC知识点一扫而光!

  • Spring AOP便是这么简略啦

在手写IOC容器之前,咱们需求把握一些java根底的知识点,别离有:注解、反射、IO流等知识点;咱们先来看一下IOC容器的全体流程

image-20190325092707843

首要,咱们先创立一个Maven项目,然后在项目的resources目录下增加一个装备文件application.properties,在装备文件中指定需求扫描的包途径

image-20190325092746167

然后咱们界说一些注解,别离表明拜访操控层、事务效劳层、数据耐久层、依靠注入注解、获取装备文件注解,代码如下:

依靠注入注解@MyAutowired

/**
 * 类 名: MyAutowired
 * 描 述: 注入注解--将需求交给IOC容器办理的类放置 -- 界说在特点上的
 */

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface MyAutowired {

    String value() default "";

}

拜访操控层注解@MyController

事务效劳层注解@MyService

/**
 * 类 名: MyService
 * 描 述: 自界说注解 -- 效劳层 -- 界说在类、接口、枚举上的
 */

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface MyService {

    String value() default "";

}

数据耐久层注解@MyMapping:

/**
 * 类 名: MyService
 * 描 述: 自界说注解 -- 数据耐久层 -- 界说在类、接口、枚举上的
 */

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface MyMapping {

    String value() default "";

}

装备文件获取注解@Value:

/* 类 名: Value
 * 描 述: 获取装备文件中的键值对
 */

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface Value {

    String value() default "";

}

界说完注解后,咱们能够开端编写代码了。依据上面的流程图,此刻应该先获取读取装备文件,从装备文件中获取需求扫描的包途径。

咱们先写一个装备文件东西类ConfigurationUtils,代码如下:

/**
 * 类 名: ConfigurationUtils
 * 描 述:
 */

public class ConfigurationUtils {

    /**
     * 项目装备文件信息
     */

    public static Properties properties;

    public ConfigurationUtils(String propertiesPath) {
        properties = this.getBeanScanPath(propertiesPath);
    }

    /**
     * @author: JiaYao
     * @demand: 读取装备文件
     */

    private Properties getBeanScanPath(String propertiesPath) {
        if (StringUtils.isEmpty(propertiesPath)) {
            propertiesPath = "/application.properties";
        }
        Properties properties = new Properties();
        // 经过类的加载器获取具有给定称号的资源
        InputStream in = ConfigurationUtils.class.getResourceAsStream(propertiesPath);
        try {
            System.out.println("正在加载装备文件application.properties");
            properties.load(in);
            return properties;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
      尔后不再爱你;              in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return properties;
    }

    /**
     * @author: JiaYao
     * @demand: 依据装备文件的key获取value的值
     */

    public static Object getPropertiesByKey(String propertiesKey) {
        if (properties.size() > 0) {
         地球的位面私运商人;   return properties.get(propertiesKey);
        }
        return null;
    }

}

上述代码中,咱们经过读取装备文件获取到装备文件信息的key-value键值对;然后咱们再依据装备文件中指定的扫描包途径进行包扫描

拿到包扫描途径后,咱们就能够获取到当时途径下的文件信息及文件夹信息,咱们将当时途径下一切以.class结束的文件增加到一个Set调集中进行存储。代码如下:

/**
 * @author: JiaYao
 * @demand: 类加载器
 */

private void classLoader() throws Clasw216ssNotFoundException, InstantiationException, IllegalAccessException {
    // 加载装备文件一切装备信息
    new ConfigurationUtils(null);
    // 获取扫描包途径
    String classScanPath = (String) ConfigurationUtils.properties.get("ioc.scan.path");
    if (StringUtils.isNotEmpty(classScanPath)) {
        classScanPath = classScanPath.replace(".""/");
    } else {
        throw new RuntimeException("请装备项目包扫描途径 ioc.scan.path");
    }
    // 扫描项目根目录中一切的class文件
    getPackageClassFile(classScanPath);
    for (String className : classSet) {
        addServiceToIoc(Class.forName(className));
    }
    // 获取带有MyService注解类的一切的带MyAutowired注解的特点并对其进行实例化
    Set beanKeySet = iocBeanMap.keySet();
    for (String beanName : beanKeySet) {
        addAutowiredToField(iocBeanMap.get(beanName));
    }
}

咱们需求扫描项目途径中一切以.class结束的文件,将其增加到一个大局的Set调集中,代码如下:

/**
 * 类调集--寄存一切的全约束类名
 */

private Set classSet = new HashSet();

/**
 * @author: JiaYao
 * @demand: 扫描项目根目录中一切的class文件
 */

private void getPackageClassFile(String packageName) {
    URL&nb唱吧下载,用简略代码完结IOC容器,新化气候sp;url = this.getClass().getClassLoader().getResource(packageName);
    File file = new File(url.getFile());
    if (file.exists() && file.isDirectory()) {
        File[] files = file.listFiles();
   成人女子;     for (File fileSon : files) {
            if (fileSon.isDirectory()) {
                // 递归扫描
                getPackageClassFile(packageName + "/" + fileSon.getName());
     &nb唱吧下载,用简略代码完结IOC容器,新化气候sp;      } else {
                // 是文件而且是以 .class结束
      &nb芷蕙sp;         if (fileSon.getName().endsWith(".class")) {
                    System.out.println("正在加载: " + packageName.replace("/"".") + "." + fileSon.getName());
                    classSet.add(packageName.replace("/"".") + "." + fileSon.getName().replace(".class"""));
                }
            }
        }
    } else {
        throw new RuntimeException("没有找到需求扫描的文件目录");
    }
}

咱们将一切的类的字节码目标都存储到一个大局的Set调集中之后,遍历这个set调集,获取在类上有指定注解的类,并将其交给IOC容器;咱们先界说一个安全的Map用来存储这些目标

/**
 * IOC容器 如: String(loginController) --> Object(loginController实例)
 */

private Map iocBeanMap = new ConcurrentHashMap(32);

/**
 * @author: JiaYao
 * @demand: 操控回转
 */

private void addServiceToIoc(Class classZ) throws IllegalAccessException, InstantiationException {
  &n体位引流bsp; // 预留方位,之后优化
    if (classZ.getAnnotation(MyController.class) != null) {
        iocBeanMap.put(toLowercaseIndex(classZ.getSimpleName()), classZ.newInstance());
        System.out.println("操控回转拜访操控层:" + toLowercaseIndex(classZ.getSimpleName()));
    } else if (classZ.getAnnotation(MyService.class) != null) {
        // 将当时类交由IOC办理
        MyService myService = (MyService) classZ.getAnnotation(MyService.class);
        iocBeanMap.put(StringUtils.isEmpty(myService.value()) ? toLowercaseIndex(classZ.getSimpleName()) : toLowercaseIndex(myService.value()), classZ.newInstance());
        System.out.println("操控回转效劳层:" + toLowercaseIndex(classZ.getSimpleName()));
    } else if (classZ.getAnnotation(MyMapping.class) != null) {
        MyMapping myMapping = (MyMapping) classZ.getAnnotation(MyMapping.class);
     &n愿望国度bsp;  iocBeanMap.put(StringUtils.isEmpty(myMapping.value()) ? toLowercaseIndex(classZ.getSimpleName()) : toLowercaseIndex(myMapping.value()), classZ.newInstance());
    蒋鸣慧    System.out.println("操控回转耐久层:" + toLowercaseIndex(classZ.getSimpleName()));
    }
}

/**
 * @author: JiaYao
 * @demand: 类名首字母转小写
 */

public static String toLowercaseIndex(String name) {
 &孔凡纯nbsp;  if (StringUtils.isNotEmpty(name)) {
        return name.substring(01).toLowerCase() + name.substring(1, name.length());
    }
    return name;
}

然后咱们再遍历这个IOC容器,获取到每一个类的实例,判别里边是有有依靠其他的类的实例,即依靠注入,代码如下:

     /**
     * @author: JiaYao
     * @demand: 依靠注入
     */

    private void addAutowiredToField(Object obj) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.getAnnotation(MyAutowired.class) != null) {
                field.setAccessible(true);
                MyAutowired myAutowired = field.getAnnotation(MyAutowired.class);
                Class fieldClass = field.getType();
                // 接口不能被实例化,需求对接口进行特别处理获取其子类,获取一切完结类
          七月冤灵      if (fieldClass.isInterface()) {
              兆加页      // 假如有指定获取子类名
 &nbs唱吧下载,用简略代码完结IOC容器,新化气候p;                  if (StringUtils.isNotEmpty(myAutowired.value())) {
                        field.set(obj, iocBeanMap.get(myAutowired.value()));
                    } else {
                        List list = findSuperInterfaceByIoc(field.getType());
                        if (list != null && list.size() > 0) {
                            if (list.size() > 1) {
                      &唱吧下载,用简略代码完结IOC容器,新化气候nbsp;         throw new RuntimeException(obj.getClass() + "  注入接口 " + field.getType() + "   失利,请在注解中指定需求注入的详细完结类");
                            } else {
                                field.set(obj, list.get(0));
                              美智广子;  // 递归依靠注入
                                addAutowiredToField(field.getType());
                            }
                        } else {
                            throw new RuntimeException("当时类" + obj.getClass() + "  不能注入接口 " + field.getType().getClass() + "  , 接口没有完结类不能被实例化");
                        }
                    }
                } else {
             截获芒果果核象甲       String beanName = StringUtils.isEmpty(myAutowired.value()) ? toLowercaseIndex(field.getName()) : toLowercaseIndex(myAutow唱吧下载,用简略代码完结IOC容器,新化气候ired.value());
                    Object beanObj = iocBeanMap.get(beanName);
                    field.set(obj, beanObj == null ? field.getType().newInstance() : beanObj);
                    System.out.println("依靠注入" + field.getName());
//                递归依靠注入
                }
                addAutowiredToField(field.getType());
            }
            if (field.getAnnotation(Value.class) != null) {
                field.setAccessible(true);
                Value value = field.getAnnotation(Value.class);
                field.set(obj, StringUtils.isNotEmpty(value.value()) ? getPropertiesByKey(value.value()) : null);
                System.out.println("注入装备文件  " + obj.getClass() + " 加载装备特点" + value.value());
            }
        }
&miya智妍nbsp;   }

上述代码中,咱们经过判别带指定注解的类里边是否有注入其它的类,然后进行递归注入;可是有一个问题,接口和抽象类不能被实例化,所以在处理接口时,就呈现了一个难题。一般咱们习气注入接口,可是接口不能被实例化,咱们需求对接口赋值它的子类,怎样获取到接口的完结类呢?

翻遍了JDK1.8的API,没有找到能够供给这样的办法。所以这儿做了写了一个循环,遍历IOC容器中的每一个类是否有完结接口,假如是相同的接口则记载,可是这样做会十分耗费功用的,其代码如下:

/**
 * @author: JiaYao
 * @demand: 判别需求注入的接口一切的完结类
 */

private List findSuperInterfaceByIoc(Class classz) {
 &nbs贞德簿本p;  Set beanNameList = iocBeanMap.keySet();
    ArrayList objectArrayList = new ArrayList<>();
    for (String beanName : beanNameList) {
        Object obj = iocBeanMap.get(beanName);
        Class[] interfaces = obj.getClass().getInterfaces();
        if (useArrayUtils(interfaces, classz)) {
            objectArrayList.add(obj);
        }
    }
    return objectArrayList;
}

关于接口的注入,暂时还没有想到有什么好的办法能够优化,也不知道Spring是怎样做的。当一个接口有多个完结类时,需求用过自界说称号进行交给IOC办理和注入注解进行获取。

到到这儿,咱们就完结了整个IOC容器的创立以及依靠注入功用了。咱们能够写一个简略的测验类来试一下咱们写的这个IOC容器;

测验代码:拜访操控层

@MyController
public class LoginController {

    @Value(value = "ioc.scan.pathTest")
    private String test;

    @MyAutowired(value = "test")
    private LoginService loginService;

    public String login() {
        return loginService.login();
    }

}

测验代码:事务效劳接口层:

public interface LoginService {

    String login();
}

测验代码:详细效劳层(这儿尝试了写两个完结类,多态状况下)

@MyService(value = "test")
public class LoginServiceImpl implements LoginService {

    setma;@MyAutowired
    private LoginMapping loginMapping;

    唱吧下载,用简略代码完结IOC容器,新化气候;@Override
    public String login() {
        return loginMapping.login();
 &傍晚改编的醉酒歌nbsp;  }
}

@MyService
public class TestLoginServiceImpl implements LoginService {

    @Override
    public String login() {
        return "测验多态状况下依靠注入";
    }
}

测验代码:数据耐久层接口层

public interface LoginMapping {

    String login();
}

测验代码:数据耐久层详细耐久层

@MyMapping
public class LoginMappingImpl implements LoginMapping {

    @Override
    public String login() {
        return "项目发动成功";
    }
}

然后咱们写一个发动类PlatformApplication

/**
 * 类 名: PlatformApplication
 *
 * @author: jiaYao
 */

public class PlatformApplication {

    public static void main(String[] args) throws Exception {
        // 从容器中获取目标(主动首字母小写)
        MyApplicationContext applicationContext =&n韩漫君bsp;new MyApplicationContext();
        LoginController loginController = (LoginController) applicationContext.getIocBean("LoginController");
        String login = loginController.login();
        System.out.println(login);
    }

}

发动程序后咱们经过断点调查一下代码状况

image-20190325093910145

操控台输出日志信息如下:

正在加载装备文件application.properties
正在加载: cn.jiayao.platform.common.MyApplicationContext.class
正在加载: cn.jiayao.platform.config.ConfigurationUtils.class
正在加载: cn.jiayao.platform.core.annotation.MyAutowired.class
正在加载: cn.jiayao.platform.core.annotation.MyController.class
正在加载: cn.jiayao.platform.core.annotation.MyMapping.class
正在加载: cn.jiayao.platform.core.annotation.MyService.class
正在加载: cn.jiayao.platform.core.annotation.Value.class
正在加载: cn.jiayao.platform.modular.controller.LoginController.class
正在加载: cn.jiayao.platform.modular.dao.impl.LoginMappingImpl.class
正在加载: cn.jiayao.platform.modular.dao.LoginMapping.class
正在加载: cn.jiayao.platform.modular.service.impl.LoginServiceImpl.class
正在加载: cn.jiayao.platform.modular.service.impl.TestLoginServiceImpl.class
正在加载: cn.jiayao.platform.modular.service.LoginService.class
正在加载: cn.jiayao.platform.PlatformApplication.class
正在加载: cn.jiayao.platform.utils.MyArrayUtils.class
操控回转拜访操控层:loginController
操控回转耐久层:loginMappingImpl
操控回转效劳层:loginServiceImpl
操控回转效劳层:testLoginServiceImpl
注入装备文件  class cn.jiayao.platform.modular.controller.LoginController 加载装备特点ioc.scan.pathTest
项目发动成功

这样,一个简略的IOC容器就创立完结了;

可是此处还有几点需求再次优化一下,现在关于Maven中引证的jar包无法进行注入,因为没有将jar包中的目标交给IOC容器办理,然后便是对接口的注入,因为接口的特别性,不能被实例化,怎样高效的获取到接口的完结类这个问题还待优化;

Gitlab地址:

  • https://gitlab.com/qingsongxi/ioc

最终

乐于输出干货的Java技能大众号:Java3y。大众号内有200多篇原创技能文章、海量视频资源、精巧脑图,无妨来重视一下!

有协助?在看!转发!


引荐阅览:

  •  一个二本大佬的生长之路

  • 系统盘为啥往往是C盘?软件为啥期望把自己装在C盘上?

  • Java 8的Stream代码,你能看懂吗? 

  • 带你搭一个SpringBoot+SpringData JPA的Demo

  •  怎样了解axis?

版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。