SpringFramework-实现简单的IOC容器

原理(手写IoC)

Java反射

  • 当程序在运行时,所有的类、对象、方法等信息都被加载到内存中。通过反射,程序可以在运行过程中访问和修改这些信息。

反射实现的过程

  1. 获取Class对象
    • 通过对象的getClass()方法: 使用对象的getClass()方法可以获取对应类的Class对象。
    • 通过类名.class: 使用类名.class语法可以获取对应类的Class对象。
    • 通过Class.forName()方法: 使用Class.forName("类的完整路径")方法可以根据类的完整路径获取对应类的Class对象。
  2. 获取类的构造函数、方法和字段
    • 获取构造函数:
      • public修饰:getConstructors()
      • public修饰或private:getDeclaredConstructors()
    • 获取方法:
      • public修饰:getMethods()
      • public或private修饰:getDeclaredMethods()
    • 获取字段:
      • public修饰:getFields()
      • public或private修饰:getDeclaredFields()
  3. 实例化对象、调用方法和访问字段
    • 实例化对象: Class对象.constructor().newInstance();
    • 调用方法: Class对象.getMethod("方法名","参数类型").invoke("类实例","参数列表")
    • 设置字段: Class对象.getDeclaredField("字段名").set("类实例","参数")
//反射的实例
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;

public class ReflectionExample {
    private String privateField = "私有字段";
    public String publicField = "公共字段";

    private ReflectionExample() {
        System.out.println("私有构造函数被调用");
    }

    public ReflectionExample(int num) {
        System.out.println("公共构造函数被调用,参数为 " + num);
    }

    private void privateMethod() {
        System.out.println("私有方法被调用");
    }

    public void publicMethod() {
        System.out.println("公共方法被调用");
    }

    public static void main(String[] args) throws Exception {
        // 获取类的 Class 对象
        Class<ReflectionExample> clazz = ReflectionExample.class;

        // 获取私有字段并设置其值
        Field privateField = clazz.getDeclaredField("privateField");
        //覆盖字段、方法和构造函数的默认访问限制,从而访问和修改私有成员。
        privateField.setAccessible(true);
        privateField.set(new ReflectionExample(), "修改后的私有字段值");
        
        // 获取公共字段并读取其值
        Field publicField = clazz.getField("publicField");
        System.out.println("公共字段的值: " + publicField.get(new ReflectionExample()));

        // 获取私有构造函数并实例化对象
        Constructor<ReflectionExample> privateConstructor = clazz.getDeclaredConstructor();
        privateConstructor.setAccessible(true);
        privateConstructor.getDeclaredConstructor().newInstance();

        // 获取带参数的公共构造函数并实例化对象
        Constructor<ReflectionExample> publicConstructor = clazz.getConstructor(int.class);
        publicConstructor.newInstance(42);

        // 调用私有方法
        Method privateMethod = clazz.getDeclaredMethod("privateMethod");
        privateMethod.setAccessible(true);
        privateMethod.invoke(new ReflectionExample());

        // 调用公共方法
        Method publicMethod = clazz.getMethod("publicMethod");
        publicMethod.invoke(new ReflectionExample());
    }
}

注解

概要

注解就是一个标记,本身不执行任何代码,框架中使用注解进行标记后实现的功能实际上是由注解解析器完成的。

注解解析器先进行扫描,找到相应的注解,之后通过反射的形式得到类,进一步完成某些操作

// 自定义注解

public @interface 注解名称{
  public 属性类型 属性名() default 默认值;
}
  • 元注解
    • @Target(ElementType.TYPE)
      • 声明被修饰的注解只能在那些位置上使用
      • TYPE:类,接口
      • FIELD:成员变量
      • METHOD:成员方法
      • PARAMETER:方法参数
      • CONSTRUCTOR:构造器
      • LOCAL_VARIABLE:局部变量
    • @Retention(RetentionPolicy.RUNTIME)
      • 声明注解的持续周期
      • SOURCE:作用于源码阶段,字节码文件不存在
      • CLASS:保留到字节码文件阶段,运行阶段不存在
      • RUNTIME:一致保留到运行阶段

利用反射获取注解

由于Class,Method,Field,Constructor都实现了AnnotatedElement接口,故可以直接对其进行注解解析

// 获取对象上注解
public Annotation[] getDeclaredAnnotations();

