手动实现 Spring 底层机制 【初始化 IOC容器+ 依赖注入+BeanPostProcessor 机制+AOP】

引言:前面我们已经用代码简单实现了Spring基于注解配置的程序

下载地址:https://download.youkuaiyun.com/download/2401_83418369/90540102

手写简单的Spring基于注解配置的程序-优快云博客

需求:

1、原生 Spring 如何实现依赖注入和 singleton 、prototype

2、Spring 底层 如何实现 Bean 后置处理器机制

3、原生 Spring 是如何实现 AOP

目标:

不用 Spring 框架, 模拟 Spring 底层实现, 也能完成相同的功能

Spring 整体架构分析:

配置环境:

先创建一个Maven项目,在原来的项目下面新建一个Maven模块

<dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.8</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.3.8</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.1</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>

实现1: 编写自己的 Spring 容器,实现扫描包,得到 bean的Class 对象

分析思路:

结合前面已经实现了 Spring基于注解配置的程序,所以这里可以直接复制一下前面的代码,修改的点有:

1、注入的bean对象全部都用@Component修饰,所以增加自定的注解@Component

代码如下:

编写自定义注解ComponentScan 

package com.study.Spring.annotation;

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

/**
 * 相当于扫描配置属性Component-Scan
 *
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
    String value() default "";
}

编写自定义注解Component 

package com.study.Spring.annotation;

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

/**
 * 这个注解相当于@Component组件注解,可以自定义bean的id名字
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
    String value() default "";
}

编写扫描的配置类

package com.study.Spring.ioc;

import com.study.Spring.annotation.ComponentScan;

/**
 * 这个文件的作用相当于配置信息Component-scan
 */
@ComponentScan("com.study.Spring.component")
public class SpringConfig {
}

编写要注入的bean对象UserDao

package com.study.Spring.component;

import com.study.Spring.annotation.Component;

@Component("userDao")
public class UserDao {
}

编写要注入的bean对象 UserService

package com.study.Spring.component;

import com.study.Spring.annotation.Component;
import com.study.Spring.annotation.Scope;

@Component("userService")
public class UserService {
}

编写类MySpringApplicationContext充当ioc容器 

package com.study.Spring.ioc;

