栏目分类:
子分类:
返回
终身学习网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
终身学习网 > IT > 软件开发 > 后端开发 > Java

手写简易Spring

Java 更新时间:发布时间: 百科书网 趣学号

项目描述

项目描述:

  • 手写Spring启动以及扫描流程
  • 手下getBean()流程
  • 手写Bean生命周期流程
  • 手写依赖注入流程
  • 手写BeanPOSTProcessor机制
  • 手写AOP机制
0. Spring创建工厂的两种方式

1. 手写Spring前准备工作

注意:下面有些代码有的只是简单写了一下大致流程,具体细节会在之后实现。

Spring实现包中

Component注解

@Retention(RetentionPolicy.RUNTIME) // 表示在.class被装载时将被读取,在程序运行期间,将一直保留。
@Target(ElementType.TYPE)           // 表示这个注解只能写在类上
public @interface Component {

    String value() default "";      // 表示beanName
}

ComponentScan注解

@Retention(RetentionPolicy.RUNTIME) // 表示在.class被装载时将被读取,在程序运行期间,将一直保留。
@Target(ElementType.TYPE)           // 表示这个注解只能写在类上
public @interface ComponentScan {

    String value();      // 用来让用户指定扫描路径,默认值为空
}

ZhouyuApplicationContext类

public class ZhouyuApplicationContext {

    private Class configClass;

    public ZhouyuApplicationContext(Class configClass) {
        this.configClass = configClass;

        // 解析配置类
        // 解析ComponentScan注解 ---> 扫描路径 ---> 扫描
    }

    public Object getBean(String beanName){

        return null;
    }
}

zhouyu使用包中

UserService类

@Component("userService")
public class UserService {

}

AppConfig类

@ComponentScan("com.zhouyu.service")        // 扫描哪个包
public class AppConfig {
}

Test类

public class Test {

    public static void main(String[] args) {
        ZhouyuApplicationContext applicationContext = new ZhouyuApplicationContext(AppConfig.class);
        Object userService = applicationContext.getBean("userService");
    }
}

2. 启动和扫描逻辑具体实现

BeanDefinition类:

public class BeanDefinition {

    private Class clazz;    // 表示当前某一个bean的类型
    private String scope;   // 当前bean的作用域(单例还是多例)


    public Class getClazz() {
        return clazz;
    }

    public void setClazz(Class clazz) {
        this.clazz = clazz;
    }

    public String getScope() {
        return scope;
    }

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

Scope类:

@Retention(RetentionPolicy.RUNTIME) // 表示在.class被装载时将被读取,在程序运行期间,将一直保留。
@Target(ElementType.TYPE)           // 表示这个注解只能写在类上
public @interface Scope {

    String value();      // 用来让用户指定扫描路径,默认值为空
}

ZhouyuApplicationContext类

public class ZhouyuApplicationContext {

    private Class configClass;

    private ConcurrentHashMap singletonObjects = new ConcurrentHashMap<>(); // 单例池
    private ConcurrentHashMap beanDefinitionMap = new ConcurrentHashMap<>();


    public ZhouyuApplicationContext(Class configClass) {
        this.configClass = configClass;

        // 解析配置类
        // 解析ComponentScan注解 ---> 扫描路径 ---> 扫描 ---> Beandefinition ---> BeanDefinitionMap
        scan(configClass);  // 扫描

        // 对于单例bean在容器启动的时候就应该创建好
        for (Map.Entry entry : beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();
            if(beanDefinition.getScope().equals("singleton")){
                Object bean = createBean(beanDefinition);   // 单例Bean
                singletonObjects.put(beanName, bean);
            }
        }
    }

