简易实现Spring Framework底层机制

Posted Al_tair

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简易实现Spring Framework底层机制相关的知识,希望对你有一定的参考价值。

实现Spring Framework底层机制


大家好呀!我是小笙,本节主要是想和大家简单讲讲ioc的实现以及aop的部分实现原理

实现Spring Framework底层机制

思考三个问题:

  • Spring容器 如何实现依赖注入singleton,prototype(单例,多例)
  • Spring 如何实现BeanPostProcesser (后置处理器)
  • Spring 如何实现AOP切面编程机制

注意:以下实现只是为了便于理解真正的 spring 底层机制,并非完全一致

整体架构

思路分析以及实现过程

1.创建Spring容器

扫描包下所有的文件,将标有特定注解的类通过反射创建bean对象,存入到ioc容器中

  • 使用lnsSpringConfig.java代替bean.xml文件
  • 扫描包下文件是否被标记注解
  • 将单例bean对象实例化放入singleton 单例池中

知识点

类加载器 详细说明

Bootstrap 类加载器 <=> 对应路径 jre/lib

Ext 类加载器 <=> 对应路径 jre/lib/ext

App 类加载器 <=> 对应路径 classpath

1.指定需要扫描包的全路径

使用 lnsSpringConfig.java 代替 bean.xml 文件(指定需要扫描包的全路径)

/**
 * @author Al_tair
 * @date 2022/7/14-22:14
 * 作用类似于bean.xml文件配置扫描包的全路径
 */
@ComponentScan(value = "com.Al_tair.spring.myComponent")
public class ClassPathConfig 

@ComponentScan注解是自己实现的,就是用 value 来存储指定扫描包的全路径

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan 
    // 通过 value 指定扫描包的全路径
    String value() default "";

主方法就是将配置类的信息传给 ioc 容器,进行扫描包进行初始化

public class AppMain 
    public static void main(String[] args) 
        // ClassPathConfig.class 配置类信息,可以通过获取该类上的注解信息来进行 bean 对象的注入
        SpringIocApplicationContext ioc = new SpringIocApplicationContext(ClassPathConfig.class);
    


2.扫描包并进行分类(单例、多例)

首先,我们需要选择存储的数据结构,我采用 Map 键值对的形式来定义 BeanDefinition 对象和 singletonObject 单例池对象

  • BeanDefinition 对象是用来存储 scope(单例或者多例) 和 bean 对象的 class 信息,方便反射初始化对象
  • singletonObject 单例池对象是提前创建来懒加载单例对象,方便获取,多例对象都是通过创建 bean 对象的时候创建
/**
  * 定义属性 BeanDefinitionMap id <-> BeanDefinition 对象
  * ConcurrentHashMap 线程安全
  */
private final ConcurrentHashMap<String,BeanDefinition> ioc = new ConcurrentHashMap<>();

/**
  * 单例池:存放单例对象
  */
private ConcurrentHashMap<String,Object> singletonObject = new ConcurrentHashMap<>();

BeanDefinition 类信息

/**
 * 用于封装/记录Bean的信息
 * 1. scope
 * 2. bean 对应的 Class 对象
 */
public class BeanDefinition 
    private String scope;
    private Class clazz;
    // ...省略

然后,我们需要找到需要扫描的类(找出.class的文件,并存储类路径)

// 1.获取注解信息:@com.Al_tair.spring.annotation.ComponentScan(value=com.Al_tair.spring.myComponent)
ComponentScan componentScan = (ComponentScan)this.springConfig.getDeclaredAnnotation(ComponentScan.class);
// 2.取出注解里的 value 值并转换成相对文件路径 com/Al_tair/spring/myComponent
String ScanPackage = componentScan.value().replace('.','/');
// 3.得到类加载器:找到资源运行的真实路径
ClassLoader classLoader = SpringIocApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(ScanPackage);
// 4.获取到资源路径上的文件夹
File file = new File(resource.getFile());
// 通过文件夹所在位置进行遍历
scanFiles(file);

扫描指定文件夹下的 .class 文件,我将这个类路径放在 list 列表下,方便以后获取

/**
  * 存储包下的全部.class文件路径
  */
private List<String> list = new ArrayList<>();

scanFiles 方法:采用递归的方式进行遍历搜索

/**
 * 扫描所有包以及子包下的类,返回全路径
 * @param files 传入需要扫描的文件
 */
private void scanFiles(File files)
    File[] f = files.listFiles();
    for (File file : f) 
        if(file.isDirectory())
            scanFiles(file);
        else
            String absolutePath = file.getAbsolutePath();
            if(absolutePath.endsWith("class")) 
                // 例如:com.Al_tair.spring.myComponent.myService.PrototypeServiceImpl
                list.add(absolutePath.substring(absolutePath.lastIndexOf("\\\\com") + 1, absolutePath.lastIndexOf(".class")).
                        replace("\\\\","."));
            
        
    

