手动实现Spring

文章详细介绍了如何手动实现Spring的IoC容器,包括扫描带有@ComponentScan注解的类,解析Component注解来找到需要的类,判断类是否为单例,并通过BeanDefinition来描述Bean的信息。此外,还涉及了依赖注入、初始化、BeanPostProcessor和Aware回调的模拟实现过程。
摘要由CSDN通过智能技术生成

这里写自定义目录标题

1、Spring使用方法

	public static void main(String[] args) {

		// 创建一个Spring容器
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
		UserService userService = (UserService) applicationContext.getBean("userService");
		userService.test();
	}

解析
从上面代码可以看出,要手动实现Spring需要有以下几个步骤:
  1. 要有一个Spring容器
  2. 要有最基本getBean的方法

2、IOC控制反转实现步骤

新建两个package,org.kenny.spring (用于存放自定义spring的类);org.kenny.test(用于存放测试类)

1. 创建一个Spring容器ApplicationContext

  1. 通过AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);可以看出创建的容器需要就收一个类。因此自定义的容器需要有一个Class的属性和一个有参数的构造函数
public class KennyApplicationContext {
	private Class configClass;
	public KennyApplicationContext(Class configClass){
		this.configClass = configClasss;
	}
}
  1. 通过UserService userService = (UserService) applicationContext.getBean("userService");可以看出,该自定义容器还包含getBean(beanName)方法.
public Object getBean(String beanName){
return null;
}

那么我们如何才能通过一个beanName获取一个实际的Bean呢?

第一步中我们创建了容器 KennyApplicationContext ,我们要从容器中获取通过BeanName获取Bean的话,我们需要把bean加载到我们容器中来。那么我们可以通过扫描的方式将bean加载到容器中,即使用ComponenetScan注解来告诉容器,我要扫描那些bean到容器中,因此我们还需要创建一个@ComponenetScan注解

  1. 创建ComponenetScan注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义扫描注解
* @description
* @author KennySong
* @date 2023-3-19
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
    String value() default "";
}

创建了包的扫描路径,Spring并不会把包下所有的bean都加载到容器中,使用@CompontScan注解来扫描声明了@Controller、@Service、@Repository、@Component注解的类。此处以@Component为例,因此我们还需要创建一个@Component的注解

  1. 创建Component注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 自定义Spring组件注解
* @description
* @author KennySong
* @date 2023-3-19
* @copyright 千景空间科技有限公司
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
    String value() default "";
}

  1. 在测试包下创建使用自定义Spring的代码
//创建AppConfig
import org.kenny.spring.ComponentScan;

@ComponentScan("org.kenny.test.service")
public class AppConfig {
}

//创建service

import org.kenny.spring.Component;

@Component("userService")

public class UserService {

    public void test(){
        System.out.println("test方法");
    }
}

//创建程序入口
import org.kenny.spring.KennyApplicationContext;
import org.kenny.test.service.UserService;

public class Main {
    public static void main(String[] args) {
     
        KennyApplicationContext kennyApplicationContext = new KennyApplicationContext(AppConfig.class);
        UserService userService = (UserService) kennyApplicationContext.getBean("userService");
        System.out.println(kennyApplicationContext.getBean("userService"));
        System.out.println(kennyApplicationContext.getBean("userService"));
        System.out.println(kennyApplicationContext.getBean("userService"));
        userService.test();
    }
}

2、解析底层实现逻辑

首先我们在创建Spring容器的时候,容器需要做的一件事情就是创建非懒加载的单例bean,我们要创建单例bean首先得找到要创建那些类,我们可以通过@ComponenetScan注解来扫描我们需要创建的单例bean,因此我们容器的构造函数需要进行扫描的实现

1、扫描逻辑

在第一步创建的KennyApplicationContext 构造方法基础上

