手动实现Spring

这里写自定义目录标题

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
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值