最后,我们要对指定注入的 bean 对象带上 MyComponent 注解以及 区分单例还是多例的 MyScope 注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponent 
    // 通过 value 可以给组件注入对象的id名,默认id名为类名首字母小写
    String value() default "";

/**
 * 指定一个 bean 的作用范围 【singleton,prototype】
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyScope 
    /**
     * 使用value来指定 singleton,prototype
     * @return
     */
    String value() default "";

然后我们需要遍历列表中每个类路径,并进行类加载,判断该类是否被 MyComponent 注解标注,如果有则将 id 和 Class 信息存储在 BeanDefinition 中

for (String s : list) 
    try 
        Class<?> aClass = classLoader.loadClass(s);
        // 判断该类是否被 MyComponent 注解标注
        if(aClass.isAnnotationPresent(MyComponent.class))
            MyComponent declaredAnnotation = aClass.getDeclaredAnnotation(MyComponent.class);
            // beanName
            String id = declaredAnnotation.value();
            if("".equals(id))
                // 将类名首字母小写作为 id
                id = s.substring(s.lastIndexOf(".") + 1,s.lastIndexOf(".") + 2).toLowerCase()
                    + s.substring(s.lastIndexOf(".")+2);
            
		   // 将 id 和 Class 信息存储在 BeanDefinition 中
            BeanDefinition beanDefinition = new BeanDefinition();
            beanDefinition.setClazz(aClass);
            // 判断是单例还是多例
            if(aClass.isAnnotationPresent(MyScope.class))
                // 标注注解则从注解的 value 取出值判断是多例还是单例
                MyScope ScopeValue = aClass.getDeclaredAnnotation(MyScope.class);
                beanDefinition.setScope(ScopeValue.value());
            else
                // 没有标注注解默认为单例对象
                beanDefinition.setScope("singleton");
            
            ioc.put(id,beanDefinition);

            // 如果是单例对象,放入单例池
            if("singleton".equals(beanDefinition.getScope()))
                Object bean = createBean(beanDefinition,id);
                singletonObject.put(id,bean);
            
        
     catch (Exception e) 
        e.printStackTrace();
    


3.创建 Bean 对象

分三种情况:

  • 依赖注入 MyAutowired 方法:在其他类 创建的时候需要注入其他 bean 对象(控制反转,动态依赖注入)
  • 单例对象直接获取该 Bean 对象(而且只能创建一次)
  • 多例对象每次获取都需要创建 Bean 对象(每个对象都是不同的)
/**
 * 创建 bean 对象
 */
private Object createBean(BeanDefinition beanDefinition,String beanName)
    // 得到 bean 的 Class 对象
    Class clazz = beanDefinition.getClazz();
    try 
        // 通过反射得到 bean 实例
        Object instanse = clazz.getDeclaredConstructor().newInstance();

        // 依赖注入
        // 1.遍历当前的所有对象的字段
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) 
            // 2. 判断字段是否被特定注解注释
            if(declaredField.isAnnotationPresent(MyAutowired.class))
                MyAutowired annotation = declaredField.getAnnotation(MyAutowired.class);
                // 3.是否必须依赖注入
                boolean required = annotation.required();
                if(required)
                    // 4.获取字段名字
                    String name = declaredField.getName();
                    // 5.通过 getBean 来组装对象
                    Object bean = getBean(name);
                    // 6.进行组装(因为属性是私有的,所以需要进行暴破)
                    declaredField.setAccessible(true);
                    declaredField.set(instanse,bean);
                else
                    // 假设都需要依赖注入
                
            
        
        return instanse;
     catch (Exception e) 
        e.printStackTrace();
    
    // 如果创建对象失败
    return null;


4.获取 Bean 对象

通过 id 来获取 bean 对象

public Object getBean(String id)
    BeanDefinition beanDefinition = ioc.get(id);
    if(beanDefinition != null && ioc.containsKey(id))
        String scope = beanDefinition.getScope();
        // 如果是单例对象,直接获取
        if("singleton".equals(scope))
            return singletonObject.get(id);
        else
            // 反之多例对象,需要创建 Bean
            return createBean(beanDefinition,id);
        
    else
        throw  new RuntimeException("没有该 bean 对象");
    

讲到这里 IOC 容器就算简单的创建出来了,接下来我们需要讲一下 AOP机制


2.实现 AOP 机制

1.首先需要实现添加对象初始化接口
/**
 * 初始化 Bean 对象的接口:用来初始化 Bean 的时候调用
 */
public interface MyInitializingBean 
    // 初始化方法:在 setter 方法执行后调用
    void afterPropertiesSet() throws Exception;

实现了该初始化接口的类:当创建 MyServiceImpl 对象的时候将会初始化

@MyComponent
public class MyServiceImpl implements MyInitializingBean 
    @MyAutowired
    private MyDaoImpl myDaoImpl;

    @Override
    public void afterPropertiesSet() throws Exception 
        System.out.println("MyServiceImpl 初始化方法被调用");
    

