IOC原理及自定义框架实现IOC功能(一)

IOC介绍

IOC全称是控制反转(Inversion of Control),又名依赖注入,其实这两个概念是一样的,控制反转是学术名词,晦涩难懂,为了便于理解,后来引入依赖注入方便理解。

为什么要使用ioc

在没有使用ioc时,项目的耦合性很大,往往在更新程序时,是不能直接删除原功能,要保留原来的部分,那么就要用一个新的类来替换旧的类,但这样一来,所有有关的类都需要修改,这样就很麻烦,而且忘记改一个,就会报错,程序运行不了,而ioc可以实现类与类解耦,层与层解耦,达到只需要更改一处,就可以更新整个项目,方便维护拓展。

如何实现ioc

要达到解耦的目的,就需要用到反射和接口来注入对象

1.创建两个类和一个测试类

//User类
public class User {
    private UserDAO userDAO;
     public UserDAO getUserDAO() {
        return userDAO;
    }
}

//UserDAO类
public class UserDAO {
}
//测试类
public class Demo {
    public static void main(String[] args) {
        User user = new User();
        user.getUserDAO();
    }
}

现在我想新建一个UserDAO1来代替UserDAO,那么按照以前的就必须修改User和Demo,如果关系还有别的类需要这两个类,那么改的更多,这就要用到反射来注入,首先让UserDAO和UserDAO1都实现IUserDAO,并且让User类的属性变为IUserDAO

public class User {
    private IUserDAO userDAO;

    public IUserDAO getUserDAO() {
        return userDAO;
    }

在demo类中用反射来注入userDAO1

 public static void main(String[] args) throws Exception {
        User user = new User();
        //如果要更换UserDAO类,只需要添加一个新的类让他实现IuserDAO,这里修改一下就可以了
        IUserDAO userDAO = new UserDAO1();
        //通过反射取得UserDAO的属性并把要更改的类的实例注入
        Class<? extends User> aClass = user.getClass();
        Field field = aClass.getDeclaredField("userDAO");
        field.setAccessible(false);
        field.set(user,userDAO);
    }

到这了,肯定会纳闷,这么麻烦我还不如一个个去new对象,一行代码就搞定了,但要是项目很大,类很多呢,一个个去找吗?万一找漏一个,就是bug。而且这个只需要写一次,以后再多的类,再多的项目都可以用这一个就可以了。

2.有很多属性甚至方法要注入呢?

那就要用到注解了,先自定义一个注解MyField,给两个属性,都给一个默认值

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyField {
     Class name() default Class.class;
     String value() default "";
}

然后将要注入的属性上加上注解,并且加上需要注入的值

public class User {
    
    @MyField(name = UserDAO1.class)
    public IUserDAO userDAO;
    
    @MyField(value = "张三")
    public String name;
    
    
    public IUserDAO getUserDAO() {
        return userDAO;
    }
}

在demo中对user中的元素进行注入

public static void main(String[] args) throws Exception {
        User user = new User();
        //遍历获取user中的所有元素
        Field[] declaredFields = user.getClass().getDeclaredFields();
        for (Field field:declaredFields){
            MyField annotation = field.getAnnotation(MyField.class);
            //判断元素上面是否有MyFiled注解
            if (annotation != null){
                //如果有就获取name和value的值
                Class userDAO1 = annotation.name();
                String name = annotation.value();
                //如果name的值不为空,且不等于默认值,就说明需要注入,直接注入
                if (userDAO1!=null && !userDAO1.equals(Class.class)){
                    field.set(user,userDAO1.newInstance());
                }
                //如果value值不为空且不等于默认值,就说明需要注入,直接注入
                if(name!=null && !name.equals("")){
                    field.set(user,name);
                }
            }
        }
        System.out.println(user);
    }

输出结果为User{userDAO=com.qf.demo.UserDAO1@511d50c0, name=‘张三’}
说明注入成功,现在我们发现如果要换UserDAO要修改的就只有注解上的值,而且注解严格来说不算java代码,这样等于说不需要修改java代码,达到解耦目的。

3.要注入的不只有User类,有很多类呢?

其实原理也一样,我在所有需要注入的类上都加一个自定义的注解SpringComponent,然后遍历整个文件夹,获取到所有的java文件,在将有SpringComponent注解的类直接传给上面写好的inject方法中

public static void main(String[] args) throws Exception {
        Injection injection = new Injection();
        injection.init("com.qf.demo");

    }

    public void init(String packageName) throws Exception {
        String basePath="D:\\program\\idea\\day52_demo\\src\\main\\java\\";
        //将传入的"com.qf.demo"转换成"com\qf\demo"
        String replace = packageName.replace(".", "/");
        File file = new File(basePath + "\\"+replace);
        //遍历该文件夹下所有文件
        File[] files = file.listFiles();
        for (File f:files){
            String name = f.getName();
            if(f.isFile()){
                //只有java文件才是哦们需要的,如果是文件就判断是不是以".java"结尾的
                if (name.endsWith(".java")){
                    //如果是就以“.”将文件名分割成"demo"和"java"两部分
                    String[] split = name.split("\\.");
                    String className = split[0];
                    Class<?> aClass = Class.forName(packageName +"." +className);
                  //判断类上是否有SpringComponent注解,如果有就调用inject方法,将类对象传入
                    SpringComponent annotation = aClass.getAnnotation(SpringComponent.class);
                    if (annotation != null){
                        inject(aClass);
                    }
                }
            }else{
                //如果不是文件就是文件夹,就使用递归循环该方法
                String childPath=packageName+"."+name;
                init(childPath);
            }
        }

    }

    public <T>void inject(Class<T> clazz) throws Exception {
        //创建传入类对象的实例对象
        T t = clazz.newInstance();
        Field[] declaredFields = clazz.getClass().getDeclaredFields();
        for (Field field:declaredFields){
            MyField annotation = field.getAnnotation(MyField.class);
            //判断元素上面是否有MyFiled注解
            if (annotation != null){
                //如果有就获取name和value的值
                Class aClass = annotation.name();
                String name = annotation.value();
                //如果name的值不为空,且不等于默认值,就说明需要注入,直接注入
                if (aClass!=null && !aClass.equals(Class.class)){
                    field.set(t,aClass.newInstance());
                }
                //如果value值不为空且不等于默认值,就说明需要注入,直接注入
                if(name!=null && !name.equals("")){
                    field.set(t,name);
                }
            }
        }
    }

结尾

现在虽然已经实现了ioc的最基础的功能,但还有很多缺点,比如:
1.每次创建新项目就必须改地址basePath
2.我要取对象内的元素的话还是要取new一个对象,和以前没啥区别
3.如果A是B的元素,而B又先执行,那么B中的A就为空

发布了2 篇原创文章 · 获赞 0 · 访问量 110

猜你喜欢

转载自blog.csdn.net/weixin_46007540/article/details/103499554