原创

Spring笔记三(自定义IOC)

温馨提示:
本文最后更新于 2023年11月30日,已超过 565 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

IOC是基于java的反射机制实现的。

一. 反射核心

Animal类

public class Animal {

    //动物名
    private String animalName;
    //动物年龄
    private int animalAge;
    //动物颜色
    public String animalColor;

    //无参构造
    public Animal() {
    }
    //全参构造
    private Animal(String animalName, int animalAge, String animalColor) {
        this.animalName = animalName;
        this.animalAge = animalAge;
        this.animalColor = animalColor;
    }
    //私有方法
    private void showAnimal(){
        System.out.println("展示动物:"+this.animalName);
    }

    @Override
    public String toString() {
        return "Animal{" +
                "animalName='" + animalName + '\'' +
                ", animalAge=" + animalAge +
                ", animalColor='" + animalColor + '\'' +
                '}';
    }

    public String getAnimalName() {
        return animalName;
    }

    public void setAnimalName(String animalName) {
        this.animalName = animalName;
    }

    public int getAnimalAge() {
        return animalAge;
    }

    public void setAnimalAge(int animalAge) {
        this.animalAge = animalAge;
    }

    public String getAnimalColor() {
        return animalColor;
    }

    public void setAnimalColor(String animalColor) {
        this.animalColor = animalColor;
    }
}

1.1.获取Class的多种方式

  • 类.class
  • 对象.getClass
  • Class.forName("全路径")
    @Test
    public void testClass() throws Exception {

        Class clazz1 = Animal.class;

        Animal animal = new Animal();
        Class clazz2 = animal.getClass();

        Class clazz3 = Class.forName("com.mhld.manage.animal.Animal");

        //利用class进行对象创建
        Animal animalTemp = (Animal)clazz3.getDeclaredConstructor().newInstance();
        System.out.println(animalTemp);

    }

1.2.获取构造方法

  • class.getConstructors 只能获取public的构造方法
  • class.getDeclaredConstructors 可获取所有的构造方法
    @Test
    public void testConstructor() {
        Class clazz = Animal.class;
        //getConstructors,只能获取public的构造方法
        Constructor[] constructors = clazz.getConstructors();
        for (Constructor con : constructors) {
            System.out.println("构造方法名:"+con.getName()+"---    参数个数="+con.getParameterCount());
        }
        System.out.println("分割线===========================");
        //getDeclaredConstructors 可获取所有的构造方法
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor con : declaredConstructors) {
            System.out.println("构造方法名:"+con.getName()+"---    参数个数="+con.getParameterCount());
        }
    }

1.3.获取属性值

  • class.getFields() 只能获取到public 属性
  • class.declaredFields() 可以获取到所有属性
    @Test
    public void testFiles() throws Exception{
        Animal animal = new Animal();
        Class clazz = animal.getClass();
        //只能获取到public 属性
        Field[] fields = clazz.getFields();
        for (Field file : fields) {
            if (file.getName().equals("animalColor")){
                file.set(animal,"灰色");
            }
            System.out.println("属性名==="+file.getName());
        }
        System.out.println(animal);
        System.out.println("分割线===========================");
        //可以获取到所有属性
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field file : declaredFields) {
            //在对私有属性值进行赋值时,需要设置字段可操作
            if (file.getName().equals("animalName")){
                file.setAccessible(true);
                file.set(animal,"老虎");
            }
            System.out.println("属性名==="+file.getName());
        }
        System.out.println(animal);
    }

1.4.获取方法

  • clazz.getMethods();
  • clazz.getDeclaredMethods();
    @Test
    public void testMethod() throws Exception{
        Class clazz = Animal.class;
        Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class,int.class,String.class);
        declaredConstructor.setAccessible(true);
        Animal animal = (Animal) declaredConstructor.newInstance("老虎",2,"黄色");
        //只能获取到public 方法
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            System.out.println("方法名==="+method.getName());
        }
        System.out.println("分割线===========================");
        //可以获取到所有方法
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method method : declaredMethods) {
            if (method.getName().equals("showAnimal")){
                //私有方法需要设置可允许操作
                method.setAccessible(true);
                //使用invoke调用方法,入参为 调用方法的对象和对应参数(没有可以不填)
                method.invoke(animal);
            }
            System.out.println("方法名==="+method.getName());
        }

    }

二. 手写IOC

模拟Controller注入service,调用service方法。service注入dao,调用dao方法

2.1.准备

2.1.1. 自定义MyBean注解和MyDi注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyBean {
}