将初始化操作放在创建 Bean 对象方法的后面

private Object createBean(BeanDefinition beanDefinition,String beanName)
    // 得到 bean 的 Class 对象
    Class clazz = beanDefinition.getClazz();
    // 通过反射得到 bean 实例
    try 
        Object instanse = clazz.getDeclaredConstructor().newInstance();
        // 依赖注入
        // ...省略

        // 创建好实例之后进行初始化
        // instanceof: 判断当前某个对象的运行类型是否为该类型实现的接口或者父类
        if(instanse instanceof MyInitializingBean)
            ((MyInitializingBean)instanse).afterPropertiesSet();
        
        return instanse;
     catch (Exception e) 
        e.printStackTrace();
    
    // 如果创建对象失败
    return null;

2.后置处理器的实现

MyBeanPostProcessor 接口实现:主要是在所有 bean 对象初始化前后调用(切面编程)

/**
 * 后置处理器:会对 Spring 容器中的所有 Bean实例生效(切面编程)
 */
public interface MyBeanPostProcessor 
    /**
     * 在 bean 对象初始化方法之前调用
     *
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) 
        return bean;
    

    /**
     * 在 bean 对象初始化方法之后调用
     *
     * @param bean
     * @param beanName
     * @return
     */
    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) 
        return bean;
    

实现自制的后置处理器接口: AOP 动态代理机制的实现核心就是后置处理器实现类中实现

/**
 * 实现自制化的后置处理器
 */
@MyComponent
public class LnsBeanPostProcessor implements MyBeanPostProcessor 

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) 
        // 后置处理器会在容器初始化 bean 对象之前生效
        // 通过 instanceof 关键字判断,可以用于多个对象的编程
        // 可以用来处理日志、权限、身份、事务等
        System.out.println("Before=" + beanName);
        return bean;
    

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) 
        System.out.println("After=" + beanName);
        return bean;
    

为了简化操作,我将后置处理器对象单独放一个容器中,方便调用对象初始化方法前后调用

    /**
     * 存放实现了 MyBeanPostProcessor 接口的后置处理器的对象
     * 实际是存放在单例池中,这里为了方便取出后置处理器的对象单独写个集合
     */
    private List<MyBeanPostProcessor> beanPostProcessorList  = new ArrayList<>();

为了添加后置处理器对象,需要使用 isAssignableFrom() 方法判断某个类是否实现了某个接口(注意区别 instanceof 关键字)

// beanDefinitionByScan 方法
for (String s : list) 
    try 
        Class<?> aClass = classLoader.loadClass(s);
        if(aClass.isAnnotationPresent(MyComponent.class))
            // 判断是否实现了MyBeanPostProcessor 接口
            // 这里不能使用instanceof来判断,因为instanceof是用来判断实例对象,但是判断某个类是否实现了某个接口就要用底下这个方法
            if(MyBeanPostProcessor.class.isAssignableFrom(aClass))
                MyBeanPostProcessor myBeanPostProcessor = (MyBeanPostProcessor)aClass.newInstance();
                beanPostProcessorList.add(myBeanPostProcessor);
                continue;
            
            MyComponent declaredAnnotation = aClass.getDeclaredAnnotation(MyComponent.class);
            // beanName
            String id = declaredAnnotation.value();
            if("".equals(id))
                // 将类名首字母小写作为 id
                id = s.substring(s.lastIndexOf(".") + 1,s.lastIndexOf(".") + 2).toLowerCase()
                    + s.substring(s.lastIndexOf(".")+2);
            

            BeanDefinition beanDefinition = new BeanDefinition();
            beanDefinition.setClazz(aClass);
            // 判断是单例还是多例
            if(aClass.isAnnotationPresent(MyScope.class))
                MyScope ScopeValue = aClass.getDeclaredAnnotation(MyScope.class);
                beanDefinition.setScope(ScopeValue.value());
            else
                beanDefinition.setScope("singleton");
            
            ioc.put(id,beanDefinition);

            // 如果是单例对象,放入单例池
            if("singleton".equals(beanDefinition.getScope()))
                Object bean = createBean(beanDefinition,id);
                singletonObject.put(id,bean);
            
        
     catch (Exception e) 
        e.printStackTrace();
    

最后需要在初始化方法前后添加后置处理器对象的调用

// createBean 方法
// 后置处理器
for (MyBeanPostProcessor myBeanPostProcessor : beanPostProcessorList) 
    Object o = myBeanPostProcessor.postProcessBeforeInitial

以上是关于简易实现Spring Framework底层机制的主要内容,如果未能解决你的问题,请参考以下文章

SpringMVC 底层机制的简易实现

SpringMVC 底层机制的简易实现

java spring的底层机制和原理是啥?

如何实现一个简易版的 Spring

Spring-AOP简介与底层实现机制,值得推荐!

Spring的 AOP底层用到两种代理机制