// 获取指定注解对象
public T getDeclaredAnnotation(Class<T> annotationClass);

// 判断当前对象上是否存在某个注解
public boolean isAnnotationPresent(Class<Annotation> annotationClass);

实例

// 自定义注解
public @interface MyAnnotation{
  String value();
  int num() default 10;
  String[] hobbies;
}
// 在类上使用注解
@MyAnnotation(value="li",num=12,hobbies={"chifan","suijiao","dadoudou"})
public class Demo{

}
// 测试类,获取注解的解析
public class AnnotationTest{
  @Test
  public void parseClass(){
    // 获取Class对象
    Class<Demo> demo = Demo.class;
    // 解析类上的注解
    // 判断类上是否有该注解
    if(demo.isAnnotationPresent(MyAnnotation.class)){
      // 获取注解对象
      Annotation myAnnotation = (MyAnnotation)demo.getDeclaredAnnotation(MyAnnotation.class)
      // 输出注解对象中的值
      System.out.println(myAnnotation.value);
      System.out.println(myAnnotation.num);
      System.out.println(myAnnotation.hobbies);
    }
  }

}

IoC/DI实例

设计思路

  • 注解类
    • com.atli.annotations.@MyAutowired:标记自动装配成员
    • com.atli.annotations.@MyBean:标记bean
    • com.atli.annotations.@MyComponentScan:标记bean扫描路径
    • com.atli.annotations.@MyConfiguration:标记配置类
  • 实体类
    • com.atli.dao.UserDao
    • com.atli.service.UserService
    • com.atli.Controller.UserController
  • 注解解析器
    • com.atli.parser.AnnotationParser

对于注解类,持续周期均为运行时
除开@MyAutowired注解作用空间为成员变量上,其余注解均位于类上

对于实体类,这里模拟简单的三层架构,需要将UserDao注入到UserService中,UserService注入到UserController中

  • 对于注解解析器的运行逻辑
    • 先扫描并解析配置类的相关信息
    • 再根据配置类中配置的bean扫描路径进行bean的扫描,每成功扫描到一个bean就将该类以全类名为key,类的class类存入到一个名为beanDef的map中。
    • 扫描完成后再对beanDef进行遍历,将所有的bean都进行实例化,以key为类名,value为实例对象存入名为beans的map中
    • 最后对beans进行遍历,解析其中各个类成员变量中的@MyAutoWired注解,对于由该注解的类创建一个新的bean实例对象,然后根据该注解所修饰的成员变量的类型进行di注入,再用注入完成的bean实例去替换beans中未进行注入的bean实例u

代码展示

com.atli.annotations包下所有的自定义注解

/**
 * TODO:用于标记需要自动注入
 * com.atli.annotations.@MyAutowired
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutowired {
}

/**
 * TODO:用于标记bean
 * com.atli.annotations.@MyBean
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyBean {
    String value() default "";
}

/**
 * TODO:配置扫描路径
 * com.atli.annotations.@MyComponentScan
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponentScan {
    String[] value() default {};
}

/**
 * TODO:指明配置类
 * com.atli.annotations.@MyConfiguration
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyConfiguration {
}

com.atli.dao|service|controller三个包下的实体类

// com.atli.dao.UserDao
@MyBean
public class UserDao {
    public void work(){
        System.out.println("work");
    }
}

//com.atli.service.UserService
@MyBean
public class UserService {
    @MyAutowired
    private UserDao userDao;

    public void work(){
        userDao.work();
    }
}

//com.atli.Controller.UserController
@MyBean
public class UserController {
    @MyAutowired
    private UserService userService;

    public void work(){
        userService.work();
    }
}

com.atli.parser包下的注解解析器

/**
 * ToDo:配置注解解析器
 */
public class AnnotationParser {

    //存储扫描到的bean的定义
    Map<String, Class> beanDefs = new HashMap();
    //存储bean的实例
    Map<String,Object> beans = new HashMap();