import com.study.Spring.annotation.Component;
import com.study.Spring.annotation.ComponentScan;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;

 public class MySpringApplicationContext {
        //容器的属性:MySpringConfig是配置类的Class对象,用于反射创建注解读取类名
        //ioc属性相当于原生的SingleTonObjects用于存储单例bean对象
        private Class MySpringConfig;

        public MySpringApplicationContext(Class mySpringConfig) {
            beanDefinitionByScan(mySpringConfig);
        }
        //这是扫描beanDefinition的方法
        public void beanDefinitionByScan(Class mySpringConfig){
            this.MySpringConfig = mySpringConfig;
            //反射获取自定义的注解对象,再获取注解中的包名
            ComponentScan componentScan = (ComponentScan) MySpringConfig.getDeclaredAnnotation(ComponentScan.class);
            String path = componentScan.value();
            path = path.replace(".", "/");
            //根据包名获取实际工作目录的路径,再通过io流获取扫描的类的绝对路径
//file:/C:/Users/DELL/IdeaProjects/Spring/out/production/Spring/com/study/Spring/component
            //这里拿到类加载器用于获取实际工作目录
            ClassLoader classLoader = MySpringConfig.getClassLoader();
            URL realPath = classLoader.getResource(path);
//     /C:/Users/DELL/IdeaProjects/Spring/out/production/Spring/com/study/Spring/component
            String filePath = realPath.getFile();
            File file = new File(filePath);
            //通过目录获取目录下的所有Class文件
            if (file.isDirectory()) {
                File[] files = file.listFiles();
                for (File item : files) {
                    String name = item.getName();
                    if (name.contains(".class")) {
                        //通过字符串的拼接获取包下面的全类名
                        path = path.replace("/", ".");
                        String className = name.substring(0, name.indexOf("."));
                        //获取全类名
                        String fullPath = path + "." + className;
//   再根据Class文件进行一次筛选:判断是否有四种注解的类才进行创建实例对象并存储在容器中
                        try {
                            //通过classloader获取的Class对象时轻量级的,不会初始化时调用静态方法
                            Class<?> aClass = classLoader.loadClass(fullPath);
                            //根据不同类的Class对象判断是否有指定的注解
                            if (aClass.isAnnotationPresent(Component.class)) {
                                System.out.println("要注入的bean对象"+aClass);
                            }else {
                                System.out.println("该Class没有声明注解");
                            }
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }

编写测试类 

package com.study.Spring;

import com.study.Spring.ioc.MySpringApplicationContext;
import com.study.Spring.ioc.SpringConfig;

public class MySpringTest {
    public static void main(String[] args) {
        MySpringApplicationContext ioc = new MySpringApplicationContext(SpringConfig.class);
        System.out.println("ok");
    }
}

结果: 

实现2:扫描将 bean 信息封装到 BeanDefinition 对象,并放入到 BeanDefinitionMap

分析思路:

切入点:判断是否存在注解@Component的条件下

beanDefinition要封装的信息有scope和Class对象,又因为BeanDefinition 这个bean对象又要存储到BeanDefinitionMap中,所以还需要获取Map的id(即:原生BeanDefinitionMap的id默认是首字母小写的对象名)但是如果自己声明了id,就是自己写的id

所以首先判断的是有没有自定义component属性value,这里先打通一条线(默认有该属性的情况),还需要判断是否制定了scope的属性,如果有就获取该属性并赋值到BeanDefinition 如果没有就设置该BeanDefinition的scope属性是singleton,因为默认没有声明注解@Scope的属性value的情况下是单例对象

新增自定义注解Scope: 

package com.study.Spring.annotation;

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

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

MySpringApplicationContext类:

package com.study.Spring.ioc;

import com.study.Spring.annotation.Component;
import com.study.Spring.annotation.ComponentScan;
import com.study.Spring.annotation.Scope;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;

/**
 *这个类相当于容器
 *
 */
    public class MySpringApplicationContext {
        //容器的属性:MySpringConfig是配置类的Class对象,用于反射创建注解读取类名
        //ioc属性相当于原生的SingleTonObjects用于存储单例bean对象
        private Class MySpringConfig;
        private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap=new ConcurrentHashMap<>();
        private ConcurrentHashMap<String,Object> singletonObjects=new ConcurrentHashMap<>();

        public MySpringApplicationContext(Class mySpringConfig) {
            beanDefinitionByScan(mySpringConfig);
        }
        //这是扫描beanDefinition的方法
        public void beanDefinitionByScan(Class mySpringConfig){
            this.MySpringConfig = mySpringConfig;
            //反射获取自定义的注解对象,再获取注解中的包名
            ComponentScan componentScan = (ComponentScan) MySpringConfig.getDeclaredAnnotation(ComponentScan.class);
            String path = componentScan.value();
            path = path.replace(".", "/");
            //根据包名获取实际工作目录的路径,再通过io流获取扫描的类的绝对路径
//file:/C:/Users/DELL/IdeaProjects/Spring/out/production/Spring/com/study/Spring/component
            //这里拿到类加载器用于获取实际工作目录
            ClassLoader classLoader = MySpringConfig.getClassLoader();
            URL realPath = classLoader.getResource(path);
//     /C:/Users/DELL/IdeaProjects/Spring/out/production/Spring/com/study/Spring/component
            String filePath = realPath.getFile();
            File file = new File(filePath);
            //通过目录获取目录下的所有Class文件
            if (file.isDirectory()) {
                File[] files = file.listFiles();
                for (File item : files) {
                    String name = item.getName();
                    if (name.contains(".class")) {
                        //通过字符串的拼接获取包下面的全类名
                        path = path.replace("/", ".");
                        String className = name.substring(0, name.indexOf("."));
                        //获取全类名
                        String fullPath = path + "." + className;
//   再根据Class文件进行一次筛选:判断是否有四种注解的类才进行创建实例对象并存储在容器中
                        try {
                            //通过classloader获取的Class对象时轻量级的,不会初始化时调用静态方法
                            Class<?> aClass = classLoader.loadClass(fullPath);
                            //根据不同类的Class对象判断是否有指定的注解
                            if (aClass.isAnnotationPresent(Component.class)) {
                                //先将beanDefinitionMap的id拿到
                                //这里通过反射获取Component注解,根据注解再取得value,即自己指定的id
                                Component component = aClass.getDeclaredAnnotation(Component.class);
                                String id = component.value();
                                //如果没有设置Component的属性值,那么就使用首字母小写的类名代替
                                if ("".equals(id)){
                                    id = StringUtils.uncapitalize(className);
                                }
                                BeanDefinition beanDefinition = new BeanDefinition();
                                //不变的量
                                beanDefinition.setClazz(aClass);
                                if (aClass.isAnnotationPresent(Scope.class)){//存在
                                    Scope scope = aClass.getDeclaredAnnotation(Scope.class);
                                    beanDefinition.setScope(scope.value());
                                }else {//不存在
                                    beanDefinition.setScope("singleton");
                                }
                                //存放到map中
                                beanDefinitionMap.put(id,beanDefinition);

                            }else {
                                System.out.println("该Class没有声明注解");
                            }
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }
        
    }

实现3:初始化 bean 单例池,并完成 getBean方法 , createBean 方法

分析思路:

在MySpringApplicationContext类中添加方法createBean,该方法用于创建对象,然后在初始化容器时,根据BeanDefinitionMap中的Scope情况,进行懒加载还是预加载,默认情况下单例对象是在容器初始化时完成初始化的。

在MySpringApplicationContext类中添加方法:

public Object createBean(BeanDefinition beanDefinition){
            //先获取beanDefinition中的Class对象
            Class clazz = beanDefinition.getClazz();
            try {
                Object instance = clazz.getDeclaredConstructor().newInstance();
                return instance;
            } 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);
            }
        }

MySpringApplicationContext类构造器 

public MySpringApplicationContext(Class mySpringConfig) {
            beanDefinitionByScan(mySpringConfig);
            //这里拿到Map里面所有键的集合,通过遍历可以得到所有Bean的信息
            Enumeration<String> keys = beanDefinitionMap.keys();
            while (keys.hasMoreElements()){
                String id = keys.nextElement();
                BeanDefinition beanDefinition = beanDefinitionMap.get(id);
                //将单例Bean对象进行创建对象,并放入singletonObjects容器
                if ("singleton".equals(beanDefinition.getScope())){
                    Object bean = createBean(beanDefinition);
                    singletonObjects.put(id,bean);
                }
            }
        }

实现4:完成依赖注入

思路分析:

依赖注入有两种方式,一种是通过配置文件注入依赖,还有一种是通过注解来注入依赖,通过注解@Autowired注入依赖是一种自动注入依赖的方式,这里需要在创建Bean对象时增加注入依赖的代码即在createBean方法中增加注入依赖的代码,那么在哪里增加代码呢?注入依赖其实就是给对象设置属性,所以应该在创建好对象之后就可以进行依赖注入

代码如下:

Field[] declaredFields = clazz.getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    //遍历所有字段,并判断是否有Autowired注解修饰
                    if (declaredField.isAnnotationPresent(Autowired.class)){
                        String name = declaredField.getName();
                        //根据注入的名字进行创建Bean对象,并将该Bean设置到该字段
                        Object bean = getBean(name);
                        //爆破
                        declaredField.setAccessible(true);
                        //给字段赋值,相当于注入依赖
                        declaredField.set(instance,bean);
                    }
                }

实现5:bean 后置处理器实现

思路分析:

根据原生的后置处理器对象需要实现BeanPostProcessor接口,所以可以根据原生的接口,自己实现两个重要的方法,然后再新建一个对象实现该接口

实现接口BeanPostProcessor

package com.study.Spring.process;

/**
 * 这是自己写的后置处理器
 * 主要有两个方法
 */
public interface BeanPostProcessor {

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


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

编写MyBeanPostProcessor 类实现该接口BeanPostProcessor

package com.study.Spring.component;

import com.study.Spring.annotation.Component;
import com.study.Spring.process.BeanPostProcessor;
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println("后置处理器的before方法被调用,Bean对象是"+bean);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("后置处理器的after方法被调用,Bean对象是"+bean);
        return bean;
    }
}

后置处理器相当于一个组件,所以在beanDefinitionByScan类中判断如果有@Component注解就可以进行下一步的判断,如果存在该类实现了BeanPostProcessor,就相当于一个后置处理器对象,因为后置处理器对象可以有多个,这里直接将该对象放入list集合中

private List<BeanPostProcessor> beanPostProcessors=new ArrayList<>();

 因为class是静态对象所以不能使用instance of方法判断该对象是否实现了该BeanPostProcessor接口,应该使用isAssignableFrom方法

                                //为了简化代码,后置处理器本身是一个组件
                                //原生的容器是将后置处理器当做组件通过createBean和getBean
                                //因为class是静态对象所以不能使用instance of方法判断该对象是否
                                // 实现了该BeanPostProcessor接口
                                if(BeanPostProcessor.class.isAssignableFrom(aClass)){
                                    BeanPostProcessor instance =(BeanPostProcessor) aClass.newInstance();
                                    beanPostProcessors.add(instance);
                                }

因为后置处理器的before方法和after方法是在init初始化方法调用前和调用后执行,所以这里还需要在createBean方法中的init方法的前后加入before和after方法

//在初始化方法调用前调用before方法
                for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
                    instance = beanPostProcessor.postProcessBeforeInitialization(instance, "...");
                }
//在初始化方法调用后调用after方法
                for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
                    instance = beanPostProcessor.postProcessAfterInitialization(instance, "...");
                }

 

最后在测试类中调试得到singleton中也有后置处理器对象,因为在原生的容器中,后置处理器是被当作一个主键 经历createBean和getBean方法,相当于将该后置处理器对象存储在singleton对象中然后再从该对象取出进行调用,但是现在为了方便调用后置处理器的方法,就直接将该对象放入list集合中,并在createBean方法中的初始化方法前后调用before和after方法

所以增加一个continue跳过后边的代码 

这样在singletonObjects中就没有该实现的对象了 

测试:新建一个类Car使用@Component自动注入依赖,然后再实现InitializingBean接口

package com.study.Spring.component;

import com.study.Spring.annotation.Component;
import com.study.Spring.process.InitializingBean;

@Component
public class Car implements InitializingBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("car的初始化方法。。。");
    }
}

结果:发现后置处理器能够生效 

后置处理器的作用相当于是aop思想,只要实现了Bean对象的创建就会调用before和after方法,相当于该方法作用于多个对象,如果想单独对某个对象进行处理,也可以使用instance of来筛选从而进行判断

解决问题:当后置处理器对象调用的方法返回的结果是空时怎么办呢?

原生的后置处理器即使你返回的结果是空,他也不影响原来的初始化的对象

所以需要在调用before和after方法的方法体中增加判断条件

实现6:AOP 机制实现

思路分析:

这里就采用硬编码方式:(后期再更新灵活方式)

编写一个接口Cal:

package com.study.Spring.aop;

public interface Cal {
    void sum(int num);
}

编写一个实现类MyCal: 

package com.study.Spring.aop;

import com.study.Spring.annotation.Component;

@Component
public class MyCal implements Cal{
    @Override
    public void sum(int num) {
        int result = 0;
        for (int i = 0; i <= num; i++) {
            result += i;
        }
        System.out.println("累加的总和:" + result);
    }
}

编写一个切面类CalAspect:

package com.study.Spring.aop;

import com.study.Spring.annotation.Component;

@Component
public class CalAspect {
    public static void showLog(){
        System.out.println("前置通知。。。。");
    }
    public static void showResult(){
        System.out.println("返回通知。。。。");
    }
}

MyBeanPostProcessor 类:

package com.study.Spring.component;

import com.study.Spring.annotation.Component;
import com.study.Spring.aop.CalAspect;
import com.study.Spring.process.BeanPostProcessor;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println("后置处理器的before方法被调用,Bean对象是"+bean);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("后置处理器的after方法被调用,Bean对象是"+bean);
        //这里相当于是切入点表达式的匹配方法类
        if ("myCal".equals(beanName)){
            Object proxyInstance = Proxy.newProxyInstance(MyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    Object invoke = null;
                    if ("sum".equals(method.getName())) {
                        CalAspect.showLog();
                        invoke = method.invoke(bean, args);
                        CalAspect.showResult();
                    } else {
                        invoke = method.invoke(bean, args);
                    }
                    return invoke;
                }
            });
            //这里返回代理对象
            return proxyInstance;
        }
        //没有匹配的类就直接返回原来的对象
        return bean;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值