public class KennyApplicationContext {
	private Class configClass;
	public KennyApplicationContext(Class configClass){
		this.configClass = configClasss;
	}
}
1. 首先判断传进来的类有没有添加@ComponenetScan注解,如果有则拿到扫描路径
 if (configClass.isAnnotationPresent(ComponentScan.class)){
 	    //得到注解
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            //得到注解的值,此时得到的是org.kenny.test.service 包名
            String path = componentScanAnnotation.value();
             System.out.println(path);
 	 
 }

然后我们继续分析,有了扫描路径后,我们拿到了很多类,但是不是所有的类都需要扫描到容器,只有实现了@Component注解的类我们才需要扫描。我们来分析下,我们扫描的路径实际是target/classes文件夹下面的路径下面的class文件。
在这里插入图片描述
我们都知道生成类的三种方法:

  • BootstrapClassLoader:是顶级加载器,默认加载的%JAVA_HOME%中lib下的jar包和class类文件,即对应jre/lib
  • ExtClassLoader:ExtClassLoader扩展类加载器,负责加载%JAVA_HOME%中lib/ext文件下的jar包和class类文件,ExtClassLoader加载器是AppClassLoader的父类,当然也不是继承(extends)关系,也是类中有parent变量,即对应jre/ext/lib
  • AppClassLoader是自定义加载器的父类,负责加载classPath下的类文件,平时引用的jar包以及我们自己写的类都是这个加载器进行加载的,同时AppClassLoader还是线程上下文加载器.,如果想实现一个自定义加载器的话就继承(extends)ClassLoader来实现,即对应target/classes。
2. 因此我们要获得classes目录下的class文件。使用AppClassLoader就可以很简单的实现:
	//由于应用程序的实际是通过AppClassLoader来加载编译后的Class文件,也就是存放在target/classes/目录下的文件
	//因此需要把配置的扫描路径替换成文件系统的实际路径,org/kenny/test/service
	path =  path.replace(".","/");
	//然后通过AppClassLoader获取根路径,App类加载器默认指向的是target/classes/目录
	ClassLoader classLoader = KennyApplicationContext.class.getClassLoader();
	//通过getResource()方法获取具体的目录
	URL resource = classLoader.getResource(path);
	String allPath = resource.getFile();
	//由于我的目录中带有空格,需要替换转义
	allPath = allPath.replaceAll("%20", " ");
	//通过路径得到文件或者目录
	File file = new File(allPath);
3. 循环获取的所有class,生成class对象

接下来循环获取的所有class,生成class对象,但是上面只能得到绝对路径,比如 D:\WorkSpaces\IDE WorkSpace\kennysong-spring\target\classes\org\kenny\test\service\UserService.class,但是我们通过AppClassLoader创建对象需要的是完整的类路径,比如org.kenny.test.service.UserService,因此我们需要转换绝对路径

if (file.isDirectory()){
	for (File f : file.listFiles()) {
		//得到绝对路径D:\WorkSpaces\IDE WorkSpace\kennysong-spring\target\classes\org\kenny\test\service\UserService.class
		String absolutePath = f.getAbsolutePath();
		System.out.println(absolutePath);
		//判断创建非懒加载的单例bean
		//解析绝对路径成包名,org.kenny.test.service.UserService
		String org = absolutePath.substring(absolutePath.indexOf("org"), absolutePath.indexOf(".class"));
		org = org.replace("\\", ".");
		System.out.println(org);
	}
}

打印结果为:org.kenny.test.service.UserService

4. 此时我们就可以根据包名创建bean
//通过类名创建类
Class<?> clazz = classLoader.loadClass(org);
5. 当创建出了扫描路径下面的类时候,我们需要判断该类上有没有实现@Component接口,只有添加了@Component注解的类才是我们要找的类。
  if (clazz.isAnnotationPresent(Component.class)){
  }
6. 找到了我们需要的需要创建的类后,还需要判断这个类是不是单例类,怎么判断该类是否是非懒加载单例类,使用@Scope注解来实现,在org.kenny.spring包下创建Scope注解。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
    String value() default "";
}

7. 修改第五步的方法,判断是否是单例类
  //判断是否是Bean
 if (clazz.isAnnotationPresent(Component.class)){
          //判断是否是单例bean,即有没有是按Scope注解
          if (clazz.isAnnotationPresent(Scope.class)){
              //原型
           
          }else {
              //单例
            
          }
      }