    public Object createBean(BeanDefinition beanDefinition){
        // 创建bean
        Class clazz = beanDefinition.getClazz();
        try {
            Object instance = clazz.getDeclaredConstructor().newInstance(); // 通过反射调用无参的构造方法得到一个对象
            return instance;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void scan(Class configClass) {

        //获取ComponentScan注解
        ComponentScan componentScanAnnotation = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);
        String path = componentScanAnnotation.value();  // 扫描路径  com.zhouyu.service
        path = path.replace(".", "/");  // 将 . 替换为 /

        // 扫描
        // 三种类加载器
        // Bootstrap --->jre/lib
        // Ext       --->jre/ext/lib
        // App       --->classpath      其实就是target中的classes目录
        ClassLoader classLoader = ZhouyuApplicationContext.class.getClassLoader();  // App类加载器
        URL resource = classLoader.getResource(path);// 相对路径,相对的就是classpath路径
        File file = new File(resource.getFile());       // 将resource转成file
        if(file.isDirectory()){                         // 判断当前file是不是一个目录
            File[] files = file.listFiles();       // 将目录下的所有文件拿出来
            for (File f : files) {
                String fileName = f.getAbsolutePath();  // 得到的这个路径是绝对路径,我们需要截取其中我们需要的
                if(fileName.endsWith(".class")){        // 是类文件的话才处理
                    // 将路径中不需要的去掉,把  改为 .  ,去掉最后的class
                    // 从com开始截取,截取到.class(不含.class),例如 comzhouyuserviceUserService
                    String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
                    // 将  替换为 .     \ 中第一个表示将第二个转义为普通的
                    className = className.replace("\", ".");

                    try {
                        Class clazz = classLoader.loadClass(className);   // // 加载类
                        if (clazz.isAnnotationPresent(Component.class)){   // 判断是否有Component注解
                            // 表示当前这个类是一个bean
                            // 解析类,判断当前类是单例bean还是prototype类型bean
                            // 解析类 --> BeanDefinition
                            Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
                            String beanName = componentAnnotation.value();  // 当前这个类的bean名字

                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setClazz(clazz);
                            if(clazz.isAnnotationPresent(Scope.class)){ // 存在Scope注解就获取这个类的Scope注解
                                Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
                                beanDefinition.setScope(scopeAnnotation.value());   // 有scope注解就设置为注解中的值
                            } else {
                                // 没有Scope注解表示当前这个bean是单例的
                                beanDefinition.setScope("singleton");
                            }
                            beanDefinitionMap.put(beanName, beanDefinition);

                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public Object getBean(String beanName){
        if(beanDefinitionMap.containsKey(beanName)){
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if(beanDefinition.getScope().equals("singleton")){      // 单例bean
                Object o = singletonObjects.get(beanName);
                return o;
            }else {     //多例
                // 每次都创建新的bean对象
                Object bean = createBean(beanDefinition);
                return bean;
            }
        } else {
            // 不存在这个bean的话就抛出空指针
            throw new NullPointerException();
        }

    }
}

实现之后可以测试一下单例bean和多例bean,看是否成功

3. 依赖注入模拟实现

@Autowired注解

@Retention(RetentionPolicy.RUNTIME) // 表示在.class被装载时将被读取,在程序运行期间,将一直保留。
@Target({ElementType.METHOD, ElementType.FIELD})           // 表示这个注解写在方法、属性上
public @interface Autowired {

}

ZhouyuApplicationContext

OrderService

@Component("orderService")
public class OrderService {

}

UserService

@Component("userService")
public class UserService {

    @Autowired
    private OrderService orderService;

    public void test(){
        System.out.println(orderService);
    }
}

测试类Test

public class Test {

    public static void main(String[] args) {
        ZhouyuApplicationContext applicationContext = new ZhouyuApplicationContext(AppConfig.class);
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.test();
    }
}

4. BeanNameAware接口回调模拟实现

如果我们想要在 UserService 中声明一个成员变量 BeanName,让Spring在启动时候将 UserService 在工厂中的名字传给这个值,我们应该如何实现呢,这就需要用到 BeanNameAware 接口了。

BeanNameAware

public interface BeanNameAware {

    void setBeanName(String name);
}

UserService

@Component("userService")
public class UserService implements BeanNameAware {

    @Autowired
    private OrderService orderService;

    private String beanName;        // 想要给这个字符串赋这个类在工厂中的名字

    @Override
    public void setBeanName(String name) {
        // Spring 会来调这个方法,把当前bean的名字传给这个方法即name
        beanName = name;
    }

    public void test(){
        System.out.println(orderService);
        System.out.println(beanName);
    }


}

最后我们需要在Spring中在启动时将bean的名字传给setBeanName并且调用setBeanName方法

ZhouyuApplicationContext

还有几处调用createBean的地方需要修改

最后测试一下

5. InitializingBean初始化机制模拟实现

如果想要在初始化的时候专门有一个方法用于处理一些逻辑时,我们应该怎么做呢?

InitializingBean

public interface InitializingBean {

    void afterPropertiesSet() throws Exception;
}

UserService

ZhouyuApplicationContext

测试

6. BeanPostProcessor模拟实现

BeanPostProcessor 是 Spring 对外提供的扩展机制

BeanPostProcessor接口

public interface BeanPostProcessor {

    // 初始化前执行这个方法
    Object postProcessBeforeInitialization(Object bean, String beanName);

    // 初始化后执行这个方法
    Object postProcessAfterInitialization(Object bean, String beanName);
}

我们如何使用 BeanPostProcessor 呢?

我们新创建一个类,然后让其实现 BeanPostProcessor接口,实现其中的两个方法,一个在初始化bean前调用,一个在初始化bean后调用,在其中写我们自己想要写的逻辑。

@Component
public class ZhouyuBeanPostProcessor implements BeanPostProcessor {

    // spring在创建任何bean的时候都会执行下面的两个方法

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        // 初始化前调用这个方法
        // 针对一个bean或者多个bean同时处理都行
        if(beanName.equals("userService")){
            System.out.println("初始化前");
            ((UserService)bean).setName("周瑜好帅");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        // 初始化后调用这个方法
        System.out.println("初始化后");
        return bean;
    }
}

那么在 Spring 中我们如何实现呢?

ZhouyuApplicationContext

测试

7. AOP模拟实现

AOP 其实就是利用 BeanPostProcessor 实现的,并且它就是在初始化后这一步实现的。

UserService

测试

转载请注明:文章转载自 www.051e.com
本文地址:http://www.051e.com/it/1065090.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 ©2023-2025 051e.com

ICP备案号:京ICP备12030808号