    /**
     * TODO:扫描配置类中配置的bean package,并将有@MyBean注解的类装入beanDefs
     * @return: bean集合封装在Map中
     */
    public AnnotationParser(Class clazz)  {
        //存储在@MyComponentScan注解中标记的包扫描路径
        String[] scanPaths={};

        //确定配置类类型指定正确
        Annotation[] annotations = clazz.getAnnotations();
        if(clazz.isAnnotationPresent(MyConfiguration.class)){
            //获取配置类中在@MyComponentScan注解中指定的bean扫描路径
            if(clazz.isAnnotationPresent(MyComponentScan.class)){
                scanPaths=((MyComponentScan)clazz.getAnnotation(MyComponentScan.class)).value();
            }
        }
        //根据之前获取的bean扫描路径进行循环遍历找到含有@MyBean注解的类
        for (String scanPath:scanPaths){
        //将每一个scanPath拆分组合成相对路径
            String packagePath = scanPath.replace('.','/');
            //从网上找的遍历目录的方法
            //注意,这里不能只是指定com.atli包下,要将这个包在项目下的位置写出来,从src目录开始
            Path startPath = Paths.get("src/main/java",packagePath);
            try {
                Files.walkFileTree(startPath, EnumSet.noneOf(FileVisitOption.class), Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
                    @Override
                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                        //获取到文件名
                        String fileName = file.getFileName().toString();
                        //获取类全路径
                        String classPath =file.toString().substring(14, file.toString().length()-".java".length()).replace('\\','.');
                        //判断所扫描到的文件是否为.java文件
                        if (fileName.endsWith(".java")) {
                            //获取类的名称
                            String className = fileName.substring(0, fileName.length()-".java".length());
                            //所遍历到的类的class类
                            Class<?> aClass = null;
                            try {
                                //注意forName方法要用类的全路径
                                aClass = Class.forName(classPath);
                            } catch (ClassNotFoundException e) {
                                throw new RuntimeException(e);
                            }
                            //判断该类是否有@MyBean注解
                            if(aClass.isAnnotationPresent(MyBean.class)){
                                //将有MyBean注解标记的类装入beanDefs中
                                //key为类全路径,即com.atli.xxx,value为class
                                beanDefs.put(classPath,aClass);
                            }
                        }
                        System.out.println("search file success:"+file);
                        return FileVisitResult.CONTINUE;
                    }

                    @Override
                    public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                        System.err.println(exc);
                        return FileVisitResult.CONTINUE;
                    }
                });
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        try {
            //进行di操作,先根据beanDef来实例化bean对象,并将其装入beans中
            // 再根据@Myautowired注解来遍历beans中的bean进行自动装配
            autowired();
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * TODO:进行di操作,先实例化所有bean,在进行属性注入
     */
    public void autowired() throws IllegalAccessException, NoSuchMethodException {
        //遍历所有的bean,寻找使用了@MyAutowired的成员变量
        for(Map.Entry<String,Class> beanDef:beanDefs.entrySet()){
            try {
                //创建当前类的实例对象
                Object bean = beanDef.getValue().getConstructor().newInstance();
                //获取类名作为key
                String[] split = beanDef.getKey().split("\\.");
                String key = split[split.length-1];
                //将当前类的实例放入到beans中
                beans.put(key, bean);
            } catch (InstantiationException e) {
                throw new RuntimeException(e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e);
            }
        }
        //遍历所有的bean实例
        for(Map.Entry<String,Object> bean:beans.entrySet()){
            //获取当前bean的所有成员变量
            Field[] fields = bean.getValue().getClass().getDeclaredFields();
            //遍历所有的成员变量
            for(Field field:fields){
                //判断当前成员变量是否有@MyAutowired注解
                if(field.isAnnotationPresent(MyAutowired.class)){
                    //需要注入的属性类型
                    Class<?> type = field.getType();
                    //设置可以注入私有属性
                    field.setAccessible(true);
                    //获取类名作为key
                    String[] split = field.getType().getName().split("\\.");
                    String key = split[split.length-1];
                    //将自动装配的bean注入到当前bean的成员变量
                    field.set(bean.getValue(),beans.get(key));
                }
            }
            //将beans中未进行di操作的bean替换为di操作后的bean
            beans.put(bean.getKey(),bean.getValue());
        }
    }

    /**
     * TODO:返回通过get方法获取的bean对象
     *
     */
    public Object getBean(String id) {
        return beans.get(id);
    }
}
// 测试类
public class IocDiTest {
    @Test
    public void test() {
        AnnotationParser ap = new AnnotationParser(MyConfig.class);
        UserController userController = (UserController) ap.getBean("UserController");
        userController.work();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值