至此,我们找到了单例类,接下来就是创建单例类了,我们接下来分析源码kennyApplicationContext.getBean("userService");,因为我们调用getBean方法是需要返回对象的,那么我们在getBean方法中还需要判断对象是单例对象还是原型对象,因为传进来的是beanName,因此在构造方法中判断单例bean和原型bean的逻辑还得在执行一遍,实际上是没有必要的,那么我们怎么解决这个问题呢。

8. 修改方法ApplicationContext里面的方法
a、创建一个Map将得到的类放入map中
public Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>();
b、创建Bean的定义类BeanDefinition
/**
* Bean的定义
* @description
* @author KennySong
* @date 2023-3-19
*/
public class BeanDefinition {
    /**
     * bean的类型
     */
    private Class type;
    /**
     * 作用域 单例还是原型
     */
    private String scope;
    /**
     * 是不是懒加载
     */
    private boolean isLazy;

    public Class getType() {
        return type;
    }

    public void setType(Class type) {
        this.type = type;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    public boolean isLazy() {
        return isLazy;
    }

    public void setLazy(boolean lazy) {
        isLazy = lazy;
    }
}
c、将找出来的bean生成BeanDefinition 在添加到map中
  try {
    		//通过类名创建类
         Class<?> clazz = classLoader.loadClass(org);
         //判断是否是Bean
         if (clazz.isAnnotationPresent(Component.class)){
             Component componentAnnotation = clazz.getAnnotation(Component.class);
             String beanName = componentAnnotation.value();
             BeanDefinition beanDefinition = new BeanDefinition();
             beanDefinition.setType(clazz);

             //判断是否是单例bean,即有没有是按Scope注解
             if (clazz.isAnnotationPresent(Scope.class)){
                 //原型
                 Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
                 String value = scopeAnnotation.value();
                 beanDefinition.setScope(value);
             }else {
                 //单例
                 beanDefinition.setScope("singleton");
             }

             beanDefinitionMap.put(beanName,beanDefinition);
         }

  } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
  }