@Target({ElementType.TYPE,ElementType.FIELD,ElementType.CONSTRUCTOR,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyDi {
}

2.1.2.dao、service、controller

public interface UserDao {
    void testUser();
}
@MyBean
public class UserDaoImpl implements UserDao{
    @Override
    public void testUser() {
        System.out.println("Dao执行了");
    }
}


public interface UserService {
    void testUser();
}
@MyBean
public class UserServiceImpl implements UserService{
    @MyDi
    private UserDao userDao;
    @Override
    public void testUser() {
        System.out.println("UserService执行了");
        userDao.testUser();
    }
}

@MyBean
public class UserController {
    @MyDi
    private UserService userService;
    public void testCustom(){
        System.out.println("UserController执行了。。。。。。。。。");
        userService.testUser();
    }
}

2.1.3.容器

容器接口,实现类,在实现类中实现IOC和DI的工作

public interface MyApplicationContext {

    Object getObject(Class clazz);
}

public class MyApplicationContextImpl implements MyApplicationContext{

    //存放Bean实例
    private Map<Class,Object> beanFactoryMap = new HashMap<>();

    private String absolutePath;

    //通过class获取实例
    @Override
    public Object getObject(Class clazz) {
        return beanFactoryMap.get(clazz);
    }

}

2. 实现IOC和DI的工作

  • 初始化构造器,传入需要扫描的包

  • 处理包路径为file文件路径(用到File进行文件扫描处理)

  • 扫描对象

    • 遍历file文件,如果该文件是文件夹,则进入迭代

    • 筛选出class文件

    • 获取class文件的文件路径

    • 将文件路径转为包路径

    • 使用反射获取到Class

    • 判断Class是否有自定义的MyBean注解,如果有,则利用反射生成单例,放到map中

      此时的自定义注解是放在实现类上的,使用Di注入的时候,对象属性是接口,所以需要获取到该实现类的接口,将KEY设置为接口Class

  • 处理依赖注入

    • 遍历map
    • 获取到map中每个对象的class
    • 获取class的field列表
    • 遍历field列表
    • 判断field是否有MyDi注解
    • 如果有注解,则获取到该field的Class,通过class从map中取出对象,通过set方法设置到当前遍历对象中

完整代码:

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;

public class MyApplicationContextImpl implements MyApplicationContext{


    //存放Bean实例
    private Map<Class,Object> beanFactoryMap = new HashMap<>();

    private String absolutePath;

    //通过class获取实例
    @Override
    public Object getObject(Class clazz) {
        return beanFactoryMap.get(clazz);
    }

    //初始化IOC容器,通过指定的包进行路径扫描及实例生成
    public MyApplicationContextImpl(String basePackage) {
        /*
        * 1. basePackage包路径分割符号是.  需要替换为\
        * 2. 获取相对路径,加上basePackage进行全路径扫描
        * 3. 处理扫描到的文件
        * 3.1 筛选出.class文件
        * 3.2 获取到该class文件的全路径(利用反射获取到class)
        * 3.3 判断class是否有自定义的注解(@MyBean),如果有,则生成实例,放到实例工厂中
        */
        try {
            basePackage = basePackage.replaceAll("\\.","\\\\");
            //通过线程方法获取到当前的URLS
            Enumeration<URL> urls =
                    Thread.currentThread().getContextClassLoader().getResources(basePackage);
            //处理绝对路径
            while (urls.hasMoreElements()){
                URL url = urls.nextElement();
                //使用utf8解码,不然会乱码
                String path = URLDecoder.decode(url.getPath(), "utf-8");
                absolutePath = path.substring(1,path.length()-basePackage.length());
            }
            //替换路径的/ 为\,后续file对象获取的路径都是xx\xx\ 便于替换,处理成包路径
            absolutePath = absolutePath.replaceAll("/","\\\\");
            //包路径下的class文件处理,对象创建
            handleFile(new File(absolutePath));
            //处理依赖注入
            handelDi();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    //处理包路径下的文件
    public void handleFile(File file){
        if (file.isFile()){
            String name = file.getName();
            //过滤class文件
            if (name.contains(".class")){
                //获取当前文件的绝对路径   xxxx\xxx\xxx
                String absoluteFilePath = file.getAbsolutePath();
                //替换父工程绝对路径
                absoluteFilePath = absoluteFilePath.replace(absolutePath,"");
                //替换到.class  将\替换为. 得到包路径
                absoluteFilePath = absoluteFilePath.replace(".class","").replaceAll("\\\\","\\.");
                try {
                    //获取到clazz,判断是否有自定义注解MyBean
                    Class clazz = Class.forName(absoluteFilePath);
                    if (clazz.isAnnotationPresent(MyBean.class)){
                        //如果有注解,生成单实例
                        Object o = clazz.getDeclaredConstructor().newInstance();
                        //获取实现的接口
                        Class[] interfaces = clazz.getInterfaces();
                        //如果有实现的接口,KEY就使用第一个接口class,如果没有,就放当前的class
                        //基础示例,后期可以根据需求进行变化
                        if (interfaces.length>0){
                            beanFactoryMap.put(interfaces[0],o);
                        }else {
                            beanFactoryMap.put(clazz,o);
                        }

                    }
                }catch (Exception e){
                    e.printStackTrace();
                    System.out.println("获取类错误");
                }
            }

        }else {
            //如果是文件夹,就进行递归
            File[] files = file.listFiles();
            for (File fileTemp : files) {
                handleFile(fileTemp);
            }
        }

    }

    //处理依赖注入
    public void handelDi(){
        //遍历实例map
        for (Map.Entry<Class, Object> entry : beanFactoryMap.entrySet()){
            //获取到对象的class
            Class clazz = entry.getValue().getClass();
            //获取属性列表
            Field[] declaredFields = clazz.getDeclaredFields();
            if (declaredFields.length>0){
                for (Field filed : declaredFields) {
                    //判断是否有依赖注入属性
                    MyDi annotation = filed.getAnnotation(MyDi.class);
                    if (null!=annotation){
                        //如果有,就获取到当前字段的clazz
                        Class filedClazz = filed.getType();
                        //从map中获取到对象
                        Object beanObj = beanFactoryMap.get(filedClazz);
                        //设置字段为可操作
                        filed.setAccessible(true);
                        try {
                            //将值自动设置到当前遍历对象中
                            filed.set(entry.getValue(),beanObj);
                        } catch (IllegalAccessException e) {
                            System.out.println("依赖注入值设置错误");
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }
    }
}

3.测试

   @Test
    public void testDi(){
        MyApplicationContext context = new MyApplicationContextImpl("com.xx.xx");
        UserController userController = (UserController) context.getObject(UserController.class);
        userController.testCustom();
    }

    UserController执行了。。。。。。。。。
    UserService执行了
    Dao执行了
正文到此结束