d、将上述代码汇总提取
  /**
     * 扫描
     * @descrition
     * 1、通过传进来的类得到扫描路径
     * 2、通过扫描路径得到class文件
     * 3、遍历class文件
     * 4、通过class文件路径创建class对象
     * 5、判断对象有没有Component注解
     * 6、如果有注解,则获取component注解的值(bean的名字),并且创建BeanDefinition对象,设置对象类型type
     * 7、判断是否是单例类,即判断有没有@Scope注解,如果有,则设置BeanDefinition对象的scope为注解值,如果没有则设置为singleton单例对象
     * 8、将BeanDefinition对象 加载到beanDefinitionMap中
     * @param configClass
     */
    private void scan(Class configClass) {
        if (configClass.isAnnotationPresent(ComponentScan.class)){
            //得到注解
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            //得到注解的值,此时得到的是org.kenny.test.service 包名
            String path = componentScanAnnotation.value();
            //由于应用程序的实际是通过AppClassLoader来加载编译后的Class文件,也就是存放在target/classes/目录下的文件
            //因此需要把配置的扫描路径替换成文件系统的实际路径,org/kenny/test/service
            path =  path.replace(".","/");
            //然后通过AppClassLoader获取根路径,App类加载器默认指向的是target/classes/目录
            ClassLoader classLoader = KennyApplicationContext.class.getClassLoader();
            //通过getResource()方法获取具体的目录
            URL resource = classLoader.getResource(path);
            String allPath = resource.getFile();
            //由于我的目录中带有空格,需要替换转义
            allPath = allPath.replaceAll("%20", " ");
            //通过路径得到文件或者目录
            File file = new File(allPath);
            //判断得到的是否是目录
            if (file.isDirectory()){
                for (File f : file.listFiles()) {
                    //得到绝对路径D:\WorkSpaces\IDE WorkSpace\kennysong-spring\target\classes\org\kenny\test\service\UserService.class
                    String absolutePath = f.getAbsolutePath();
                    System.out.println(absolutePath);
                    //判断创建非懒加载的单例bean
                    //解析绝对路径成包名,org.kenny.test.service.UserService
                    String org = absolutePath.substring(absolutePath.indexOf("org"), absolutePath.indexOf(".class"));
                    org = org.replace("\\", ".");
                    System.out.println(org);

                    try {
                        //通过类名创建类
                        Class<?> clazz = classLoader.loadClass(org);
                        //判断是否是Bean
                        if (clazz.isAnnotationPresent(Component.class)){
                            Component componentAnnotation = clazz.getAnnotation(Component.class);
                            String beanName = componentAnnotation.value();
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setType(clazz);

                            //判断是否是单例bean,即有没有是按Scope注解
                            if (clazz.isAnnotationPresent(Scope.class)){
                                //原型
                                Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
                                String value = scopeAnnotation.value();
                                beanDefinition.setScope(value);
                            }else {
                                //单例
                                beanDefinition.setScope("singleton");
                            }

                            beanDefinitionMap.put(beanName,beanDefinition);
                        }

                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

修改构造方法:

    public KennyApplicationContext(Class configClass) {
        this.configClass = configClass;
        //判断传进来的类是否添加了@ComponentScan注解
        scan(configClass);
    }

2、创建单例Bean

上述代码我们基本实现了Spring扫描的逻辑,接下来要实现单例Bean的逻辑。
首先我们 public Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>(); beanDefinitionMap 中已经存放了所有的Bean的定义,我们循环这个Map去创建bean,为了getBean()中使用,创建 public Map<String,Object> singletonObjects = new HashMap<>();存放所有的单例类对象

//1、创建单例对象Map
    public Map<String,Object> singletonObjects = new HashMap<>();
//2、创建createBean方法
  private Object createBean(String beanName,BeanDefinition beanDefinition){
        Class clazz = beanDefinition.getType();
        Object object = null;
        try {
            object = clazz.getConstructor().newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
        return object;
    }
    //3、ApplicationContext构造方法中添加以下方法
for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
      String key = entry.getKey();
      BeanDefinition value = entry.getValue();
      if ("singleton".equals(value.getScope())){
          //创建单例对象
          Object bean = createBean(key, value);
          singletonObjects.put(key,bean);

      }
  }

3、完善getBean()

  1. 首先我们判断传进来的beanName有没有被扫描到容器中
  2. 如果没有则返回空指针异常,存在则从beanDefinitionMap中取出bean定义
  3. 通过beanDefinition判断是单例还是原型
  4. 如果是单例,则从单例Map中取出单例对象并返回
  5. 如果是原型类,则通过调用createBean创建一个新的对象并返回
public Object   getBean(String beanName){
        if (!beanDefinitionMap.containsKey(beanName)){
          throw new NullPointerException();
        }
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        //判断单例还是原型
        if ("singleton".equals(beanDefinition.getScope())){
            Object singletonBean = singletonObjects.get(beanName);
            return singletonBean;
        }else {
            //原型
            Object prototypeBean = createBean(beanName, beanDefinition);
            return prototypeBean;
        }
    }

3、 总结

至此,我们整个Spring扫描以及生成非懒加载的单例Bean就实现了,目录结构如下:
在这里插入图片描述
我们贴出完整的ApplicationContext代码

KennyApplicationContext.java

package org.kenny.spring;

import com.sun.deploy.util.StringUtils;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

public class KennyApplicationContext {
    private Class configClass;
    public Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>();
    public Map<String,Object> singletonObjects = new HashMap<>();
    public KennyApplicationContext(Class configClass) {
        this.configClass = configClass;
        //判断传进来的类是否添加了@ComponentScan注解
        scan(configClass);
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String key = entry.getKey();
            BeanDefinition value = entry.getValue();
            if ("singleton".equals(value.getScope())){
                //创建单例对象
                Object bean = createBean(key, value);
                singletonObjects.put(key,bean);

            }
        }

    }

    private Object createBean(String beanName,BeanDefinition beanDefinition){
        Class clazz = beanDefinition.getType();
        Object object = null;
        try {
            object = clazz.getConstructor().newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
        return object;
    }

    public Object   getBean(String beanName){
        if (!beanDefinitionMap.containsKey(beanName)){
          throw new NullPointerException();
        }
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        //判断单例还是原型
        if ("singleton".equals(beanDefinition.getScope())){
            Object singletonBean = singletonObjects.get(beanName);
            return singletonBean;
        }else {
            //原型
            Object prototypeBean = createBean(beanName, beanDefinition);
            return prototypeBean;
        }
    }

    /**
     * 扫描
     * @descrition
     * 1、通过传进来的类得到扫描路径
     * 2、通过扫描路径得到class文件
     * 3、遍历class文件
     * 4、通过class文件路径创建class对象
     * 5、判断对象有没有Component注解
     * 6、如果有注解,则获取component注解的值(bean的名字),并且创建BeanDefinition对象,设置对象类型type
     * 7、判断是否是单例类,即判断有没有@Scope注解,如果有,则设置BeanDefinition对象的scope为注解值,如果没有则设置为singleton单例对象
     * 8、将BeanDefinition对象 加载到beanDefinitionMap中
     * @param configClass
     */
    private void scan(Class configClass) {
        if (configClass.isAnnotationPresent(ComponentScan.class)){
            //得到注解
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            //得到注解的值,此时得到的是org.kenny.test.service 包名
            String path = componentScanAnnotation.value();
            //由于应用程序的实际是通过AppClassLoader来加载编译后的Class文件,也就是存放在target/classes/目录下的文件
            //因此需要把配置的扫描路径替换成文件系统的实际路径,org/kenny/test/service
            path =  path.replace(".","/");
            //然后通过AppClassLoader获取根路径,App类加载器默认指向的是target/classes/目录
            ClassLoader classLoader = KennyApplicationContext.class.getClassLoader();
            //通过getResource()方法获取具体的目录
            URL resource = classLoader.getResource(path);
            String allPath = resource.getFile();
            //由于我的目录中带有空格,需要替换转义
            allPath = allPath.replaceAll("%20", " ");
            //通过路径得到文件或者目录
            File file = new File(allPath);
            //判断得到的是否是目录
            if (file.isDirectory()){
                for (File f : file.listFiles()) {
                    //得到绝对路径D:\WorkSpaces\IDE WorkSpace\kennysong-spring\target\classes\org\kenny\test\service\UserService.class
                    String absolutePath = f.getAbsolutePath();
                    System.out.println(absolutePath);
                    //判断创建非懒加载的单例bean
                    //解析绝对路径成包名,org.kenny.test.service.UserService
                    String org = absolutePath.substring(absolutePath.indexOf("org"), absolutePath.indexOf(".class"));
                    org = org.replace("\\", ".");
                    System.out.println(org);

                    try {
                        //通过类名创建类
                        Class<?> clazz = classLoader.loadClass(org);
                        //判断是否是Bean
                        if (clazz.isAnnotationPresent(Component.class)){
                            Component componentAnnotation = clazz.getAnnotation(Component.class);
                            String beanName = componentAnnotation.value();
                            if ("".equals(beanName)){
                                //默认名字
                                beanName = Introspector.decapitalize(clazz.getSimpleName());
                            }
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setType(clazz);

                            //判断是否是单例bean,即有没有是按Scope注解
                            if (clazz.isAnnotationPresent(Scope.class)){
                                //原型
                                Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
                                String value = scopeAnnotation.value();
                                beanDefinition.setScope(value);
                            }else {
                                //单例
                                beanDefinition.setScope("singleton");
                            }

                            beanDefinitionMap.put(beanName,beanDefinition);
                        }

                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

}


测试方法如下:

    public static void main(String[] args) {
        //扫描--->创建非懒加载的单例bean
        KennyApplicationContext kennyApplicationContext = new KennyApplicationContext(AppConfig.class);
        UserService userService = (UserService) kennyApplicationContext.getBean("userService");
        System.out.println(kennyApplicationContext.getBean("userService"));
        System.out.println(kennyApplicationContext.getBean("userService"));
        System.out.println(kennyApplicationContext.getBean("userService"));
        userService.test();
    }

结果如下:
在这里插入图片描述
应为我们UserService类上@Scope为prototype,不是单例类,因此生成了三个不同的对象。如果我们将@Scope设置为singleton或者不添加@Scope注解,则就只会有一个实例。
在这里插入图片描述

3、DI依赖注入实现步骤

1、通过上面createBean方法的基础上,我们通过class获取类上的所有属性clazz.getDeclaredFields()
2、循环属性判断该属性是否添加了@Autowired注解,如果实现了注解,就可以使用getBea方法进行赋值;

    
    private Object createBean(String beanName,BeanDefinition beanDefinition){
        Class clazz = beanDefinition.getType();
        Object instance = null;
        try {
            instance = clazz.getConstructor().newInstance();
            //依赖注入
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(Autowired.class)){
                    //字段赋值
                    field.setAccessible(true);
                    field.set(instance,getBean(field.getName()));
                }
            }
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
        return instance;
    }

通过我们上面所实现的代码可以发现,我们是循环创建单例bean的。那么我们在geBean方法调用的时候,可能bean还没有创建,因此需要修改下我们getBean的方法,在原有的从单例池获取beanObject singletonBean = singletonObjects.get(beanName);的下面添加以下代码。

//判断单例bean是否存在
            if(singletonBean==null){
                singletonBean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName,singletonBean);
            }

完整代码如下:


    public Object   getBean(String beanName){
        if (!beanDefinitionMap.containsKey(beanName)){
          throw new NullPointerException();
        }
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        //判断单例还是原型
        if ("singleton".equals(beanDefinition.getScope())){
            Object singletonBean = singletonObjects.get(beanName);
            //判断单例bean是否存在
            if(singletonBean==null){
                singletonBean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName,singletonBean);
            }
            return singletonBean;
        }else {
            //原型
            Object prototypeBean = createBean(beanName, beanDefinition);
            return prototypeBean;
        }
    }

4、初始化

1、首先创建InitializingBean 接口,定义afterPropertiesSet方法。

/**
* Spring 初始化
* @description
* @author KennySong
* @date 2023-3-22
*/
public interface InitializingBean {
    void afterPropertiesSet();
}

2、其次在依赖注入完成以后,进行初始化操作,判断当前创建的类是否实现了初始化的Bean的接口,如果实现了就调用afterPropertiesSet();方法,进行初始化。

  //初始化,判断当前创建的类是否实现了初始化的Bean的接口
            if (instance instanceof InitializingBean){
                ((InitializingBean) instance).afterPropertiesSet();
            }

5、BeanPostProcessor (重要组件),初始化前,初始化后操作

1、定义BeanPostProcessor 接口

/**
* Bean初始化前,初始化后
* @description
* @author KennySong
* @date 2023-3-22
*/
public interface BeanPostProcessor {
    default Object postProcessBeforeInitialization(Object bean, String beanName)  {
        return bean;
    }

    default Object postProcessAfterInitialization(Object bean, String beanName) {
        return bean;
    }

}

2、创建MyBeanPostProcessor 类,实现BeanPostProcessor 接口里面的方法。

public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

如果我们想spring去调用我们的方法,一般写法是new MyBeanPostProcessor ().postProcessAfterInitialization(a,b)去使用,很明显spring不可能去new一个我们自己的类。那么我们怎么来实现呢?

  • 首先肯定是我们定义的类要被spring发现,那么怎么发现,实际上添加@Component即可
  • 发现后,spring则需要去判断该类有没有实现BeanPostProcessor ,clazz.isAssignableFrom(BeanPostProcessor.class)如果有,则使用list存起来,此处不能用instanceof ,应为还在扫描阶段,instanceof是判断实例用的。
  • 最后在初始化后调用初始化后的代码。

1、基于BeanPostProcessor 实现AOP

1、我们把执行了初始化后的对象赋值给instance
2、需改MyBeanPostProcessor 中postProcessAfterInitialization方法,我们用动态代理,返回一个代理对象,应为JDK的动态代理是基于接口是实现的,因此我们创建一个IUserService接口,修改UserService实现IUserService。修改postProcessAfterInitialization方法如下:

        //AOP
        if (beanName.equals("userService")){
            Object proxyInstance = Proxy.newProxyInstance(MyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    //进行切面逻辑编写
                    System.out.println("UserService切面");
                    //继续执行原有的方法
                    return method.invoke(bean,args);
                }
            });
            return proxyInstance;
        }

初始话后我们返回了动态代理的对象实现了切面逻辑:


        KennyApplicationContext kennyApplicationContext = new KennyApplicationContext(AppConfig.class);
        IUserService userService = (IUserService) kennyApplicationContext.getBean("userService");
//        System.out.println(kennyApplicationContext.getBean("userService"));
//        System.out.println(kennyApplicationContext.getBean("userService"));
//        System.out.println(kennyApplicationContext.getBean("orderService"));
        userService.test();

执行结果如下:

初始化前
初始化后
UserService切面
test

6、BeanNameAware —》Aware回调

1、创建BeanNameAware接口


public interface BeanNameAware {
    void setBeanName(String name);
}

2、在初始化前进行aware回调


//Aware回调

if (instance instanceof BeanNameAware){
	((BeanNameAware) instance).setBeanName(beanName);
}

7、 总结

1.扫描

(1)、创建SpringContext解析ComponentScan注解

通过传入的AppConfig解析ComponentScan注解,拿到扫描路径,解析扫描路径,通过AppClassLoader拿到需要扫描的包路径

(2)、扫描bean,解析Component注解

将扫描路径扫描到后,通过AppClassLoader中loadClass方法加载对象,判断对象是不是添加了Component注解,如果是则是我们要找的bean,不是则不管。通过Component注解可以拿到bean的名字,如果没有传值,则使用Introspector.decapitalize(clazz.getSimpleName());获取默认名字。

(3)、判断是不是实现了BeanPostProcessor接口

使用BeanPostProcessor.class.isAssignableFrom(clazz)判断是不是实现了BeanPostProcessor 接口,如果实现了则直接实例化对象,并将该对象放入beanPostProcessorList中存储。如果没有实现则执行第四步。因为BeanPostProcessor 是针对所有的Bean,所以不需要使用map存储。

(4)、创建BeanDefinition

将扫描到的bean创建BeanDefinition对象,BeanDefinition对象用于存储对象,描述对象类型、作用域(单例还是原型)、是不是懒加载等信息;判断作用域是使用Scope注解,创建完成后将BeanDefinition对象存入beanDefinitionMap中。

2、创建单例Bean

(1)、循环beanDefinitionMap判断是否是单例bean

通过循环扫描得到的beanDefinitionMap,来判断当前bean是否是单例bean,如果是单例bean则直接实例化bean,并将当前对象方式单例Map中singletonObjects。

(2)、创建bean

通过BeanDefinition中类型,调用默认构造方法,实例化对象instance

(3)、依赖注入

通过bean类型,使用 clazz.getDeclaredFields()获取所有的字段,循环字段列表,判断该字段是否添加了Autowired注解,如果添加了则设置字段可编辑field.setAccessible(true);,并通过instance赋值,赋值的时候需要传入对象以及值,对象则是第二步实例化的 instance,而需要赋的值,则可以通过getBean方法拿到,写法如下field.set(instance,getBean(field.getName()));

(4)、Aware回调

当有bean实现了BeanNameAware接口时候,则在依赖注入完成后,进行Aware回调。实现方式是判断实例是否实现了BeanNameAware接口instance instanceof BeanNameAware,如果实现了则调用其方法如: ((BeanNameAware) instance).setBeanName(beanName);

(5)、BeanPostProcessor初始化前方法调用

通过扫描拿到了所有实现了BeanPostProcessor 接口的bean,instance初始化之前循环调用postProcessBeforeInitialization方法beanPostProcessor.postProcessBeforeInitialization(instance,beanName);,如果将创建的beanPostProcessor返回的对象赋值给instance实例的话,我们可以实现AOP instance = beanPostProcessor.postProcessBeforeInitialization(instance,beanName);

(6)、初始化

判断当前对象是否继承了InitializingBean接口,instance instanceof InitializingBean,如果继承了,则执行初始化逻辑,调用afterPropertiesSet();方法,代码如下 ((InitializingBean) instance).afterPropertiesSet();

(7)、BeanPostProcessor初始化后方法调用

与第5步相同。

3、getBean(String beanName)以及BeanPostProcessor 方法的修改。

1、getBean(String beanName)

  1. 从创建bean的时候我们调用了一次getBean方法,需要通过beanName获取bean。通过传入的beanName在beanDefinitionMap中找出扫描到的beanDefinition
  2. 如果为空,则说明没有扫描到该bean,返回空指针异常,不为空,则继续执行
  3. 不为空取出beanDefinition,判断是否是单例,如果是单例则从singletonObjects中取出bean,如果取出的bean为空,则可能当前的bean还未创建完成,则调用createBean方法,创建一个实例并返回,如果不为空则直接返回
  4. 如果不是单例,则调用createBean方法,创建一个实例并返回。

2、BeanPostProcessor 方法的修改。

比如我们自己写了一个MyBeanPostProcessor 实现了BeanPostProcessor 接口。
那么我们可以重写 postProcessAfterInitialization(Object bean, String beanName) 或者postProcessBeforeInitialization(Object bean, String beanName) 方法,应为我们在创建bean的时候,我们进行了赋值。那么我们可以在自定义BeanPostProcessor 类中通过动态代理返回一个代理对象,那么我们通过getBean方法调用的时候,实际获取的是我们返回回去的代理对象。那么我们就可以在调用实际方法前做一些事情,如下:

//AOP
        if (beanName.equals("userService")){
            Object proxyInstance = Proxy.newProxyInstance(MyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    //进行切面逻辑编写
                    System.out.println("UserService切面");
                    return method.invoke(bean,args);
                }
            });
            return proxyInstance;
        }

方法解析这里就不在赘述,上面已经解析过了。那么我门在调用userService里面的方法的时候,都会先执行我们这个切面逻辑。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现 Spring 框架是一个非常复杂的任务,需要深入理解 Spring 的核心原理和各个模块的实现细节。如果你是想了解 Spring 的内部实现,可以参考 Spring 源码,里面包含了丰富的注释和文档,可以帮助你快速理解 Spring 的各个模块。 如果你是想手动实现一个简单的 Spring 框架,可以按照以下步骤进行: 1. 实现 BeanFactory 接口,定义 getBean() 方法,用于获取 Bean 对象。 2. 实现 BeanDefinition 接口,定义 Bean 对象的属性,比如类名、构造函数参数、属性等。 3. 实现 BeanPostProcessor 接口,定义初始化前后的处理逻辑,可以在这里实现依赖注入、AOP等功能。 4. 实现 ApplicationContext 接口,继承 BeanFactory 接口,定义 refresh() 方法,用于加载配置文件、实例化 Bean 对象、处理依赖关系等操作。 5. 实现 Resource 接口,定义资源加载的方式,比如从类路径、文件系统、URL等位置加载资源。 6. 实现 BeanDefinitionReader 接口,定义读取 Bean 定义的方式,比如从 XML、JSON、注解等方式读取 Bean 定义。 7. 实现 PropertyEditorRegistrar 接口,定义属性编辑器的注册逻辑,用于将字符串类型的属性值转换为实际类型的属性值。 8. 实现 BeanNameGenerator 接口,定义生成 Bean 名称的逻辑,用于生成默认的 Bean 名称或者根据注解等方式生成 Bean 名称。 以上是一个简单的 Spring 框架的实现步骤,具体实现细节可以参考 Spring 源码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值